angr是一个很厉害的二进制分析工具,能够实现很多自动化的逆向工作。最近正在学习,在看文档的时候发现了有个angrCTF的实战可以练习angr。
angrCTF:http://angr.oregonctf.org/
angr文档:https://docs.angr.io/
这篇文章会是这个系列的第一篇文章。写这个系列也是因为我本身是逆向新手,记忆力还不行,估计过几天就忘记这个工具怎么用了。故在此记录。第一篇就只记录angrCTF的前三道题,感觉这三道题比较类似。
另外,还发现了一个大佬写的关于这方面的教程,贴在这里(因为我写的太垃圾了):
https://github.com/ZERO-A-ONE/AngrCTF_FITM
00_angr_find
题目告诉我们00_angr_find这个二进制文件是一个输入正确密码,然后打印Good Job.
否则输出Try Again.
的小程序。我们的目标就是去找到这个密码。只要将下面的python代码处的???
填好即可。
# Before you begin, here are a few notes about these capture-the-flag
# challenges.
#
# Each binary, when run, will ask for a password, which can be entered via stdin
# (typing it into the console.) Many of the levels will accept many different
# passwords. Your goal is to find a single password that works for each binary.
#
# If you enter an incorrect password, the program will print "Try again." If you
# enter a correct password, the program will print "Good Job."
#
# Each challenge will be accompanied by a file like this one, named
# "scaffoldXX.py". It will offer guidance as well as the skeleton of a possible
# solution. You will have to edit each file. In some cases, you will have to
# edit it significantly. While use of these files is recommended, you can write
# a solution without them, if you find that they are too restrictive.
#
# Places in the scaffoldXX.py that require a simple substitution will be marked
# with three question marks (???). Places that require more code will be marked
# with an ellipsis (...). Comments will document any new concepts, but will be
# omitted for concepts that have already been covered (you will need to use
# previous scaffoldXX.py files as a reference to solve the challenges.) If a
# comment documents a part of the code that needs to be changed, it will be
# marked with an exclamation point at the end, on a separate line (!).
import angr
import sys
def main(argv):
# Create an Angr project.
# If you want to be able to point to the binary from the command line, you can
# use argv[1] as the parameter. Then, you can run the script from the command
# line as follows:
# python ./scaffold00.py [binary]
# (!)
path_to_binary = argv[1] # :string
project = angr.Project(path_to_binary)
# Tell Angr where to start executing (should it start from the main()
# function or somewhere else?) For now, use the entry_state function
# to instruct Angr to start from the main() function.
initial_state = project.factory.entry_state()
# Create a simulation manager initialized with the starting state. It provides
# a number of useful tools to search and execute the binary.
simulation = project.factory.simgr(initial_state)
# Explore the binary to attempt to find the address that prints "Good Job."
# You will have to find the address you want to find and insert it here.
# This function will keep executing until it either finds a solution or it
# has explored every possible path through the executable.
# (!)
print_good_address = ??? # :integer (probably in hexadecimal)
simulation.explore(find=print_good_address)
# Check that we have found a solution. The simulation.explore() method will
# set simulation.found to a list of the states that it could find that reach
# the instruction we asked it to search for. Remember, in Python, if a list
# is empty, it will be evaluated as false, otherwise true.
if simulation.found:
# The explore method stops after it finds a single state that arrives at the
# target address.
solution_state = simulation.found[0]
# Print the string that Angr wrote to stdin to follow solution_state. This
# is our solution.
print (solution_state.posix.dumps(sys.stdin.fileno()))
else:
# If Angr could not find a path that reaches print_good_address, throw an
# error. Perhaps you mistyped the print_good_address?
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
下面这个angr程序就是去找到一个地址能够打印Good Job.
。然后angr就会一直执行,直到找到一个满足条件的输入或者遍历完所有可执行路径。
然后问题来了,如何才能找到打印Good Job.
的地址呢?一开始卡了半天,后面我试着用ida打开00_angr_find
文件,生成了二进制程序的控制流图。
然后试着将有Good Job.
这个基本块的地址放到问号里,结果输出一个字符串,再将这个字符串在angrCTF网站输进去,显示成功。真的是非常的amazing!
01_angr_avoid
万事开头难,搞定了第一个后,第二个就很简单了。python代码如下。主要是由于二进制非常大,我们需要设置一些angr避免执行的地址,加快符号执行的速度。
import angr
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state()
simulation = project.factory.simgr(initial_state)
# Explore the binary, but this time, instead of only looking for a state that
# reaches the print_good_address, also find a state that does not reach
# will_not_succeed_address. The binary is pretty large, to save you some time,
# everything you will need to look at is near the beginning of the address
# space.
# (!)
print_good_address = ???
will_not_succeed_address = ???
simulation.explore(find=print_good_address, avoid=will_not_succeed_address)
if simulation.found:
solution_state = simulation.found[0]
print (solution_state.posix.dumps(sys.stdin.fileno()))
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
代码中的第一个问号是表示打印good job的地址,第二个问号填的是打印try again的地址。还是和刚才一样,用ida打开二进制文件,会发现这次二进制文件比较大,不好生成可视化的控制流图。不过不影响。我们可以直接手撕汇编代码。
点击ida左侧的窗口,定位到main函数,在main函数上方有这样一段代码。打印try again的地址是804860A,所以设置avoid的地址是804860A,另外因为在将good job放在栈后,就跳转到了804861A。所以盲猜打印good job的地址为804861A。
最后也非常的amazing,竟然又给我蒙对了。
02_angr_find_condition
能够搜索到达某个指令的状态虽然很有用,但是在某些情况瞎,我们可能无法知道某个特定指令的地址(比如我这个菜鸡就不会熟练使用ida)。这个例子里就是教我们如何找到一个打印Good Job.的状态。这里改动的代码在is_successful(state)
和should_abort(state)
这两个函数的返回语句处。主要思想就是当判断输出的值是否有Good Job. ,有的话就是成功了,否则如果是Try Again就需要终止。
这样就减少了打开IDA去分析指令地址的步骤,实现了自动化逆向的过程,很赞!
# It is very useful to be able to search for a state that reaches a certain
# instruction. However, in some cases, you may not know the address of the
# specific instruction you want to reach (or perhaps there is no single
# instruction goal.) In this challenge, you don't know which instruction
# grants you success. Instead, you just know that you want to find a state where
# the binary prints "Good Job."
#
# Angr is powerful in that it allows you to search for a states that meets an
# arbitrary condition that you specify in Python, using a predicate you define
# as a function that takes a state and returns True if you have found what you
# are looking for, and False otherwise.
import angr
import sys
def main(argv):
path_to_binary = argv[1]
project = angr.Project(path_to_binary)
initial_state = project.factory.entry_state()
simulation = project.factory.simgr(initial_state)
# Define a function that checks if you have found the state you are looking
# for.
def is_successful(state):
# Dump whatever has been printed out by the binary so far into a string.
stdout_output = state.posix.dumps(sys.stdout.fileno())
# Return whether 'Good Job.' has been printed yet.
# (!)
return state.solver.is_true("Good Job." in str(stdout_output)) # :boolean
# Same as above, but this time check if the state should abort. If you return
# False, Angr will continue to step the state. In this specific challenge, the
# only time at which you will know you should abort is when the program prints
# "Try again."
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return state.solver.is_true("Try again." in str(stdout_output)) # :boolean
# Tell Angr to explore the binary and find any state that is_successful identfies
# as a successful state by returning True.
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
print (solution_state.posix.dumps(sys.stdin.fileno()))
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
结果自然也让我非常amazing!solved!