發現漏洞的第一步則是需要尋找到可利用的反組合指令片段,在某些時候遠端緩衝區溢位需要通過類似於jmp esp
等特定的反組合指令實現跳轉功能,並以此來執行佈置好的ShellCode
惡意程式碼片段,LyScript
外掛則可以很好的完成對當前程序記憶體中特定函數的檢索工作。
一般而言遠端緩衝區溢位攻擊通常利用的是一些具有緩衝區溢位漏洞的函數或是特定的組合指令片段,如:
在遠端緩衝區溢位攻擊中,攻擊者也可以利用組合指令jmp esp
來實現對攻擊程式碼的執行。該指令允許攻擊者跳轉到堆疊中的任意位置,並從那裡執行惡意程式碼。
在預設情況下,LyScript外掛並不具備搜尋連續指令的能力,雖然提供了get_disasm_code()
系列的反組合函數,但此類函數通常僅僅只能實現簡單的反組合功能,讀者如果需要實現其他附加功能,含需要自行動手去實現,首先我們自行實現一個簡單的組合指令檢索功能,用於尋找可利用的指令片段"pop esp","jmp esp","jmp eax","pop ecx"
等指令集。
這段程式碼實現的機制可總結為如下步驟;
connect
函數來連線到要偵錯的程式,並使用get_local_base
和get_local_size
函數獲取程式的記憶體範圍。search_asm
的列表,該列表包含要搜尋的組合指令。while
迴圈來遍歷記憶體範圍中的每一個地址,並呼叫get_disasm_one_code
函數獲取該地址處的反組合程式碼。search_asm
列表中的每一個指令,並檢查當前反組合程式碼是否與列表中的指令匹配。如果匹配,則輸出該地址和反組合程式碼。程式碼很容易被理解和實現,本質上僅僅只是提取所記憶體中所有的組合指令集,並依次列舉對比是否符合列表中的條件,其最終實現程式碼如下所示;
from LyScript32 import MyDebug
if __name__ == "__main__":
dbg = MyDebug()
dbg.connect()
local_base_start = dbg.get_local_base()
local_base_end = local_base_start + dbg.get_local_size()
print("開始地址: {} --> 結束地址: {}".format(hex(local_base_start),hex(local_base_end)))
search_asm = ["pop esp","jmp esp","jmp eax","pop ecx"]
while local_base_start <= local_base_end:
disasm = dbg.get_disasm_one_code(local_base_start)
# print("地址: 0x{:08x} --> 反組合: {}".format(local_base_start,disasm))
# 尋找指令
for index in range(0, len(search_asm)):
if disasm == search_asm[index]:
print("地址: {} --> 反組合: {}".format(hex(local_base_start), disasm))
# 遞增計數器
local_base_start = local_base_start + dbg.get_disasm_operand_size(local_base_start)
dbg.close()
如上程式碼被執行後,則會輸出當前程序內所有可被利用的指令片段,其輸出效果圖如下圖所示;
機器碼的搜尋與組合指令集的搜尋方式基本保持一致,但慶幸的是搜尋指令集可使用scan_memory_all()
這個官方函數,該函數可用於掃描當前EIP所處位置,也就是當前EIP所在模組的所有符合條件的機器碼,需要注意的是,在搜尋具有漏洞函數時,通常我們會搜尋程序內的完整模組,則此時應該先得到該模組的入口地址,並通過set_register()
設定到該模組所在記憶體,然後再次對該記憶體區域進行搜尋,程式碼中opcode
用於指定一段機器碼序列,此處讀者可指定搜尋多種機器碼,並將搜尋結果放入到該列表內進行儲存。
這段程式碼的實現原理可總結為如下所示的步驟;
opcode
的列表,該列表包含要搜尋的機器碼。get_all_module
函數獲取程式中的模組列表。對於每個模組,它將eip暫存器設定為該模組的入口點,然後呼叫scan_memory_all
函數搜尋該模組中是否存在要搜尋的機器碼。根據上述流程可總結為如下所示的程式碼片段;
from LyScript32 import MyDebug
import time
if __name__ == "__main__":
dbg = MyDebug()
dbg.connect()
# 需要搜尋的指令集片段
opcode = ['ff 25','ff 55 fc','8b fe']
# 迴圈搜尋指令集記憶體地址
for index,entry in zip(range(0,len(opcode)), dbg.get_all_module()):
eip = entry.get("entry")
base_name = entry.get("name")
if eip != 0:
dbg.set_register("eip",eip)
search_address = dbg.scan_memory_all(opcode[index])
if search_address != False:
print("搜尋模組: {} --> 匹配個數: {} --> 機器碼: {}"
.format(base_name,len(search_address),opcode[index]))
# 輸出地址
for search_index in search_address:
print("[*] {}".format(hex(search_index)))
time.sleep(0.3)
dbg.close()
以strcpy
函數為例,讀者只需要搜尋特徵['57 8b 7c 24 08 eb 6e','ff 55 fc','8b fe']
即可定位到當前模組內所有呼叫該函數機器其他函數的記憶體地址。
執行後即可輸出當前模組內所有被呼叫機器碼的詳細地址,輸出效果如下圖所示;