# angr
![](https://i.imgur.com/c8SMfCs.png)
```bash=
root@HOME-X213212:~# tree angr_ctf/dist/
angr_ctf/dist/
├── 00_angr_find
├── 01_angr_avoid
├── 02_angr_find_condition
├── 03_angr_symbolic_registers
├── 04_angr_symbolic_stack
├── 05_angr_symbolic_memory
├── 06_angr_symbolic_dynamic_memory
├── 07_angr_symbolic_file
├── 08_angr_constraints
├── 09_angr_hooks
├── 10_angr_simprocedures
├── 11_angr_sim_scanf
├── 12_angr_veritesting
├── 13_angr_static_binary
├── 14_angr_shared_library
├── 15_angr_arbitrary_read
├── 16_angr_arbitrary_write
├── 17_angr_arbitrary_jump
├── lib14_angr_shared_library.so
├── scaffold00.py
├── scaffold01.py
├── scaffold02.py
├── scaffold03.py
├── scaffold04.py
├── scaffold05.py
├── scaffold06.py
├── scaffold07.py
├── scaffold08.py
├── scaffold09.py
├── scaffold10.py
├── scaffold11.py
├── scaffold12.py
├── scaffold13.py
├── scaffold14.py
├── scaffold15.py
├── scaffold16.py
└── scaffold17.py
0 directories, 37 files
```
# apt install checksec
```
apt install checksec
```
```bash=
(env) root@HOME-X213212:~/angr_ctf/14_angr_shared_library# checksec --file=./14_angr_shared_library
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 74) Symbols No 0 2 ./14_angr_shared_library
```
# 10_angr_simprocedures
09 和 10 算是 我們手動構造新的fucntion
![](https://i.imgur.com/xjfN29p.png)
09還是算抽象,10算是另外一種方式讓使用者撰寫腳本更容易一點
![](https://i.imgur.com/Nwu0K4d.png)
![](https://i.imgur.com/7cRXu71.png)
可以看到我們的流程圖的路徑變得這麼多,意謂著angr要嘗試去走訪這些路徑找到唯一解變得越來越困難
不過我們還是先f5
![](https://i.imgur.com/TRyX4ia.png)
![](https://i.imgur.com/m9MUKwK.png)
目標路徑在這裡
![](https://i.imgur.com/KM91k2W.png)
```python=
# This challenge is similar to the previous one. It operates under the same
# premise that you will have to replace the check_equals_ function. In this
# case, however, check_equals_ is called so many times that it wouldn't make
# sense to hook where each one was called. Instead, use a SimProcedure to write
# your own check_equals_ implementation and then hook the check_equals_ symbol
# to replace all calls to scanf with a call to your SimProcedure.
#
# You may be thinking:
# Why can't I just use hooks? The function is called many times, but if I hook
# the address of the function itself (rather than the addresses where it is
# called), I can replace its behavior everywhere. Furthermore, I can get the
# parameters by reading them off the stack (with memory.load(regs.esp + xx)),
# and return a value by simply setting eax! Since I know the length of the
# function in bytes, I can return from the hook just before the 'ret'
# instruction is called, which will allow the program to jump back to where it
# was before it called my hook.
# If you thought that, then congratulations! You have just invented the idea of
# SimProcedures! Instead of doing all of that by hand, you can let the already-
# implemented SimProcedures do the boring work for you so that you can focus on
# writing a replacement function in a Pythonic way.
# As a bonus, SimProcedures allow you to specify custom calling conventions, but
# unfortunately it is not covered in this CTF.
import angr
import claripy
import sys
def main(argv):
path_to_binary = "/home/angr/angr-dev/test/10_angr_simprocedures"
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state(
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
# Define a class that inherits angr.SimProcedure in order to take advantage
# of Angr's SimProcedures.
class ReplacementCheckEquals(angr.SimProcedure):
# A SimProcedure replaces a function in the binary with a simulated one
# written in Python. Other than it being written in Python, the function
# acts largely the same as any function written in C. Any parameter after
# 'self' will be treated as a parameter to the function you are replacing.
# The parameters will be bitvectors. Additionally, the Python can return in
# the ususal Pythonic way. Angr will treat this in the same way it would
# treat a native function in the binary returning. An example:
#
# int add_if_positive(int a, int b) {
# if (a >= 0 && b >= 0) return a + b;
# else return 0;
# }
#
# could be simulated with...
#
# class ReplacementAddIfPositive(angr.SimProcedure):
# def run(self, a, b):
# if a >= 0 and b >=0:
# return a + b
# else:
# return 0
#
# Finish the parameters to the check_equals_ function. Reminder:
# int check_equals_AABBCCDDEEFFGGHH(char* to_check, int length) { ...
# (!)
def run(self, to_check, length):
# We can almost copy and paste the solution from the previous challenge.
# Hint: Don't look up the address! It's passed as a parameter.
# (!)
user_input_buffer_address = to_check
user_input_buffer_length = length
# Note the use of self.state to find the state of the system in a
# SimProcedure.
user_input_string = self.state.memory.load(
user_input_buffer_address,
user_input_buffer_length
)
check_against_string = "ORSDDWXHZURJRBDH"
# Finally, instead of setting eax, we can use a Pythonic return statement
# to return the output of this function.
# Hint: Look at the previous solution.
return claripy.If(
user_input_string == check_against_string,
claripy.BVV(1, 32),
claripy.BVV(0, 32)
)
# Hook the check_equals symbol. Angr automatically looks up the address
# associated with the symbol. Alternatively, you can use 'hook' instead
# of 'hook_symbol' and specify the address of the function. To find the
# correct symbol, disassemble the binary.
# (!)
check_equals_symbol = "check_equals_ORSDDWXHZURJRBDH" # :string
project.hook_symbol(check_equals_symbol, ReplacementCheckEquals())
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
print(stdout_output)
return b'Good Job.' in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Try again.' in stdout_output # :boolean
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.posix.dumps(0)
print(solution)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
```
![](https://i.imgur.com/Xeo5IVS.png)
# 11_angr_sim_scanf
替換掉scanf 裡面的 argument 為 symbolic
```
# This time, the solution involves simply replacing scanf with our own version,
# since Angr does not support requesting multiple parameters with scanf.
import angr
import claripy
import sys
def main(argv):
path_to_binary = "/home/angr/angr-dev/test/11_angr_sim_scanf"
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state(
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
class ReplacementScanf(angr.SimProcedure):
# Finish the parameters to the scanf function. Hint: 'scanf("%u %u", ...)'.
# (!)
def run(self, format_string, scanf0_address, scanf1_address):
# Hint: scanf0_address is passed as a parameter, isn't it?
scanf0 = claripy.BVS('scanf0', 32)
scanf1 = claripy.BVS('scanf1', 32)
# ...
# The scanf function writes user input to the buffers to which the
# parameters point.
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
self.state.memory.store(scanf1_address, scanf1, endness=project.arch.memory_endness)
# ...
# Now, we want to 'set aside' references to our symbolic values in the
# globals plugin included by default with a state. You will need to
# store multiple bitvectors. You can either use a list, tuple, or multiple
# keys to reference the different bitvectors.
# (!)
self.state.globals['solution'] = (scanf0,scanf1)
scanf_symbol = "__isoc99_scanf"
project.hook_symbol(scanf_symbol, ReplacementScanf())
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
print(stdout_output)
return b'Good Job.' in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Try again.' in stdout_output # :boolean
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
# Grab whatever you set aside in the globals dict.
stored_solutions0 = solution_state.globals['solution']
# for x in stored_solutions0:
solution = solution_state.solver.eval(stored_solutions0[0])
solution2 = solution_state.solver.eval(stored_solutions0[1])
# solution =stored_solutions0
print('{} {}'.format(solution, solution2))
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
```
![](https://i.imgur.com/Dnxpo4C.png)
# 12_angr_sim_scanf
參考:https://blog.csdn.net/A951860555/article/details/119102789#12_angr_veritesting_540
因此這道題就是使用veritesting參數來緩解路徑爆炸,原理簡單引用如下:從高層面來說,有兩種符號執行,一種是動態符號執行(Dynamic Symbolic Execution,簡稱 DSE),另一種是靜態符號執行(Static Symbolic Execution,簡稱 SSE)。動態符號執行會去執行程序然後為每一條路徑生成一個表達式。而靜態符號執行將程序轉換為表達式,每個表達式都表示任意條路徑的屬性。基於路徑的 DSE 在生成表達式上引入了很多的開銷,然而生成的表達式很容易求解。而 SSE 雖然生成表達式容易,但是表達式難求解。 veritesting 就是在這二者中做權衡,使得能夠在引入低開銷的同時,生成較易求解的表達式。
```python=
# When you construct a simulation manager, you will want to enable Veritesting:
# project.factory.simgr(initial_state, veritesting=True)
# Hint: use one of the first few levels' solutions as a reference.
import angr
import claripy
import sys
def main(argv):
path_to_binary = "/home/angr/angr-dev/test/12_angr_veritesting"
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state(
add_options = {}
)
simulation = project.factory.simgr(initial_state,veritesting=True)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
print(stdout_output)
return b'Good Job.' in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Try again.' in stdout_output # :boolean
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.posix.dumps(sys.stdin.fileno())
print(solution)
print('{}'.format(solution))
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
```
![](https://i.imgur.com/3NMnp95.png)
# 13_angr_static_binary
https://www.anquanke.com/post/id/231460
![](https://i.imgur.com/UmrUdP0.png)
![](https://i.imgur.com/CgSdwxU.png)
![](https://i.imgur.com/wZjLjCb.png)
![](https://i.imgur.com/kp8RANk.png)
```python
project.hook(0x804ED40, angr.SIM_PROCEDURES['libc']['printf']())
project.hook(0x804ED80, angr.SIM_PROCEDURES['libc']['scanf']())
project.hook(0x804F350, angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x8048D10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())
```
```python=
# This challenge is the exact same as the first challenge, except that it was
# compiled as a static binary. Normally, Angr automatically replaces standard
# library functions with SimProcedures that work much more quickly.
#
# To solve the challenge, manually hook any standard library c functions that
# are used. Then, ensure that you begin the execution at the beginning of the
# main function. Do not use entry_state.
#
# Here are a few SimProcedures Angr has already written for you. They implement
# standard library functions. You will not need all of them:
# angr.SIM_PROCEDURES['libc']['malloc']
# angr.SIM_PROCEDURES['libc']['fopen']
# angr.SIM_PROCEDURES['libc']['fclose']
# angr.SIM_PROCEDURES['libc']['fwrite']
# angr.SIM_PROCEDURES['libc']['getchar']
# angr.SIM_PROCEDURES['libc']['strncmp']
# angr.SIM_PROCEDURES['libc']['strcmp']
# angr.SIM_PROCEDURES['libc']['scanf']
# angr.SIM_PROCEDURES['libc']['printf']
# angr.SIM_PROCEDURES['libc']['puts']
# angr.SIM_PROCEDURES['libc']['exit']
#
# As a reminder, you can hook functions with something similar to:
# project.hook(malloc_address, angr.SIM_PROCEDURES['libc']['malloc']())
#
# There are many more, see:
# https://github.com/angr/angr/tree/master/angr/procedures/libc
#
# Additionally, note that, when the binary is executed, the main function is not
# the first piece of code called. In the _start function, __libc_start_main is
# called to start your program. The initialization that occurs in this function
# can take a long time with Angr, so you should replace it with a SimProcedure.
# angr.SIM_PROCEDURES['glibc']['__libc_start_main']
# Note 'glibc' instead of 'libc'.
import angr
import claripy
import sys
def main(argv):
bin_path = "/home/angr/angr-dev/test/13_angr_static_binary"
project = angr.Project(bin_path)
initial_state = project.factory.entry_state()
simulation = project.factory.simgr(initial_state)
project.hook(0x804ED40, angr.SIM_PROCEDURES['libc']['printf']())
project.hook(0x804ED80, angr.SIM_PROCEDURES['libc']['scanf']())
project.hook(0x804F350, angr.SIM_PROCEDURES['libc']['puts']())
project.hook(0x8048D10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']())
def is_successful(state):
stdout_output = state.posix.dumps(1)
return b"Good Job." in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(1)
return b"Try again." in stdout_output
simulation.explore(find = is_successful, avoid = should_abort)
if simulation.found:
solution_state = simulation.found[0]
print(solution_state.posix.dumps(0))
else:
raise(Exception("Could not find the solution"))
if __name__ == "__main__":
main(sys.argv)
```
![](https://i.imgur.com/2peedDO.png)
# 14_angr_shared_library
大概分兩派 有兩篇文章
開啟PIE情況
對於開啟PIE的程序,程序的基址固定在0x400000處,提取使用時應該加上該值。
或者關閉時為
0x08048000 兩者都可以。
這部分可以假設是動態要操作的fucntion 是validate
但是他在
lib14_angr_shared_library.so
![](https://i.imgur.com/DTK33Mg.png)
程式主體
14_angr_shared_library
![](https://i.imgur.com/UbuM1Zx.png)
https://blog.csdn.net/RichardYSteven/article/details/4967359
![](https://i.imgur.com/P3bTKGe.png)
這邊只有找到 linux virtual memory address space
![](https://i.imgur.com/Xl8hrK6.png)
```
0x6d7
```
000006D7 進行偏移就會找到實際的 validate 在runtime的address
Script - ARGS
● Claripy frontends
# Create a 32-bit symbolic bitvector "x"
> claripy.BVS('x', 32)
# Create a 32-bit bitvectory with the value 0x12345678
> claripy.BVV(0x12345678, 32)
<BV32 BVV(0x12345678, 32)>
validate fucntion end
```
0x783
```
![](https://i.imgur.com/4tVx9lm.png)
![](https://i.imgur.com/5243yCj.png)
s1為symbloic buffer_pointer
a2直接填入8 claripy.BVV(8, 32)
![](https://i.imgur.com/K6KShOk.png)
```python=
# The shared library has the function validate, which takes a string and returns
# either true (1) or false (0). The binary calls this function. If it returns
# true, the program prints "Good Job." otherwise, it prints "Try again."
#
# Note: When you run this script, make sure you run it on
# lib14_angr_shared_library.so, not the executable. This level is intended to
# teach how to analyse binary formats that are not typical executables.
import angr
import claripy
import sys
def main(argv):
path_to_binary = "/home/angr/angr-dev/test/lib14_angr_shared_library.so"
# The shared library is compiled with position-independent code. You will need
# to specify the base address. All addresses in the shared library will be
# base + offset, where offset is their address in the file.
# (!)
base = 0x08048000
project = angr.Project(path_to_binary, load_options={
'main_opts' : {
'base_addr' : base
}
})
# Initialize any symbolic values here; you will need at least one to pass to
# the validate function.
# (!)
buffer_pointer = claripy.BVV(0xC0000000, 32)
# Begin the state at the beginning of the validate function, as if it was
# called by the program. Determine the parameters needed to call validate and
# replace 'parameters...' with bitvectors holding the values you wish to pass.
# Recall that 'claripy.BVV(value, size_in_bits)' constructs a bitvector
# initialized to a single value.
# Remember to add the base value you specified at the beginning to the
# function address!
# Hint: int validate(char* buffer, int length) { ...
# (!)
validate_function_address = base + 0x6d7
initial_state = project.factory.call_state(
validate_function_address,
buffer_pointer,
claripy.BVV(8, 32)
)
# Inject a symbolic value for the password buffer into the program and
# instantiate the simulation. Another hint: the password is 8 bytes long.
# (!)
password = claripy.BVS( "password", 8*8 )
initial_state.memory.store( buffer_pointer , password)
simulation = project.factory.simgr(initial_state)
# We wish to reach the end of the validate function and constrain the
# return value of the function (stored in eax) to equal true (value of 1)
# just before the function returns. We could use a hook, but instead we
# can search for the address just before the function returns and then
# constrain eax
# (!)
check_constraint_address = base + 0x783
simulation.explore(find=check_constraint_address)
if simulation.found:
solution_state = simulation.found[0]
# Determine where the program places the return value, and constrain it so
# that it is true. Then, solve for the solution and print it.
# (!)
solution_state.add_constraints( solution_state.regs.eax != 0 )
solution = solution_state.solver.eval(password, cast_to=bytes)
print('solution is {0}'.format(solution))
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
```
![](https://i.imgur.com/bxEFNqi.png)
# 15_angr_arbitrary_read
緩衝區溢位攻擊
https://blog.csdn.net/weixin_46196863/article/details/114630563
http://godleon.blogspot.com/2008/02/indirect-addressing-indirect-address.html
選取 puts address
![](https://i.imgur.com/b9Z33H4.png)
good job address
![](https://i.imgur.com/pg0ZNka.png)
這邊要注意這個細節
# Warning: Endianness only applies to integers. If you store a string in
# memory and treat it as a little-endian integer, it will be backwards.
scanf0_address = key_address
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = input_address
self.state.memory.store(scanf1_address, scanf1)
self.state.globals['solution0'] = scanf0
self.state.globals['solution1'] = scanf1
```python=
# This binary takes both an integer and a string as a parameter. A certain
# integer input causes the program to reach a buffer overflow with which we can
# read a string from an arbitrary memory location. Our goal is to use Angr to
# search the program for this buffer overflow and then automatically generate
# an exploit to read the string "Good Job."
#
# What is the point of reading the string "Good Job."?
# This CTF attempts to replicate a simplified version of a possible vulnerability
# where a user can exploit the program to print a secret, such as a password or
# a private key. In order to keep consistency with the other challenges and to
# simplify the challenge, the goal of this program will be to print "Good Job."
# instead.
#
# The general strategy for crafting this script will be to:
# 1) Search for calls of the 'puts' function, which will eventually be exploited
# to print out "Good Job."
# 2) Determine if the first parameter of 'puts', a pointer to the string to be
# printed, can be controlled by the user to be set to the location of the
# "Good Job." string.
# 3) Solve for the input that prints "Good Job."
#
# Note: The script is structured to implement step #2 before #1.
# Some of the source code for this challenge:
#
# #include <stdio.h>
# #include <stdlib.h>
# #include <string.h>
# #include <stdint.h>
#
# // This will all be in .rodata
# char msg[] = "${ description }$";
# char* try_again = "Try again.";
# char* good_job = "Good Job.";
# uint32_t key;
#
# void print_msg() {
# printf("%s", msg);
# }
#
# uint32_t complex_function(uint32_t input) {
# ...
# }
#
# struct overflow_me {
# char buffer[16];
# char* to_print;
# };
#
# int main(int argc, char* argv[]) {
# struct overflow_me locals;
# locals.to_print = try_again;
#
# print_msg();
#
# printf("Enter the password: ");
# scanf("%u %20s", &key, locals.buffer);
#
# key = complex_function(key);
#
# switch (key) {
# case ?:
# puts(try_again);
# break;
#
# ...
#
# case ?:
# // Our goal is to trick this call to puts to print the "secret
# // password" (which happens, in our case, to be the string
# // "Good Job.")
# puts(locals.to_print);
# break;
#
# ...
# }
#
# return 0;
# }
import angr
import claripy
import sys
def main(argv):
path_to_binary = "/home/angr/angr-dev/test/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):
# Hint: scanf("%u %20s")
def run(self, format_string, key_address, input_address):
# %u
scanf0 = claripy.BVS('scanf0', 4*8)
# %20s
scanf1 = claripy.BVS('scanf1', 8*20)
# The bitvector.chop(bits=n) function splits the bitvector into a Python
# list containing the bitvector in segments of n bits each. In this case,
# we are splitting them into segments of 8 bits (one byte.)
for char in scanf1.chop(bits=8):
# Ensure that each character in the string is printable. An interesting
# experiment, once you have a working solution, would be to run the code
# without constraining the characters to the printable range of ASCII.
# Even though the solution will technically work without this, it's more
# difficult to enter in a solution that contains character you can't
# copy, paste, or type into your terminal or the web form that checks
# your solution.
# (!)
self.state.add_constraints(char >= 'A', char <= 'Z')
# Warning: Endianness only applies to integers. If you store a string in
# memory and treat it as a little-endian integer, it will be backwards.
scanf0_address = key_address
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = input_address
self.state.memory.store(scanf1_address, scanf1)
self.state.globals['solution0'] = scanf0
self.state.globals['solution1'] = scanf1
scanf_symbol = "__isoc99_scanf" # :string
project.hook_symbol(scanf_symbol, ReplacementScanf())
# 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):
# Recall that puts takes one parameter, a pointer to the string it will
# print. If we load that pointer from memory, we can analyse it to determine
# if it can be controlled by the user input in order to point it to the
# location of the "Good Job." string.
#
# Treat the implementation of this function as if puts was just called.
# The stack, registers, memory, etc should be set up as if the x86 call
# instruction was just invoked (but, of course, the function hasn't copied
# the buffers yet.)
# The stack will look as follows:
# ...
# esp + 7 -> /----------------\
# esp + 6 -> | puts |
# esp + 5 -> | parameter |
# esp + 4 -> \----------------/
# esp + 3 -> /----------------\
# esp + 2 -> | return |
# esp + 1 -> | address |
# esp -> \----------------/
#
# Hint: Look at level 08, 09, or 10 to review how to load a value from a
# memory address. Remember to use the correct endianness in the future when
# loading integers; it has been included for you here.
# (!)
puts_parameter = state.memory.load( state.regs.esp+4, 4, endness=project.arch.memory_endness)
# The following function takes a bitvector as a parameter and checks if it
# can take on more than one value. While this does not necessary tell us we
# have found an exploitable state, it is a strong indication that the
# bitvector we checked may be controllable by the user.
# Use it to determine if the pointer passed to puts is symbolic.
# (!)
if state.solver.symbolic(puts_parameter):
# Determine the location of the "Good Job." string. We want to print it
# out, and we will do so by attempting to constrain the puts parameter to
# equal it. Hint: use 'objdump -s <binary>' to look for the string's
# address in .rodata.
# (!)
good_job_string_address = 0x484F4A47 # :integer, probably hexadecimal
# Create an expression that will test if puts_parameter equals
# good_job_string_address. If we add this as a constraint to our solver,
# it will try and find an input to make this expression true. Take a look
# at level 08 to remind yourself of the syntax of this.
# (!)
is_vulnerable_expression = puts_parameter == good_job_string_address # :boolean bitvector expression
# Have Angr evaluate the state to determine if all the constraints can
# be met, including the one we specified above. If it can be satisfied,
# we have found our exploit!
#
# When doing this, however, we do not want to edit our state in case we
# have not yet found what we are looking for. To test if our expression
# is satisfiable without editing the original, we need to clone the state.
copied_state = state.copy()
# We can now play around with the copied state without changing the
# original. We need to add our vulnerable expression as a state to test it.
# Look at level 08 and compare this call to how it is called there.
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,
# instead of just querying whether the constraint _could_ be added.
state.add_constraints(is_vulnerable_expression)
return True
else:
return False
else: # not state.solver.symbolic(???)
return False
simulation = project.factory.simgr(initial_state)
# In order to determine if we have found a vulnerable call to 'puts', we need
# to run the function check_puts (defined above) whenever we reach a 'puts'
# call. To do this, we will look for the place where the instruction pointer,
# state.addr, is equal to the beginning of the puts function.
def is_successful(state):
# We are looking for puts. Check that the address is at the (very) beginning
# of the puts function. Warning: while, in theory, you could look for
# any address in puts, if you execute any instruction that adjusts the stack
# pointer, the stack diagram above will be incorrect. Therefore, it is
# recommended that you check for the very beginning of puts.
# (!)
puts_address = 0x08048370
print(state.addr)
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"]
flag = str(solution_state.solver.eval(scanf0)).encode("utf-8")
flag += b" " + solution_state.solver.eval(scanf1, cast_to=bytes)
print(flag)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
```
![](https://i.imgur.com/PDPL4zM.png)
https://medium.com/@ktecv2000/%E7%B7%A9%E8%A1%9D%E5%8D%80%E6%BA%A2%E4%BD%8D%E6%94%BB%E6%93%8A%E4%B9%8B%E4%B8%80-buffer-overflow-83516aa80240
![](https://i.imgur.com/3VurMXp.png)
既然可以任意讀取那麼我們換成別的address
![](https://i.imgur.com/6SYCC3N.png)
![](https://i.imgur.com/6fJovQS.png)
好像要預設是 const char 才行。
![](https://i.imgur.com/gqq8Wxg.png)
# 16_angr_arbitrary_write
任意寫入
可以看到有機會可以蓋掉 dest的value
![](https://i.imgur.com/zIs95xI.png)
```python=
# 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 = "/home/angr/angr-dev/test/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()
# Again, scanf needs to be replaced.
class ReplacementScanf(angr.SimProcedure):
# Hint: scanf("%u %20s")
def run(self, format_string, key_address, input_address):
# %u
scanf0 = claripy.BVS('scanf0', 4*8)
# %20s
scanf1 = claripy.BVS('scanf1', 8*20)
# The bitvector.chop(bits=n) function splits the bitvector into a Python
# list containing the bitvector in segments of n bits each. In this case,
# we are splitting them into segments of 8 bits (one byte.)
for char in scanf1.chop(bits=8):
# Ensure that each character in the string is printable. An interesting
# experiment, once you have a working solution, would be to run the code
# without constraining the characters to the printable range of ASCII.
# Even though the solution will technically work without this, it's more
# difficult to enter in a solution that contains character you can't
# copy, paste, or type into your terminal or the web form that checks
# your solution.
# (!)
self.state.add_constraints(char >= 48, char <= 96)
# Warning: Endianness only applies to integers. If you store a string in
# memory and treat it as a little-endian integer, it will be backwards.
scanf0_address = key_address
self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness)
scanf1_address = input_address
self.state.memory.store(scanf1_address, scanf1)
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_dest = state.memory.load(state.regs.esp+4,4,endness=project.arch.memory_endness)
strncpy_src = state.memory.load(state.regs.esp+8,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(strncpy_dest) and state.solver.symbolic(src_contents) :
# Use ltrace to determine the password. 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 = "NDYNWEUJ" # :string
buffer_address = 0x57584344 # :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. Note that we can pass multiple expressions to
# extra_constraints.
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.solver.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 =solution_state.globals["solution0"]
scanf1 =solution_state.globals["solution1"]
flag = str(solution_state.solver.eval(scanf0)).encode("utf-8")
flag += b" " + solution_state.solver.eval(scanf1, cast_to=bytes)
print(flag)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
```
![](https://i.imgur.com/FTl0ya5.png)
# 17_angr_arbitrary_jump
緩衝區溢出任意跳轉
我們要跳來這裡
![](https://i.imgur.com/72rnmzH.png)
input
![](https://i.imgur.com/ivE8dHh.png)
```pyhton
# An unconstrained state occurs when there are too many
# possible branches from a single instruction. This occurs, among other ways,
# when the instruction pointer (on x86, eip) is completely symbolic, meaning
# that user input can control the address of code the computer executes.
# For example, imagine the following pseudo assembly:
#
# mov user_input, eax
# jmp eax
#
# The value of what the user entered dictates the next instruction. This
# is an unconstrained state. It wouldn't usually make sense for the execution
# engine to continue. (Where should the program jump to if eax could be
# anything?) Normally, when Angr encounters an unconstrained state, it throws
# it out. In our case, we want to exploit the unconstrained state to jump to
# a location of our choosing. We will get to how to disable Angr's default
# behavior later.
#
# This challenge represents a classic stack-based buffer overflow attack to
# overwrite the return address and jump to a function that prints "Good Job."
# Our strategy for solving the challenge is as follows:
# 1. Initialize the simulation and ask Angr to record unconstrained states.
# 2. Step through the simulation until we have found a state where eip is
# symbolic.
# 3. Constrain eip to equal the address of the "print_good" function.
import angr
import claripy
import sys
def main(argv):
path_to_binary = "/home/angr/angr-dev/test/17_angr_arbitrary_jump"
project = angr.Project(path_to_binary)
# Make a symbolic input that has a decent size to trigger overflow
# (!)
class ReplacementScanf(angr.SimProcedure):
# Hint: scanf("%u %20s")
def run(self, format_string, input_address):
# %u
symbolic_input = claripy.BVS("input", 64 * 8)
# The bitvector.chop(bits=n) function splits the bitvector into a Python
# list containing the bitvector in segments of n bits each. In this case,
# we are splitting them into segments of 8 bits (one byte.)
for char in symbolic_input.chop(bits=8):
# Ensure that each character in the string is printable. An interesting
# experiment, once you have a working solution, would be to run the code
# without constraining the characters to the printable range of ASCII.
# Even though the solution will technically work without this, it's more
# difficult to enter in a solution that contains character you can't
# copy, paste, or type into your terminal or the web form that checks
# your solution.
# (!)
self.state.add_constraints(char >= 'A', char <= 'z')
# Warning: Endianness only applies to integers. If you store a string in
# memory and treat it as a little-endian integer, it will be backwards.
self.state.memory.store(input_address, symbolic_input)
self.state.globals['solution'] = symbolic_input
scanf_symbol = "__isoc99_scanf" # :string
project.hook_symbol(scanf_symbol, ReplacementScanf())
# Create initial state and set stdin to the symbolic input
initial_state = project.factory.entry_state(
add_options = {
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)
# The save_unconstrained=True parameter specifies to Angr to not throw out
# unconstrained states. Instead, it will move them to the list called
# 'simulation.unconstrained'. Additionally, we will be using a few stashes
# that are not included by default, such as 'found' and 'not_needed'. You will
# see how these are used later.
# (!)
simulation = project.factory.simgr(
initial_state,
save_unconstrained=True,
stashes={
'active' : [initial_state],
'unconstrained' : [],
'found' : [],
'not_needed' : []
}
)
# Explore will not work for us, since the method specified with the 'find'
# parameter will not be called on an unconstrained state. Instead, we want to
# explore the binary ourselves. To get started, construct an exit condition
# to know when the simulation has found a solution. We will later move
# states from the unconstrained list to the simulation.found list.
# Create a boolean value that indicates a state has been found.
def has_found_solution():
return simulation.found
# An unconstrained state occurs when there are too many possible branches
# from a single instruction. This occurs, among other ways, when the
# instruction pointer (on x86, eip) is completely symbolic, meaning
# that user input can control the address of code the computer executes.
# For example, imagine the following pseudo assembly:
#
# mov user_input, eax
# jmp eax
#
# The value of what the user entered dictates the next instruction. This
# is an unconstrained state. It wouldn't usually make sense for the execution
# engine to continue. (Where should the program jump to if eax could be
# anything?) Normally, when Angr encounters an unconstrained state, it throws
# it out. In our case, we want to exploit the unconstrained state to jump to
# a location of our choosing. Check if there are still unconstrained states
# by examining the simulation.unconstrained list.
# (!)
def has_unconstrained_to_check():
return len(simulation.unconstrained) > 0
# The list simulation.active is a list of all states that can be explored
# further.
# (!)
def has_active():
return len(simulation.active) > 0
while (has_active() or has_unconstrained_to_check()) and (not has_found_solution()):
for unconstrained_state in simulation.unconstrained:
# Look for unconstrained states and move them to the 'found' stash.
# A 'stash' should be a string that corresponds to a list that stores
# all the states that the state group keeps. Values include:
# 'active' = states that can be stepped
# 'deadended' = states that have exited the program
# 'errored' = states that encountered an error with Angr
# 'unconstrained' = states that are unconstrained
# 'found' = solutions
# anything else = whatever you want, perhaps you want a 'not_needed',
# you can call it whatever you want
# (!)
simulation.move('unconstrained', 'found')
# Advance the simulation.
simulation.step()
if simulation.found:
solution_state = simulation.found[0]
# Constrain the instruction pointer to target the print_good function and
# (!)
solution_state.add_constraints(solution_state.regs.eip == 0x42585249)
# Constrain the symbolic input to fall within printable range (capital
# letters) for the web UI. Ensure UTF-8 encoding.
# (!)
# for byte in symbolic_input.chop(bits=8):
# solution_state.add_constraints(
# byte >= ???,
# byte <= ???
# )
# Solve for the symbolic_input
# test =solution_state.state.globals['solution']
# print(test)
flag = solution_state.solver.eval(solution_state.globals["solution"], cast_to=bytes)
print(flag)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
```
![](https://i.imgur.com/gX31e84.png)
17題理解還是怪怪的有空再來製作例子,再用angr跑一次
# 參考
https://lantern.cool/note-tool-angr/#%E8%84%9A%E6%9C%AC-13
https://icode.best/i/46761544756831
https://www.anquanke.com/post/id/231460
https://hitcon.org/2016/CMT/slide/day1-r1-a-1.pdf
https://blog.ycdxsb.cn/f7e7b79c.html
https://www.anquanke.com/post/id/216504