堆棧是計(jì)算機(jī)中的兩種重要數(shù)據(jù)結(jié)構(gòu) 堆(Heap)和棧(Stack)它們?cè)谟?jì)算機(jī)程序中起著關(guān)鍵作用,在內(nèi)存中堆區(qū)(用于動(dòng)態(tài)內(nèi)存分配)和棧區(qū)(用于存儲(chǔ)函數(shù)調(diào)用、局部變量等臨時(shí)數(shù)據(jù)),進(jìn)程在運(yùn)行時(shí)會(huì)使用堆棧進(jìn)行參數(shù)傳遞,這些參數(shù)包括局部變量,臨時(shí)空間以及函數(shù)切換時(shí)所需要的棧幀等。
- 棧(Stack)是一種遵循后進(jìn)先出(LIFO)原則的線性數(shù)據(jù)結(jié)構(gòu)。它主要用于存儲(chǔ)和管理程序中的臨時(shí)數(shù)據(jù),如函數(shù)調(diào)用和局部變量。棧的主要操作包括壓棧(添加元素)和彈棧(移除元素)。
- 堆(Heap)是一種樹形數(shù)據(jù)結(jié)構(gòu),通常用于實(shí)現(xiàn)優(yōu)先隊(duì)列。堆中的每個(gè)節(jié)點(diǎn)都有一個(gè)鍵值(key),并滿足特定性質(zhì)。最常見的堆類型是二叉堆(包括最大堆和最小堆)。堆在計(jì)算機(jī)程序中的應(yīng)用包括堆排序算法和內(nèi)存管理等。
而針對(duì)棧地址的分析在漏洞挖掘中尤為重要,棧溢出(Stack Overflow)是一種計(jì)算機(jī)程序中的運(yùn)行時(shí)錯(cuò)誤,通常發(fā)生在緩沖區(qū)(buffer)中。緩沖區(qū)是一段內(nèi)存空間,用于臨時(shí)存儲(chǔ)數(shù)據(jù)。當(dāng)程序試圖向棧中寫入過多數(shù)據(jù)時(shí),可能導(dǎo)致棧溢出,從而破壞其他內(nèi)存區(qū)域或?qū)е鲁绦虮罎ⅲ瑖?yán)重的則可能會(huì)導(dǎo)致黑客控制EIP指針,而執(zhí)行惡意代碼。
棧溢出的原因主要有以下幾點(diǎn):
-
遞歸調(diào)用過深:當(dāng)函數(shù)遞歸調(diào)用自身的層次過深時(shí),可能導(dǎo)致棧溢出。這是因?yàn)槊看魏瘮?shù)調(diào)用都會(huì)在棧中分配內(nèi)存,用于存儲(chǔ)函數(shù)的局部變量和返回地址。如果遞歸層數(shù)太多,可能導(dǎo)致??臻g不足,從而引發(fā)棧溢出。
-
局部變量占用過多??臻g:如果函數(shù)中的局部變量(尤其是數(shù)組和結(jié)構(gòu)體)占用過多??臻g,可能導(dǎo)致棧溢出。這種情況下,可以考慮將部分局部變量移到堆內(nèi)存中,以減小??臻g的壓力。
-
緩沖區(qū)溢出:當(dāng)程序向緩沖區(qū)寫入的數(shù)據(jù)超過其分配的空間時(shí),可能發(fā)生緩沖區(qū)溢出。這種溢出可能導(dǎo)致棧空間中的其他數(shù)據(jù)被破壞,從而引發(fā)棧溢出。
LyScript 插件中提供了針對(duì)堆棧的操作函數(shù),對(duì)于堆的開辟與釋放通??墒褂?code>create_alloc()及delete_alloc()
在之前的文章中我們已經(jīng)使用了堆創(chuàng)建函數(shù),本章我們將重點(diǎn)學(xué)習(xí)針對(duì)棧的操作函數(shù),棧操作函數(shù)有三種,其中push_stack
用于入棧,pop_stack
用于出棧,而最有用的還屬peek_stack
函數(shù),該函數(shù)可用于檢查指定堆棧位置處的內(nèi)存參數(shù),利用這個(gè)特性就可以實(shí)現(xiàn),對(duì)堆棧地址的檢測(cè),或?qū)Χ褩5膾呙璧取?/p>
讀者注意:由于peek_stack命令傳入的堆棧下標(biāo)位置默認(rèn)從0開始,而輸出的結(jié)果則一個(gè)十進(jìn)制有符號(hào)長(zhǎng)整數(shù),一般而言有符號(hào)數(shù)會(huì)出現(xiàn)復(fù)數(shù)的情形,讀者在使用時(shí)應(yīng)更具自己的需求自行轉(zhuǎn)換。
而針對(duì)有符號(hào)與無(wú)符號(hào)數(shù)的轉(zhuǎn)換也很容易實(shí)現(xiàn),long_to_ulong
函數(shù)用于將有符號(hào)整數(shù)轉(zhuǎn)換為無(wú)符號(hào)整數(shù)(long_to_ulong)而與之對(duì)應(yīng)的ulong_to_long
函數(shù),則用于將無(wú)符號(hào)整數(shù)轉(zhuǎn)換為有符號(hào)整數(shù)(ulong_to_long)。這些函數(shù)都接受一個(gè)整數(shù)參數(shù)(inter)和一個(gè)布爾參數(shù)(is_64)。當(dāng) is_64
為 False
時(shí),函數(shù)處理32位整數(shù);當(dāng) is_64
為 True
時(shí),函數(shù)處理64位整數(shù)。
-
有符號(hào)整數(shù)轉(zhuǎn)無(wú)符號(hào)數(shù)(long_to_ulong):通過將輸入整數(shù)與相應(yīng)位數(shù)的最大值執(zhí)行按位與操作
(&)
來實(shí)現(xiàn)轉(zhuǎn)換。對(duì)于32位整數(shù),使用(1 << 32) - 1
計(jì)算最大值;對(duì)于64位整數(shù),使用(1 << 64) - 1
計(jì)算最大值。 -
無(wú)符號(hào)整數(shù)轉(zhuǎn)有符號(hào)數(shù)(ulong_to_long):通過計(jì)算輸入整數(shù)與相應(yīng)位數(shù)的最高位的差值來實(shí)現(xiàn)轉(zhuǎn)換。首先,它使用按位與操作
(&)
來計(jì)算輸入整數(shù)與最高位之間的關(guān)系。對(duì)于32位整數(shù),使用(1 << 31) - 1 和 (1 << 31)
;對(duì)于64位整數(shù),使用(1 << 63) - 1
和(1 << 63)
。然后,將這兩個(gè)結(jié)果相減以獲得有符號(hào)整數(shù)。
from LyScript32 import MyDebug
# 有符號(hào)整數(shù)轉(zhuǎn)無(wú)符號(hào)數(shù)
def long_to_ulong(inter,is_64 = False):
if is_64 == False:
return inter & ((1 << 32) - 1)
else:
return inter & ((1 << 64) - 1)
# 無(wú)符號(hào)整數(shù)轉(zhuǎn)有符號(hào)數(shù)
def ulong_to_long(inter,is_64 = False):
if is_64 == False:
return (inter & ((1 << 31) - 1)) - (inter & (1 << 31))
else:
return (inter & ((1 << 63) - 1)) - (inter & (1 << 63))
if __name__ == "__main__":
dbg = MyDebug()
connect_flag = dbg.connect()
print("連接狀態(tài): {}".format(connect_flag))
for index in range(0,10):
# 默認(rèn)返回有符號(hào)數(shù)
stack_address = dbg.peek_stack(index)
# 使用轉(zhuǎn)換
print("默認(rèn)有符號(hào)數(shù): {:15} --> 轉(zhuǎn)為無(wú)符號(hào)數(shù): {:15} --> 轉(zhuǎn)為有符號(hào)數(shù): {:15}".
format(stack_address, long_to_ulong(stack_address),ulong_to_long(long_to_ulong(stack_address))))
dbg.close()
如上代碼中我們?cè)诋?dāng)前堆棧中向下掃描10條,并通過轉(zhuǎn)換函數(shù)以此輸出該堆棧信息的有符號(hào)與無(wú)符號(hào)形式,這段代碼輸出效果如下圖所示;
我們繼續(xù)完善這個(gè)功能,通過使用get_disasm_one_code()
獲取到堆棧的反匯編代碼,并以此來進(jìn)行更多的判斷形勢(shì),如下代碼中只需要增加反匯編一行功能即可。
if __name__ == "__main__":
dbg = MyDebug()
connect_flag = dbg.connect()
print("連接狀態(tài): {}".format(connect_flag))
for index in range(0,10):
# 默認(rèn)返回有符號(hào)數(shù)
stack_address = dbg.peek_stack(index)
# 反匯編一行
dasm = dbg.get_disasm_one_code(stack_address)
# 根據(jù)地址得到模塊基址
if stack_address <= 0:
mod_base = 0
else:
mod_base = dbg.get_base_from_address(long_to_ulong(stack_address))
print("stack => [{}] addr = {:10} base = {:10} dasm = {}".format(index, hex(long_to_ulong(stack_address)),hex(mod_base), dasm))
dbg.close()
運(yùn)行上代碼,將自動(dòng)掃描前十行堆棧中的反匯編指令,并輸出如下圖所示的功能;
如上圖我們可以得到堆棧處的反匯編參數(shù),但如果我們需要檢索堆棧特定區(qū)域內(nèi)是否存在返回到模塊的地址,該如何實(shí)現(xiàn)呢?
該功能的實(shí)現(xiàn)其實(shí)很簡(jiǎn)單,首先需要得到程序全局狀態(tài)下的所有加載模塊的基地址,然后得到當(dāng)前堆棧內(nèi)存地址內(nèi)的實(shí)際地址,并通過實(shí)際內(nèi)存地址得到模塊基址,對(duì)比全局表即可拿到當(dāng)前模塊是返回到了哪個(gè)模塊的。
if __name__ == "__main__":
dbg = MyDebug()
connect_flag = dbg.connect()
print("連接狀態(tài): {}".format(connect_flag))
# 得到程序加載過的所有模塊信息
module_list = dbg.get_all_module()
# 向下掃描堆棧
for index in range(0,10):
# 默認(rèn)返回有符號(hào)數(shù)
stack_address = dbg.peek_stack(index)
# 反匯編一行
dasm = dbg.get_disasm_one_code(stack_address)
# 根據(jù)地址得到模塊基址
if stack_address <= 0:
mod_base = 0
else:
mod_base = dbg.get_base_from_address(long_to_ulong(stack_address))
# print("stack => [{}] addr = {:10} base = {:10} dasm = {}".format(index, hex(long_to_ulong(stack_address)),hex(mod_base), dasm))
if mod_base > 0:
for x in module_list:
if mod_base == x.get("base"):
print("stack => [{}] addr = {:10} base = {:10} dasm = {:15} return = {:10}"
.format(index,hex(long_to_ulong(stack_address)),hex(mod_base), dasm,
x.get("name")))
dbg.close()
運(yùn)行如上代碼片段,則會(huì)輸出如下圖所示的堆棧返回位置;文章來源:http://www.zghlxwxcb.cn/news/detail-540957.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-540957.html
到了這里,關(guān)于4.8 x64dbg 學(xué)會(huì)掃描應(yīng)用堆棧的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!