[CodeGate2014] nuclear : libpthead(send, recv, system)
아이디어 자체는 어렵지 않았으나 `` libc``가 아닌 다른 library의 함수가 호출될거라는 생각을 못했기 때문에 꽤 헤맸다.
recv buffer 초기화에 별로 신경을 안썼더니, send의 결과로 받은 값에 이전 send 결과로 받은 데이터가 포함되어 몇 byte 씩 틀어져 있거나 처음에 303031이 들어가 시간을 꽤 소모함.
fork를 사용하는 standalone 방식의 서버 프로그램이다.
leak
``c __isoc99_sscanf()`` 실행 이후 stack context
```
0000| 0xffffd050 --> 0xffffd070 ("1/1\n")
0004| 0xffffd054 --> 0x8049877 ("%f/%f")
0008| 0xffffd058 --> 0xffffd274 --> 0x3f800000
0012| 0xffffd05c --> 0xffffd270 --> 0x3f800000
gdb-peda$ x/16x 0xffffd270
0xffffd270: 0x3f800000 0x3f800000 0x53534150 0x45444f43
0xffffd280: 0x00000a7e 0x00000000 0x00000000 0x00000000
0xffffd290: 0x00000000 0x00000000 0x00000004 0x0804c008
0xffffd2a0: 0xf7fa7000 0x00000001 0xffffd318 0x080490d9
```
* 부동소수점이라 다르게 박힌다.
검사를 통과하기 위해서는 1. passcode를 leak하거나, 2. passcode에 저장되어있는 값을 null byte 등으로 변경해야 한다.
passcode에 있는 값을 변경하려면 그 위에 위치한 buf, longtitude, latitude에서 overflow가 발생해야 하는데 별 다른 overflow 지점이 보이지는 않는다.
* latitude나 longtitude의 타입이 다르기는 하지만, 어차피 ``c %f``로 읽어오기 때문에 4byte가 넘어가지는 않는다.
latitude, longtitude 4byte를 모두 채워 passcode와 이어지게 하는 것은 가능한데,
passcode를 어떻게 출력할 것인가가 문제다.
```
0000| 0xffffd050 --> 0xffffd070 ("12345678/12345678\n")
0004| 0xffffd054 --> 0x8049877 ("%f/%f")
0008| 0xffffd058 --> 0xffffd274 ("Na<KPASSCODE~\n")
0012| 0xffffd05c --> 0xffffd270 ("Na<KNa<KPASSCODE~\n")
gdb-peda$ x/s 0xffffd270
0xffffd270: "Na<KNa<KPASSCODE~\n"
```
이런 식으로 PASSCODE가 노출될 수는 있으나 latitude나 longtitude를 대상으로 하는 출력 코드가 따로 없다.
그러나, 아무 command나 입력하면 `` [!] Unknown command : asdfds``가 발생하면서 ``c buf``가 출력되므로 `` buf, latitude, longtitude, passcode``를 모두 잇는다면 passcode가 leak된다.
exploit
launch 메뉴에서 passcode를 입력하면 thread(t1)을 생성하며, t1이 다시 t2를 생성한다.
t1은 100초가 지나면 종료되고 t2는 그냥 buf에서 `` 0x512``만큼 recv한다.
buf의 크기는 `` 0x512``가 아니라 그냥 `` 512(0x200)``이기 때문에 여기서 overflow가 발생한다.
처음 t1, t2가 생성되고 나서는 buf가 비어있기 때문에, t2는 일단 recv에서 blocking 되어 있는 상태라 데이터를 전송해 overflow를 일으킬 수 있다.
두 가지 방법으로 해결할 수 있는데, 둘 모두 PIE가 안걸려 있기 때문에 가능한 방법이다.
#1-1 libpthread RTL - system + nc
PIE가 안걸려 있으므로 ``c send(GOT)``로 libc_base를 구해 ``c system()`` 등의 주소를 알아낼 수 있으며 ret과 param위치를 조작할 수 있으니 RTL을 사용할 수 있다.
`` send@got / recv@got``를 leak해서 libc_base를 구하려고 했는데, 자꾸 안되길래 확인해보니
```bash
0xf7faf840 0xf7fbc467 Yes (*) /lib/i386-linux-gnu/libpthread.so.0
0xf7e13490 0xf7f4499e Yes (*) /lib/i386-linux-gnu/libc.so.6
```
`` libpthread.so.0``도 매핑되어 있다.
```bash
gdb-peda$ print recv
$2 = {<text variable, no debug info>} 0xf7fb90a0 <recv>
```
``c recv()``는 `` libc.so.6``이 아니라, ``libpthread.so.0``에 위치한다. ( Ubuntu 14.04.5 LTS )
추가로 ``c send() / system()``도 ``libpthread.so.0``에 위치한다.
* 그래서 ``c system()``을 사용한 exploit은 ``c send(), recv()``를 기준으로 구한 libpt_base를 사용했을 때 제대로 동작한다.
해서, 이를 이용해 base를 구한다면 이는 `` libpt_base``가 된다.
문제는 libpthread-db같은게 존재하지 않아서 서버에서 사용하는 libpthread 버전을 알아내는 것이 어렵고, 따라서 base를 구하기가 어렵다는 점이다.
그냥 서버에 있는 libpthread를 대상으로 `` objdump``로 base와 offset을 계산했다.
https://github.com/umbum/pwn/blob/master/exploit/cg_nuclear.py#L21-L67
``c system()``을 사용하면 ``c recv()``로 ``bash /bin/sh | nc``를 넘긴 다음 이를 인자로 사용할 수 있기 때문에, stdio를 연결해 줄 필요가 없어 수월하다.
#1-2 libc RTL - dup2 + exec*
``c system()``은 libpt에 있기 때문에 사용할 수 없다. 따라서 libc에 있는 ``c exec*()``를 사용해야 한다.
xinetd가 아니라 standalone이기 때문에 표준입출력이 소켓과 연결되어있지 않아 `` nc``를 사용하는 것이 편한데,
``c exec*('/bin/sh')``를 사용한다면 `` nc``를 사용할 수 없어 `` STDIO``와 `` sockfd``를 연결해주어야 한다.
libc_base는 ``c send(), recv()``말고 다른 함수를 기준으로 구해야 한다.
여기서는 fclose@got를 사용했고 libc-database를 사용했다.
one shot을 쓰는게 간단한데, 모두 동작하지 않아 그냥 RTL했다.
https://github.com/umbum/pwn/blob/master/exploit/cg_nuclear.py#L72-L115
#2 Shellcode
``c recv()``를 사용할 수 있으니, ROP chain을 구성해 recv로 buf 위치를 지정해 적당한 곳에 쉘코드를 쓴 다음 buf로 리턴할 수 있다. 근데 ``c mprotect()``도 호출해줘야 해서 좀 번거롭다. 귀찮아서 이렇게는 안풀었다.
?
```c
snprintf(&s, 0x80u, "%c[2J%c[0;0H", 27, 27); // shell output clear
snprintf(&s, 0x80u, "%c[%d;%dH", 27, 5, 5); // shell cursor return to top
```
'Security > CTF' 카테고리의 다른 글
[CodeGate2014] angry_doraemon (0) | 2017.08.24 |
---|---|
[MCSC2014] tinypwn - SROP (0) | 2017.08.10 |
[Samsung CTF 2017] ASM (Addition, Subtract, and Multiplication) (0) | 2017.07.10 |
[0ctf] BABYHEAP - fastbin attack, chunk overlap (0) | 2017.07.02 |
[SecuInside 2017] BABYHEAP : realloc(ptr, 0) / UAF (0) | 2017.07.02 |