03_angr_symbolic_registers
输入值在寄存器设置
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // ebx
int v4; // eax
int v5; // edx
int v6; // ST1C_4
int v7; // ST14_4
unsigned int v9; // [esp+8h] [ebp-10h]
int v10; // [esp+Ch] [ebp-Ch]
print_msg();
printf("Enter the password: ");
v4 = get_user_input();
v6 = v5;
v7 = complex_function_1(v4);
v9 = complex_function_2(v3);
v10 = complex_function_3(v6);
if ( v7 || v9 || v10 )
puts("Try again.");
else
puts("Good Job.");
return 0;
}
get_user_input();使用的是寄存器带出参数,如果我们使用老办法是没办法向v3\v4\v4赋值的。
我们要手动设置寄存器的值。
# Angr doesn't currently support reading multiple things with scanf (Ex:
# scanf("%u %u).) You will have to tell the simulation engine to begin the
# program after scanf is called and manually inject the symbols into registers.
import angr
import claripy
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
# Sometimes, you want to specify where the program should start. The variable
# start_address will specify where the symbolic execution engine should begin.
# Note that we are using blank_state, not entry_state.
# (!)
start_address = ??? # :integer (probably hexadecimal)入口地址,就是模拟器在哪个地方启动,地方不是确定,但要在操作输入数据之前。
initial_state = project.factory.blank_state(addr=start_address)
# Create a symbolic bitvector (the datatype Angr uses to inject symbolic
# values into the binary.) The first parameter is just a name Angr uses
# to reference it.
# You will have to construct multiple bitvectors. Copy the two lines below
# and change the variable names. To figure out how many (and of what size)
# you need, dissassemble the binary and determine the format parameter passed
# to scanf.
# (!)
password0_size_in_bits = ??? # :integer要输入数据的长度,单位为字节
password0 = claripy.BVS('password0', password0_size_in_bits)
...
# Set a register to a symbolic value. This is one way to inject symbols into
# the program.
# initial_state.regs stores a number of convenient attributes that reference
# registers by name. For example, to set eax to password0, use:
#
# initial_state.regs.eax = password0
#
# You will have to set multiple registers to distinct bitvectors. Copy and
# paste the line below and change the register. To determine which registers
# to inject which symbol, dissassemble the binary and look at the instructions
# immediately following the call to scanf.
# (!)
initial_state.regs.??? = password0#寄存器的值等于随机参数
...
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return ???
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return ???
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
# Solve for the symbolic values. If there are multiple solutions, we only
# care about one, so we can use eval, which returns any (but only one)
# solution. Pass eval the bitvector you want to solve for.
# (!)
solution0 = solution_state.se.eval(password0)#格式化输出值
...
# Aggregate and format the solutions you computed above, and then print
# the full string. Pay attention to the order of the integers, and the
# expected base (decimal, octal, hexadecimal, etc).
solution = ??? # :string
print solution
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
答案:
0x80488d1
password0_size_in_bits = 32 # :integer
password0=claripy.BVS('password0',password0_size_in_bits)
password1_size_in_bits = 32 # :integer
password1=claripy.BVS('password1',password1_size_in_bits)
password2_size_in_bits = 32 # :integer
password2=claripy.BVS('password2',password2_size_in_bits)
initial_state.regs.eax = password0
initial_state.regs.ebx = password1
initial_state.regs.edx = password2
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Good Job.' in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return 'Try again.' in stdout_output
说明:
为什么是0x80488d1,因为这里是将寄存器的值放入栈,然后传参。
sub esp,0Ch是手动平衡栈。
04_angr_symbolic_stack
输入值在栈中
你可能有个疑问,为什么不能用之前的方法,非要手动设置栈呢,因为这里需要输入多个值,angr不支持多个值的输入,上一题的开头提到了。
这个需要搞清楚栈结构,这里不再提及。
int handle_user()
{
int result; // eax
int v1; // [esp+8h] [ebp-10h]
int v2; // [esp+Ch] [ebp-Ch]
__isoc99_scanf("%u %u", &v2, &v1);
v2 = complex_function0(v2);
v1 = complex_function1(v1);
if ( v2 == 1726148847 && v1 == -1738470817 )
result = puts("Good Job.");
else
result = puts("Try again.");
return result;
}
答案放入了
# This challenge will be more challenging than the previous challenges that you
# have encountered thus far. Since the goal of this CTF is to teach symbolic
# execution and not how to construct stack frames, these comments will work you
# through understanding what is on the stack.
# ! ! !
# IMPORTANT: Any addresses in this script aren't necessarily right! Dissassemble
# the binary yourself to determine the correct addresses!
# ! ! !
import angr
import claripy
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
# For this challenge, we want to begin after the call to scanf. Note that this
# is in the middle of a function.
#
# This challenge requires dealing with the stack, so you have to pay extra
# careful attention to where you start, otherwise you will enter a condition
# where the stack is set up incorrectly. In order to determine where after
# scanf to start, we need to look at the dissassembly of the call and the
# instruction immediately following it:
# sub $0x4,%esp
# lea -0x10(%ebp),%eax
# push %eax
# lea -0xc(%ebp),%eax
# push %eax
# push $0x80489c3
# call 8048370 <__isoc99_scanf@plt>
# add $0x10,%esp
# Now, the question is: do we start on the instruction immediately following
# scanf (add $0x10,%esp), or the instruction following that (not shown)?
# Consider what the 'add $0x10,%esp' is doing. Hint: it has to do with the
# scanf parameters that are pushed to the stack before calling the function.
# Given that we are not calling scanf in our Angr simulation, where should we
# start?
# (!)
#说了这么多,简单的说就是要我们找到从哪里开始。
#和上一题一样,从sub esp,10h后开始。因为这一句是手动平栈的过程。
start_address = 0x008048697
initial_state = project.factory.blank_state(addr=start_address)
# We are jumping into the middle of a function! Therefore, we need to account
# for how the function constructs the stack. The second instruction of the
# function is:
# mov %esp,%ebp
# At which point it allocates the part of the stack frame we plan to target:
# sub $0x18,%esp
# Note the value of esp relative to ebp. The space between them is (usually)
# the stack space. Since esp was decreased by 0x18
#
# /-------- The stack --------\
# ebp -> | |
# |---------------------------|
# | |
# |---------------------------|
# . . . (total of 0x18 bytes)
# . . . Somewhere in here is
# . . . the data that stores
# . . . the result of scanf.
# esp -> | |
# \---------------------------/
#
# Since we are starting after scanf, we are skipping this stack construction
# step. To make up for this, we need to construct the stack ourselves. Let us
# start by initializing ebp in the exact same way the program does.
#进入一个函数的时候都要xor ebp,esp,就是检验栈平衡。
#因为栈在没有执行分配时,是平衡状态(esp == ebp)
#虽然从我们这个地方启动模拟器确实是平衡状态,保险起见,还是要手动初始化一下。
initial_state.regs.ebp = initial_state.regs.esp
# scanf("%u %u") needs to be replaced by injecting two bitvectors. The
# reason for this is that Angr does not (currently) automatically inject
# symbols if scanf has more than one input parameter. This means Angr can
# handle 'scanf("%u")', but not 'scanf("%u %u")'.
# You can either copy and paste the line below or use a Python list.
# (!)
password0 = claripy.BVS('password0', 32)
password1 = claripy.BVS('password1', 32)
# Here is the hard part. We need to figure out what the stack looks like, at
# least well enough to inject our symbols where we want them. In order to do
# that, let's figure out what the parameters of scanf are:
# sub $0x4,%esp
# lea -0x10(%ebp),%eax
# push %eax
# lea -0xc(%ebp),%eax
# push %eax
# push $0x80489c3
# call 8048370 <__isoc99_scanf@plt>
# add $0x10,%esp
# As you can see, the call to scanf looks like this:
# scanf( 0x80489c3, ebp - 0xc, ebp - 0x10 )
# format_string password0 password1
# From this, we can construct our new, more accurate stack diagram:
#
# /-------- The stack --------\
# ebp -> | padding |
# |---------------------------|
# ebp - 0x01 | more padding |
# |---------------------------|
# ebp - 0x02 | even more padding |
# |---------------------------|
# . . . <- How much padding? Hint: how
# |---------------------------| many bytes is password0?
# ebp - 0x0b | password0, second byte |
# |---------------------------|
# ebp - 0x0c | password0, first byte |
# |---------------------------|
# ebp - 0x0d | password1, last byte |
# |---------------------------|
# . . .
# |---------------------------|
# ebp - 0x10 | password1, first byte |
# |---------------------------|
# . . .
# |---------------------------|
# esp -> | |
# \---------------------------/
#
# Figure out how much space there is and allocate the necessary padding to
# the stack by decrementing esp before you push the password bitvectors.
#偏移量
padding_length_in_bytes = 8 # :integer
initial_state.regs.esp -= padding_length_in_bytes
# Push the variables to the stack. Make sure to push them in the right order!
# The syntax for the following function is:
#
# initial_state.stack_push(bitvector)
#
# This will push the bitvector on the stack, and increment esp the correct
# amount. You will need to push multiple bitvectors on the stack.
# (!)压栈,压入参数
initial_state.stack_push(pawword0, password1) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
...
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return "Good Job" in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return "Try again" in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
solution0 = solution_state.se.eval(password0)
solution1 = solution_state.se.eval(password1)
solution = "".join(map("{:x}",[password0, password1]))
print solution
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
如果你使用的是python3可能会报错:
使用python2,因为python2和python3字符流机制不一样。
还没有解决,暂时使用python2吧。
05_angr_symbolic_memory
输入值在堆区。
import angr
import claripy
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
start_address = 0x080485F9
initial_state = project.factory.blank_state(addr=start_address)
# The binary is calling scanf("%8s %8s %8s %8s").
# (!)
password0 = claripy.BVS('password0', 8 * 8)
password1 = claripy.BVS('password1', 8 * 8)
password2 = claripy.BVS('password2', 8 * 8)
password3 = claripy.BVS('password3', 8 * 8)#注意这是bit单位
# Determine the address of the global variable to which scanf writes the user
# input. The function 'initial_state.memory.store(address, value)' will write
# 'value' (a bitvector) to 'address' (a memory location, as an integer.) The
# 'address' parameter can also be a bitvector (and can be symbolic!).
# (!)将堆区第地址和输入相连
password0_address = 0x0A1BA1C0
initial_state.memory.store(password0_address, password0)
password1_address = 0x0A1BA1C8
initial_state.memoty.store(password1_address, password1)
password2_address = 0x0A1BA1D0
initial_state.memory.store(password2_address, password2)
password3_address = 0x0A1BA1D8
initial_state.memory.store(password3_address, password3)
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return "Good Job" in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return "Try again" in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
# Solve for the symbolic values. We are trying to solve for a string.
# Therefore, we will use eval, with named parameter cast_to=str
# which returns a string instead of an integer.
# (!)求解符号值。我们正在尝试求解字符串。因此,我们将使用eval,命名参数cast_to=str返回字符串而不是整数。
solution0 = solution_state.se.eval(password0,cast_to=str)
solution1 = solution_state.se.eval(password1,cast_to = str)
solution = "".join(map("{:x}", [solution0, solution1]))
print solution
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
答案附在文中了。
总结,这一节稍微蓝一点点,主要使用了angr向不同存储区或存储器的赋值。