0x01探索模板
import angr
import claripy
import sys
def main(argv):
path_to_binary = "15_angr_arbitrary_read"
project = angr.Project(path_to_binary)
# You can either use a blank state or an entry state; just make sure to start
# at the beginning of the program.
# (!)
initial_state = project.factory.entry_state()
# Again, scanf needs to be replaced.
class ReplacementScanf(angr.SimProcedure): #鉤取scanf函數重新定義,獲取到它的參數
#定義的輸入第二個輸入的數據爲可見字符
# Hint: scanf("%u %20s")
def run(self, format_string, checksec_key_address,input_address):
scanf0 = claripy.BVS('scanf0', 4*8)
scanf1 = claripy.BVS('scanf1', 20*8)
for char in scanf1.chop(bits=8): #把整個數據分割每8位爲一組
self.state.add_constraints(char >= '0', char <= 'z')#添加限制,__isoc99_scanf進來的v4的所有字符爲可見字符
scanf0_address = checksec_key_address
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
self.state.memory.store(input_address, scanf1, endness=project.arch.memory_endness) #保存求解變量到內存中
self.state.globals['solution0'] = scanf0
self.state.globals['solution1'] = scanf1 #保存這兩個變量到state,結束的時候可以查看這裏對應的輸入
scanf_symbol = '__isoc99_scanf'# :string
project.hook_symbol(scanf_symbol, ReplacementScanf())#在這裏執行hook
# We will call this whenever puts is called. The goal of this function is to
# determine if the pointer passed to puts is controllable by the user, such
# that we can rewrite it to point to the string "Good Job."
def check_puts(state): #執行到這裏是剛執行完puts函數 獲取現在puts函數的參數1.檢查符號化 2.複製一份執行流添加限制判斷在這個限制之下是否有解(添加的限制不能取消)
puts_parameter = state.memory.load(state.regs.esp+4,4, endness=project.arch.memory_endness) #情景是剛剛調用了puts,它的參數在esp+4位置處
if state.se.symbolic(puts_parameter):#檢查這個參數是否爲符號化對象
good_job_string_address = 0x4D52584B # :integer, probably hexadecimal Good Job.的地址
is_vulnerable_expression = puts_parameter==good_job_string_address # :boolean bitvector expression
#運行到這裏所有輸入得到的狀態都已經有了,現在要做的是從這些輸入中判斷是否有一種輸入滿足輸出的內容爲Good job.
copied_state = state.copy() #複製執行狀態的上下文
copied_state.add_constraints(is_vulnerable_expression) #複製的新進程添加限制爲 puts參數爲Good job.的地址
if copied_state.satisfiable():# 判斷添加了上面這個約束是否有解
state.add_constraints(is_vulnerable_expression) # 如果有解的話就保存到我們執行的那個狀態對象
return True
#複製一個執行狀態對象用來探路,直到探到正確的路才讓真正的執行流走到這裏
else:
return False
else: # not state.se.symbolic(???)
return False
simulation = project.factory.simgr(initial_state)
def is_successful(state): #到這裏剛剛執行完puts函數 相當於下斷點
puts_address = 0x8048370 #puts@plt的地址(已經執行過了call)
if state.addr == puts_address: #程序執行到這裏(也就是路徑探測到這裏)
# Return True if we determine this call to puts is exploitable.
return check_puts(state)
else:
# We have not yet found a call to puts; we should continue!
return False
simulation.explore(find=is_successful)
if simulation.found: #如果找到說明滿足條件1.能夠走到puts函數 2.走到puts函數的參數爲格式化對象 3.判斷輸出的內容是否爲Good job.
solution_state = simulation.found[0]
solution0 = solution_state.se.eval(solution_state.globals['solution0'],cast_to=bytes)
solution1 = solution_state.se.eval(solution_state.globals['solution1'], cast_to=bytes) # 輸出字符串序列化的內容
print(solution0, solution1)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
0x02獲取輸入(能運行到這裏的輸入)(解析結果)
獲取滿足條件的格式化輸入
solution_state.posix.dumps(sys.stdin.fileno())
獲取設置變量的值
與password1=claripy.BVS('password1',password_size_in_bits)
結合使用獲取滿足條件的設置的變量的值
solution0=solution_state.se.eval(password0)
在hook函數中的變量獲取能走到某處的輸入
self.state.globals['solution0'] = scanf0 #在hook函數裏面聲明這是個最終要解析的變量
stored_solutions0 = solution_state.globals['solution0']
solution0 = solution_state.se.eval(stored_solutions0) #最終的解析
解析結果字符串序列化
輸出結果爲字符串
solution0 = solution_state.se.eval(solution_state.globals['solution0'],cast_to=bytes)
添加限制條件
constrained_parameter_desired_value = "MRXJKZYRKMKENFZB" # :string 約束這些數據得到符合要求的
constraint_expression = constrained_parameter_bitvector == constrained_parameter_desired_value
solution_state.add_constraints(constraint_expression)#添加約束條件
)
0x03獲取輸出(運行到這裏的時候的狀態)
獲取運行到某處程序的格式化輸出
stdout_output=state.posix.dumps(sys.stdout.fileno())
獲取運行到某處的某內存地址的內容
constrained_parameter_address = 0x0804A050 #獲取對應內存區域在運行到這裏的時候的值
constrained_parameter_size_bytes = 0x10
constrained_parameter_bitvector = solution_state.memory.load(
constrained_parameter_address,
constrained_parameter_size_bytes
)
獲取運行到某處寄存器的值
用於判斷運行結果是否符合條件,或者添加限制條件
solution_state.regs.eax != 0
運行到某處獲取棧的數據
puts_parameter = state.memory.load(state.regs.esp+4,4, endness=project.arch.memory_endness) #情景是剛剛調用了puts,它的參數在esp+4位置處
獲取運行到哪個地址
個人理解應該是類似下斷點,不知道可不可以繼續往下運行程序
strncpy_address = 0x08048410
if state.addr == strncpy_address:
0x04探索
設置探索成功與失敗運行到的地址
這裏開啓了pie
print_good_address = 0x400000+0x000136D
will_not_succeed_address = 0x400000+0x000137E
simulation.explore(find=print_good_address, avoid=will_not_succeed_address)
設置成功和失敗函數
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Good Job.'.encode() in stdout_output# :boolean
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.'.encode() in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
複製一份狀態去探測看是否有滿足限制條件的值
#運行到這裏所有輸入得到的狀態都已經有了,現在要做的是從這些輸入中判斷是否有一種輸入滿足輸出的內容爲Good job.
copied_state = state.copy() #複製執行狀態的上下文
copied_state.add_constraints(is_vulnerable_expression) #複製的新進程添加限制爲 puts參數爲Good job.的地址
if copied_state.satisfiable():# 判斷添加了上面這個約束是否有解
state.add_constraints(is_vulnerable_expression) # 如果有解的話就保存到我們執行的那個狀態對象
return True
#複製一個執行狀態對象用來探路,直到探到正確的路才讓真正的執行流走到這裏
簡單的虛擬探測功能和上一個一樣
兩個限制條件爲and的關係,兩個同時成立
if state.satisfiable(extra_constraints=(does_src_hold_password, does_dest_equal_buffer_address)): #嘗試求解滿足兩個條件1.strncpy函數的目的地址爲
#if ( !strncmp(password_buffer, "KZYRKMKE", 8u) )中password_buffer的地址 2.strncpy中的前8位數據的內容爲KZYRKMKE
state.add_constraints(does_src_hold_password, does_dest_equal_buffer_address)# 嘗試求解成功添加真正的限制
return True
0x05設置初始化變量
設置加載開始地址
start_address=0x804890E
initial_state=project.factory.blank_state(addr=start_address) #設置加載地址 blank_state
設置變量
是一堆變量
password_size_in_bits=32
password0=claripy.BVS('password0',password_size_in_bits) #設置二維向量三個
設置寄存器
initial_state.regs.eax=password0
設置棧數據
ida中的代碼
int v1; // [esp+8h] [ebp-10h]
int v2; // [esp+Ch] [ebp-Ch]
__isoc99_scanf("%u %u", &v2, &v1);
initial_state.regs.ebp = initial_state.regs.esp
padding_length_in_bytes = 8 # :integer
initial_state.regs.esp -= padding_length_in_bytes #放入棧中
initial_state.stack_push(password0) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
initial_state.stack_push(password1)
設置bss段內容(已知內存地址)
password0_address = 0x09FD92A0 #把數據放到
initial_state.memory.store(password0_address, password0)
從scanf0_address加載數據存到scanf0_address 最後一個參數應該表示讀取多少
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
設置堆內容(運行地址未知)
fake_heap_address0 = 0x40000
pointer_to_malloc_memory_address0 =0x09FD92AC
initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness) #寫一個任意的堆地址,把這個地址寫入到變量
initial_state.memory.store(fake_heap_address0, password0) #設置堆具體內容
設置文件內容
password_file = angr.storage.SimFile(filename, content=password) #把內容放入文件
initial_state.fs.insert(filename, password_file)
0x06hook方法
hook一個地址
def skip_check_equals_(state):下面定義的代碼就是要把check_equals_called_address地址處的難書hook成什麼
check_equals_called_address = 0x80486B3 #這個地址是call的地址參數沒有改變,只改變call
instruction_to_skip_length = 0x05 #不同系統下call指令的大小不同
@project.hook(check_equals_called_address, length=instruction_to_skip_length)
def skip_check_equals_(state): #hook成這個函數
hook一組函數
根據函數名字hook函數,直接獲取被hook函數的參數
class ReplacementCheckEquals(angr.SimProcedure):
def run(self, to_check,check_data_length): #to_check,check_data_length代表着被hook的函數的兩個參數
......
project.hook_symbol(check_equals_symbol, ReplacementCheckEquals()) #開始hook
0x07特殊操作
判斷條件
claripy.If(條件,條件爲True時的返回值,條件爲False時的返回值) => 創建條件判斷
state.regs.eax = claripy.If(
user_input_string == check_against_string,
claripy.BVV(1, 32),
claripy.BVV(0, 32)
)
分割數據給claripy.BVS(‘scanf0’, 4*8)生成的二位向量添加限制
for char in scanf1.chop(bits=8): #把整個數據分割每8位爲一組
self.state.add_constraints(char >= '0', char <= 'z')#添加限制,__isoc99_scanf進來的v4的所有字符爲可見字符
判斷是否爲符號化對象(運行結束找出一個數據進行檢測要加的條件)
state.se.symbolic(puts_parameter):#檢查這個參數是否爲符號化對象
0x08解析特殊文件
輸入有很多(比如30個字節)
simulation=project.factory.simgr(initial_state,veritesting=True)
對自己寫的c庫函數
用angr自帶的hook函數hook爲庫函數
project.hook(0x804f350, angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x8048d10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())
用angr分析動態鏈接庫文件
加載動態鏈接庫,庫函數
base = 0x400000
project = angr.Project(path_to_binary, load_options={
'main_opts' : {
'custom_base_addr' : base
}
}) #加載動態鏈接庫
buffer_pointer=claripy.BVV(0x3000000,32)#創造一個32位的指針,值是任意的但這個值指向輸入數據
validate_function_address = base+0x000006D7
initial_state = project.factory.call_state(validate_function_address, buffer_pointer,claripy.BVV(8,32))#要調用的庫函數和他的參數
0x09注意點
用 state.memory.load(strncpy_src, strncpy_len)函數獲取到的值
1.是倒序存儲需要完全逆過來還原才能進行字符串比較
src_contents = state.memory.load(strncpy_src, strncpy_len)
password_string = "KZYRKMKE"# :string
does_src_hold_password = src_contents[-1:-64] == password_string #小端字節序存儲用這種方式反轉數據
2.需要檢查是否是符號化對象
if state.se.symbolic(strncpy_dest):
是符號化對象