0x15測試流程
設初始狀態: 設置hook函數,聲明要獲取輸入的變量
設置成功失敗狀態: 檢查運行到某一地址時內存數據的值,可以用copied_state = state.copy() 探測之後添加限制條件來檢查最終結果是否符合要求
解析成功數據:對聲明的變量解析得到輸入,解析得到格式化輸入,添加限制條件解析出來最終的值
#檢測程序漏洞,並且檢測這個漏洞能否被利用
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)
核心代碼
把數據分成8bit爲一組過濾
for char in scanf1.chop(bits=8): #把整個數據分割每8位爲一組
self.state.add_constraints(char >= '0', char <= 'z')#添加限制,__isoc99_scanf進來的v4的所有字符爲可見字符
在successful函數裏面下斷點
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)
if state.se.symbolic(puts_parameter):#檢查這個參數是否爲符號化對象
用來探測加上條件之後是否有解
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
加入cast_to=bytes選項輸出的結果是字符串
solution0 = solution_state.se.eval(solution_state.globals['solution0'],cast_to=bytes)
0x16漏洞測試2
# Essentially, the program does the following:
#
# scanf("%d %20s", &key, user_input);
# ...
# // if certain unknown conditions are true...
# strncpy(random_buffer, user_input);
# ...
# if (strncmp(secure_buffer, reference_string)) {
# // The secure_buffer does not equal the reference string.
# puts("Try again.");
# } else {
# // The two are equal.
# puts("Good Job.");
# }
#
# If this program has no bugs in it, it would _always_ print "Try again." since
# user_input copies into random_buffer, not secure_buffer.
#
# The question is: can we find a buffer overflow that will allow us to overwrite
# the random_buffer pointer to point to secure_buffer? (Spoiler: we can, but we
# will need to use Angr.)
#
# We want to identify a place in the binary, when strncpy is called, when we can:
# 1) Control the source contents (not the source pointer!)
# * This will allow us to write arbitrary data to the destination.
# 2) Control the destination pointer
# * This will allow us to write to an arbitrary location.
# If we can meet both of those requirements, we can write arbitrary data to an
# arbitrary location. Finally, we need to contrain the source contents to be
# equal to the reference_string and the destination pointer to be equal to the
# secure_buffer.
import angr
import claripy
import sys
def main(argv):
path_to_binary = "16_angr_arbitrary_write"
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()
class ReplacementScanf(angr.SimProcedure):
# Hint: scanf("%u %20s")
def run(self, format_string, check_key,input_buffer):
# %u
scanf0 = claripy.BVS('scanf0', 4*8)
# %20s
scanf1 = claripy.BVS('scanf1', 20*8)
for char in scanf1.chop(bits=8):
self.state.add_constraints(char >= "0", char <= "z")
scanf0_address = check_key
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = input_buffer
self.state.memory.store(scanf1_address, scanf1, endness=project.arch.memory_endness)
self.state.globals['solution0'] = scanf0
self.state.globals['solution1']= scanf1
scanf_symbol = '__isoc99_scanf' # :string
project.hook_symbol(scanf_symbol, ReplacementScanf())
# In this challenge, we want to check strncpy to determine if we can control
# both the source and the destination. It is common that we will be able to
# control at least one of the parameters, (such as when the program copies a
# string that it received via stdin).
def check_strncpy(state):
# The stack will look as follows:
# ... ________________
# esp + 15 -> / \
# esp + 14 -> | param2 |
# esp + 13 -> | len |
# esp + 12 -> \________________/
# esp + 11 -> / \
# esp + 10 -> | param1 |
# esp + 9 -> | src |
# esp + 8 -> \________________/
# esp + 7 -> / \
# esp + 6 -> | param0 |
# esp + 5 -> | dest |
# esp + 4 -> \________________/
# esp + 3 -> / \
# esp + 2 -> | return |
# esp + 1 -> | address |
# esp -> \________________/
# (!)
strncpy_src = state.memory.load(state.regs.esp+8,4,endness=project.arch.memory_endness)
strncpy_dest = state.memory.load(state.regs.esp+4,4,endness=project.arch.memory_endness)
strncpy_len = state.memory.load(state.regs.esp+12,4,endness=project.arch.memory_endness)
# We need to find out if src is symbolic, however, we care about the
# contents, rather than the pointer itself. Therefore, we have to load the
# the contents of src to determine if they are symbolic.
# Hint: How many bytes is strncpy copying?
# (!)
src_contents = state.memory.load(strncpy_src, strncpy_len)
# Our goal is to determine if we can write arbitrary data to an arbitrary
# location. This means determining if the source contents are symbolic
# (arbitrary data) and the destination pointer is symbolic (arbitrary
# destination).
# (!)
if state.se.symbolic(strncpy_dest) and state.se.symbolic(src_contents): #dest指針和src的內容爲符號化對象
# Use ltrace to determine the reference string. Decompile the binary to
# determine the address of the buffer it checks the password against. Our
# goal is to overwrite that buffer to store the password.
# (!)
password_string = "KZYRKMKE"# :string
buffer_address = 0x4D52584C # :integer, probably in hexadecimal
# Create an expression that tests if the first n bytes is length. Warning:
# while typical Python slices (array[start:end]) will work with bitvectors,
# they are indexed in an odd way. The ranges must start with a high value
# and end with a low value. Additionally, the bits are indexed from right
# to left. For example, let a bitvector, b, equal 'ABCDEFGH', (64 bits).
# The following will read bit 0-7 (total of 1 byte) from the right-most
# bit (the end of the string).
# b[7:0] == 'H'
# To access the beginning of the string, we need to access the last 16
# bits, or bits 48-63:
# b[63:48] == 'AB'
# In this specific case, since we don't necessarily know the length of the
# contents (unless you look at the binary), we can use the following:
# b[-1:-16] == 'AB', since, in Python, -1 is the end of the list, and -16
# is the 16th element from the end of the list. The actual numbers should
# correspond with the length of password_string.
# (!)
does_src_hold_password = src_contents[-1:-64] == password_string #小端字節序存儲用這種方式反轉數據
# Create an expression to check if the dest parameter can be set to
# buffer_address. If this is true, then we have found our exploit!
# (!)
does_dest_equal_buffer_address = buffer_address==strncpy_dest
# In the previous challenge, we copied the state, added constraints to the
# copied state, and then determined if the constraints of the new state
# were satisfiable. Since that pattern is so common, Angr implemented a
# parameter 'extra_constraints' for the satisfiable function that does the
# exact same thing:
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
#這樣執行完strncpy函數strncpy函數一定返回非零
state.add_constraints(does_src_hold_password, does_dest_equal_buffer_address)# 嘗試求解成功添加真正的限制
return True
else:
return False
else: # not state.se.symbolic(???)
return False
simulation = project.factory.simgr(initial_state)
def is_successful(state):
strncpy_address = 0x08048410
if state.addr == strncpy_address:
return check_strncpy(state)
else:
return False
simulation.explore(find=is_successful)
if simulation.found:
solution_state = simulation.found[0]
solution0=solution_state.se.eval(solution_state.globals['solution0'])
solution1=solution_state.se.eval(solution_state.globals["solution1"],cast_to=bytes)
print (hex(solution0),solution1)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
#strncmp(password_buffer, "KZYRKMKE", 8u) 作爲判斷函數沒有不可控的,只有從它的變量入手
#真正不可控的是strncpy函數,測試這個函數是否能改變strncpy函數的參數值,把它改爲需要的內容
核心代碼
添加限制嘗試求解
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
#strncmp(password_buffer, “KZYRKMKE”, 8u) 作爲判斷函數沒有不可控的,只有從它的變量入手
#真正不可控的是strncpy函數,測試這個函數是否能改變strncpy函數的參數值,把它改爲需要的內容