ctf逆向工具angr的学习笔记(代码片段)

__lifanxin __lifanxin     2023-01-03     145

关键词:

概述

  angr
  简单介绍一下angr,借助官方的原话就是 – A powerful and user-friendly binary analysis platform!,一个功能强大且用户友好的二进制分析平台。这里主要总结一下使用angr解决ctf逆向题的一些用法和套路。
  安装的话,大家可以直接使用pip安装,不过这里可能会遇到一个小坑。我在安装angr之前已经安装了pwntools,此时再安装angr,由于有一个依赖库两者需要的版本是不一样的,所以安装后导致pwntools无法使用。解决的话,大家可以采用python虚拟环境将做题环境分开,推荐使用pipenv,有空的话再写一个笔记,不过这个操作比较简单,读者简单搜索即可解决。

如何学习

  angr官方文档
  个人在网上发现了一个名为 – angr_ctf的项目,非常适合新手入门学习。作者也是通过一道道基本题目来帮助读者入手angr,对于我们这种re手来说,直接去阅读文档肯定是效率极低的,所以通过做题的方式来学习我感觉是非常可以的。
  首先直接git clone下整个项目,然后如下截图所示,是其部分的项目内容截图,其中带有数字编号的是题目,一共有18道题,进入每道有编号的题目里,会有相应生成c程序的模板和脚本。dist目录的话存放的就是已经生成好了的二进制程序,solutions目录下存放了对应程序和解题脚本,所以我们直接在这个目录下学习就好。


  如下截图所示,进入solutions目录,然后再进入编号为00的题目目录里,可以看到一共有三个文件,第一个对应二进制程序,第二个名为scaffold00.py的脚本文件是需要我们完成的解题脚本,作者在里面给了充分的解释和代码来帮助我们学习并解决题目,第三个文件就是作者给出的完整解题脚本。


  下面直接开始学习,我会将自己的解题脚本贴出来,由于作者已经给了足够的注释,我就不详细介绍每个API的用途,个别需要思考的地方我会单独注释。

题目列表

00_angr_find

  如下截图所示,是该题目的主要逻辑,print_msg函数用于打印提示信息,然后要求我们输入密码,之后会用complex_function对密码进行变换,最终和程序中给出的字符串进行比较,相同即成功。


  下面我们用angr进行求解,脚本如下。

import angr


# Create a Pro --> Create a simu --> Explore
pro = angr.Project("./00_angr_find")

init_state = pro.factory.entry_state()  # start() addr
simu = pro.factory.simgr(init_state)

good_addr = 0x804867d
simu.explore(find=good_addr)

# get res
if simu.found:
    res = simu.found[0]
    for i in range(3):
        # print stdin/stdout/stderr, stdin is flag
        print(res.posix.dumps(i))
else:
    print("No result!")

  如下是程序的运行结果,上面一开始是一些警告信息,主要是由于angr的求解代码没有进一步设置更多的值,这表明我们的代码确实不是很完善,当然应对简单的题目足够,当后面学习更多的知识时再解决。最后三行输出依次是stdin/stdout/stderr的值,stdin代表输入的passwd即flag,stdout是程序运行的输出,程序没有运行出错,所以stderr没有输出。

01_angr_avoid

  如下截图所示,由于main函数中逻辑太长无法反编译,所以只能尝试分析汇编,可以发现整个逻辑和上题中是一样的,要求输入密码然后变换后和已知字符串进行比对。有区别的是最后判断加入了should_succeed,而avoid_me函数会将其值置为0从而无法达到good_addr,因此这里的angr求解脚本需要避免进入到avoid中。



  求解脚本如下,经过实际测试不加入avoid也是可以求解的,不过会使用更多的时间。

import angr


pro = angr.Project("./01_angr_avoid")

init_state = pro.factory.entry_state()
simu = pro.factory.simgr(init_state)

good_addr = 0x080485E0
avoid_addr = 0x080485A8   # avoid_me func
simu.explore(find=good_addr, avoid=avoid_addr)

if simu.found:
    res = simu.found[0]
    for i in range(3):
        print(res.posix.dumps(i))
else:
    print("No result!")
"""
b'RNGFXITY'
b'placeholder\\nEnter the password: '
b''
"""

02_angr_find_condition

  如下截图所示,是本题的主要逻辑,和前面的题目差不多,此处就不多分析。


  求解脚本如下,这道题的目的主要是学习另外一种条件判断的方法,前面的题目指定是地址,这里也可以指定为一个条件函数。

import angr


pro = angr.Project("./02_angr_find_condition")

init_state = pro.factory.entry_state()
simu = pro.factory.simgr(init_state)

def success(state):
    output = state.posix.dumps(1)   # get output from stdout
    return b"Good Job." in output

def abort(state):
    output = state.posix.dumps(1)   # get output from stdout
    return b"Try again." in output

simu.explore(find=success, avoid=abort)
if simu.found:
    res = simu.found[0]
    for i in range(3):
        print(res.posix.dumps(i))
else:
    print("No result!")
"""
b'UFOHHURD'
b'placeholder\\nEnter the password: Good Job.\\n'
b''
"""

03_angr_symbolic_registers

  如下截图所示,是本题的主要逻辑,同样也是先获取输入,然后使用了三个函数进行变换,最后进行判断。


  这道题最主要的区别在于scanf读取了三个参数,以前的angr版本不支持scanf获取多个参数,所以会采用设置寄存器求解的方法,如下脚本所示。不过现在安装最新版本的话是可以支持scanf获取多个参数的,所以仍然可以用上面的方法直接求解。

import angr
import claripy


pro = angr.Project("./03_angr_symbolic_registers")

start_addr = 0x080488D1
init_state = pro.factory.blank_state(addr=start_addr)  # blank_state, not entry_state

# create three vars
size_in_bits = 32
passwd0 = claripy.BVS("passwd0", size_in_bits)
passwd1 = claripy.BVS("passwd1", size_in_bits)
passwd2 = claripy.BVS("passwd2", size_in_bits)

# scanf --> eax/ebx/edx
init_state.regs.eax = passwd0
init_state.regs.ebx = passwd1
init_state.regs.edx = passwd2

simu = pro.factory.simgr(init_state)

def success(state):
    output = state.posix.dumps(1)
    return b"Good Job." in output

def abort(state):
    output = state.posix.dumps(1)
    return b"Try again." in output

simu.explore(find=success, avoid=abort)

if simu.found:
    res = simu.found[0]
    solu0 = res.solver.eval(passwd0)  # you can also use init_state.regs.eax replace passwd0
    solu1 = res.solver.eval(passwd1)
    solu2 = res.solver.eval(passwd2)

    solu = " ".join(map(":x".format, [solu0, solu1, solu2]))
    print(solu)
else:
    print("No result!")
"""
db01abf7 4930dc79 d17de5ce
"""

04_angr_symbolic_stack

  如下截图所示,是该程序的主逻辑,这道题和上面一道题类似,同样需要设置参数来求解。


  脚本如下,由于设置参数是在栈上,所以我们需要在初始状态时模拟程序本身的堆栈情况,即在正确的栈位置将参数传进入,主要看esp和ebp两个指针。

import angr
import claripy


pro =angr.Project("./04_angr_symbolic_stack")

start_addr = 0x08048697
init_state = pro.factory.blank_state(addr=start_addr)

# 初始时ebp无值,esp有值,将两个置于同一个值
init_state.regs.ebp = init_state.regs.esp

passwd0 = claripy.BVS("passwd0", 32)
passwd1 = claripy.BVS("passwd1", 32)

# 安装程序的运行逻辑构造正确的栈帧
init_state.regs.esp -= 8
init_state.stack_push(passwd0)
init_state.stack_push(passwd1)

# test esp
# 因为程序是用ebp索引局部变量,所以局部变量和ebp的位置一定要准确
# 而对于esp,只需要保证ebp索引局部变量时esp的值是小于该局部变量的位置即可
# init_state.regs.esp -= 0x100
# print(init_state.regs.esp, init_state.regs.ebp)

simu = pro.factory.simgr(init_state)

def success(state):
    output = state.posix.dumps(1)
    return b"Good Job." in output

def abort(state):
    output = state.posix.dumps(1)
    return b"Try again." in output

simu.explore(find=success, avoid=abort)

if simu.found:
    res = simu.found[0]
    solu0 = res.solver.eval(passwd0)
    solu1 = res.solver.eval(passwd1)
    
    solu = " ".join(map(str, [solu0, solu1]))
    print(solu)
else:
    print("No result!")
"""
1213922930 1153451551
"""

05_angr_symbolic_memory

  如下截图所示,是本题的主逻辑,同样也是scanf读取多个参数,其中有四个参数都在bss段中。


  求解脚本如下。

import angr
import claripy


pro = angr.Project("./05_angr_symbolic_memory")

start_addr = 0x08048603
init_state = pro.factory.blank_state(addr=start_addr)

# symbolics
syms = []
for i in range(4):
    syms.append(claripy.BVS(str(i), 64))

# scanf 参数位置
bss = [0x0A29FAA0, 0x0A29FAA8, 0x0A29FAB0, 0x0A29FAB8]
for i in range(4):
    init_state.memory.store(bss[i], syms[i])

good = 0x08048677
bad = 0x08048665
simu = pro.factory.simgr(init_state)
simu.explore(find=good, avoid=bad)

if simu.found:
    res = simu.found[0]
    flag = b""
    for i in range(4):
        flag += res.solver.eval(syms[i], cast_to=bytes) + b" "   # cast_to only bytes or int
    print(flag)
else:
    print("No result!")
"""
QLVJFWGI YPEKZGCV LCWKCULV STBDTTXE
"""

06_angr_symbolic_dynamic_memory

  如下截图所示,是本题的主要逻辑,scanf会读取两个参数,而两个参数的内存位置是由malloc动态分配的。


  求解脚本如下。

import angr
import claripy


pro = angr.Project("./06_angr_symbolic_dynamic_memory")

start_addr = 0x0804869B
init_state = pro.factory.blank_state(addr=start_addr)

syms = []
for i in range(2):
    syms.append(claripy.BVS(str(i), 64))

# 假的未使用的地址
fake_heap_addr = [0x4444444, 0x4444454]
# 参数指针地址
buffer_addr = [0x0A79A118, 0x0A79A120]
for i in range(2):
	# angr 默认为大端,这里要使用程序的arch
    init_state.memory.store(buffer_addr[i], fake_heap_addr[i], endness=pro.arch.memory_endness)
    init_state.memory.store(fake_heap_addr[i], syms[i])

good = 0x08048763
bad = 0x08048751
simu = pro.factory.simgr(init_state)
simu.explore(find=good, avoid=bad)

if simu.found:
    res = simu.found[0]
    flag = b""
    for i in range(2):
        flag += res.solver.eval(syms[i], cast_to=bytes) + b" "
    print(flag)
else:
    print("No result!")
"""
IDMRHRCZ PLBQSLBO
"""

07_angr_symbolic_file

  如下截图所示,是本题的主要逻辑,在该题目中使用了fread函数来从文件中读取输入,因此接下来将尝试使用angr的文件系统来解题。


  解题脚本如下。

import angr
import claripy


pro = angr.Project("./07_angr_symbolic_file")

start_addr = 0x080488D8
init_state = pro.factory.blank_state(addr=start_addr)

# 构造文件
filename = "WCEXPXBW.txt"
size = 64
file_con = claripy.BVS("flag", size * 8)
sym_file = angr.storage.SimFile(filename, content=file_con, size=size)
init_state.fs.insert(filename, sym_file)

good = 0x080489BA
bad = 0x080489A0
simu = pro.factory.simgr(init_state)
simu.explore(find=good, avoid=bad)

if simu.found:
    res = simu.found[0]
    flag = res.solver.eval(file_con, cast_to=bytes)
    print(flag)
else:
    print("No result!")
"""
GQLWJWWI\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00
"""

08_angr_constraints

  如下截图所示,是本题的主要逻辑,该题目的重点在于check函数,check会将经过complex_function变换的字符串和程序中固定字符串进行逐一比较。如果check过于复杂的话,而用于比较的字符串本身就存在,我们可以将find设置为check函数,同时增加约束为结果字符串,这样就可以节省运行check函数的时间。


  求解脚本如下。

import angr
import claripy


pro = angr.Project("./08_angr_constraints")

start_addr = 0x0804862A
init_state = pro.factory.blank_state(addr=start_addr)

flag = claripy.BVS("flag", 16*8)
buffer = 0x0804A050
init_state.memory.store(buffer, flag)

check = 0x08048671
simu = pro.factory.simgr(init_state)
simu.explore(find=check)

if simu.found:
    res = simu.found[0]
    constraints = res.memory.load(buffer, 16)
    r_value = "BWYRUBQCMVSBRGFU"
    res.add_constraints(constraints == r_value)
    flag = res.solver.eval(flag, cast_to=bytes)
    print(flag)
else:
    print("No result!")
"""
MILFJRHUFPNXOEEU
"""

09_angr_hooks

  如下截图所示,是本题目的主逻辑,该题目和上道题不同的点在于check函数有返回值,
因此这里将利用hook技术来解题。


  求解脚本如下。

import angr
import claripy


pro = angr.Project("./09_angr_hooks")

init_state = pro.factory.entry_state()

check_addr = 0x080486B8 # check func addr
check_instr_len = 0x5   # instr "call check_equals_XKSPZSJKJYQCQXZV" length 
@pro.hook(check_addr, length=check_instr_len)
def skip_check(state):
    buffer = 0x0804A054
    size = 16
    flag = state.memory.load(buffer, size)

    check_str = "XKSPZSJKJYQCQXZV"
    # 设置返回值
    state.regs.eax = claripy.If(
        flag == check_str,
        claripy.BVV(1, 32),   # BVV创建具体值,BVS创建符号变量
        claripy.BVV(0, 32)
    )

good = 0x08048772
bad = 0x08048760
simu = pro.factory.simgr(init_state)
simu.explore(find=good, avoid=bad)

if simu.found:
    res = simu.found[0]
    flag = res.posix.dumps(0)
    print(flag)
else:
    print("No result!")
"""
ZJOIPFTRNZOXIMLEWGLFMCQOKWLUFJIB
"""

10_angr_simprocedures

  如下截图所示,这道题初看和前面的题差不多,但实际上看汇编会发现多次调用了check函数,实际上反编译的代码应该是很长的,看来IDA是优化处理过了,只显示调用了一次check。


  对于调用了很多次的函数来说,单独hook就不起作用了,此时需要使用angr的SimProcedure模块,求解脚本如下。

import angr
import claripy


pro = angr.Project("./10_angr_simprocedures")

init_state = pro.factory.entry_state()

# 构造一个check函数
class ReplaceCheck(angr.SimProcedure):

    def run(self, to_check, length):
        buffer = to_check
        length = length
        flag = self.state.memory.load(
            buffer,
            length
        )

        check_str = "WQNDNKKWAWOLXBAC"
        return claripy.If(
            flag == check_str,
            claripy.BVV(1, 32),
            claripy.BVV(0, 32)
        )

# 使用符号hook源程序的check函数
check_sym = "check_equals_WQNDNKKWAWOLXBAC"
pro.hook_symbol(check_sym, ReplaceCheck())

good = 0x0804A98E
bad = 0x0804A97C
simu = pro.factory.simgr(init_state)
simu.explore(find=good, avoid=bad)

if simu.found:
    res_state = simu.found[0]
    flag = res_state.posix.dumps(0)
    print(flag)
else:
    print("No result!")
"""
URRKXXAPWVQQFMOT
"""

总结

不忘初心,砥砺前行!

《有趣的二进制:软件安全与逆向分析》读书笔记:使用工具探索更广阔的世界(代码片段)

...前言本篇继续阅读学习《有趣的二进制:软件安全与逆向分析》,本章是使用工具探索更广阔的世界,简单介绍了REMnux&ClamAV、ZeroWineTryouts和一些启发式技术,这里就简单记录了下几个工具一、 查看详情

《有趣的二进制:软件安全与逆向分析》读书笔记:使用工具探索更广阔的世界(代码片段)

...前言本篇继续阅读学习《有趣的二进制:软件安全与逆向分析》,本章是使用工具探索更广阔的世界,简单介绍了REMnux&ClamAV、ZeroWineTryouts和一些启发式技术,这里就简单记录了下几个工具一、 查看详情

angr01_angr_avoidavoid路径优化(代码片段)

文章目录1解题过程2IDA设置的问题01_angr_avoid是angr的第2个例子,下载位置:https://github.com/jakespringer/angr_ctf1解题过程(angr)dist$ipythonPython3.8.2(default,Apr272020,15:53:34)Type'copyright','credits 查看详情

angr01_angr_avoidavoid路径优化(代码片段)

文章目录1解题过程2IDA设置的问题01_angr_avoid是angr的第2个例子,下载位置:https://github.com/jakespringer/angr_ctf1解题过程(angr)dist$ipythonPython3.8.2(default,Apr272020,15:53:34)Type'copyright','credits 查看详情

ctf_web:反序列化学习笔记ctf经典考题由浅至深(代码片段)

0x00CTF中的反序列化题目这类题目中主要是利用反序列化各种魔术方法的绕过或调用,从而构造符合条件的序列化字符串,完成特定的功能,在这一点上对于整个代码段的执行流程要很清楚。我们先从最简单的开始看... 查看详情

angr00_angr_finddemo(代码片段)

文章目录1启动angr2使用angr解00_angr_find00_angr_find是angr的第一个例子,下载位置:https://github.com/jakespringer/angr_ctf1启动angr依次执行如下指令:exportWORKON_HOME=$HOME/Python-workhomesource/usr/share/vir 查看详情

angr00_angr_finddemo(代码片段)

文章目录1启动angr2使用angr解00_angr_find00_angr_find是angr的第一个例子,下载位置:https://github.com/jakespringer/angr_ctf1启动angr依次执行如下指令:exportWORKON_HOME=$HOME/Python-workhomesource/usr/share/vir 查看详情

学习笔记——mybatis逆向工程mbg;mybatis逆向工程mbg使用步骤(代码片段)

2023-01-12一、逆向工程1、逆向工程数据库中表影响程序中代码(表影响java对象)。MyBatisGenerator:简称MGB,是一个专门为MyBatis框架使用定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。2、正向工程应... 查看详情

《有趣的二进制:软件安全与逆向分析》读书笔记:通过逆向工程学习如何读懂二进制代码(代码片段)

...3、例子结语前言上一篇学完内网之后,打算学习一些逆向相关知识本篇开始阅读学习《有趣 查看详情

《有趣的二进制:软件安全与逆向分析》读书笔记:通过逆向工程学习如何读懂二进制代码(代码片段)

...3、例子结语前言上一篇学完内网之后,打算学习一些逆向相关知识本篇开始阅读学习《有趣 查看详情

什么是android逆向?如何学习安卓逆向?android逆向自学笔记入门到实战(代码片段)

前言“安卓逆向”不是一个新名词,它伴随着安卓开发而生,但是一直以来,仅限于技术圈内流行,对于非技术圈人士,往往把“APP破解”和“安卓逆向”划等号。这是写给非技术圈人士看的,在于普及安... 查看详情

ctf-misc(入门|笔记|工具)(代码片段)

...二种是协议分析第三种数据提取图片分析zstegBMPPNGforemost工具binwalksteghidestegslovetweakpngJPGF5-steganographystegdetect压缩包分析zip伪加密crc32zip包明文攻击其它条形码扫描pyc反编译数据转换到ASCII乱码strings命令动态图片分解关于PDF的数 查看详情

ctf-misc(入门|笔记|工具)(代码片段)

...二种是协议分析第三种数据提取图片分析zstegBMPPNGforemost工具binwalksteghidestegslovetweakpngJPGF5-steganographystegdetect压缩包分析zip伪加密crc32zip包明文攻击其它条形码扫描pyc反编译数据转换到ASCII乱码strings命令动态图片分解关于PDF的数 查看详情

ctf-web渗透(入门|笔记|工具)(代码片段)

php各种漏洞绕过传送门:https://cloud.tencent.com/developer/article/2127498php伪协议详细博客讲解:https://blog.csdn.net/cosmoslin/article/details/120695429http://hummer.vin/2022/05/10/PHP%E4%BC%AA%E5%8D%8F%E 查看详情

ctf权威竞赛指南笔记ctf

...模式混合模式竞赛内容reverse针对Windows、Linux平台的软件逆向破解pwn溢出类题型webweb漏洞,如XSS、文件包含、代码执行、上传漏洞、SQLcrypto针对加/解密技术的题目mobileandroid和ios平台的逆向分析,需要学习java、android开发misc杂项题... 查看详情

《逆向工程核心原理》学习笔记:高级逆向分析技术(代码片段)

目录前言一、TLS回调函数1、TLS简介(1)IMAGE_DATA_DIRECTORY(2)IMAGE_TLS_DIRECTORY2、TLS回调函数简介3、示例1:HelloTls.exe4、示例2:TlsTest.exe二、TEB1、TEB简介2、TEB访问方法三、PEB1、PEB简介(1ÿ 查看详情

angr02_angr_find_condition完整脚本(代码片段)

文章目录1解题过程02_angr_find_condition是angr的第3个例子,下载位置:https://github.com/jakespringer/angr_ctf1解题过程importangrimportsysdefmain(argv):bin_path=argv[1]p=angr.Project(bin_path)init_state&# 查看详情

angr学习

例子中脚本解释:0ctf_momo_3 脚本解释脚本思路由于程序对输入验证是单字符比较,可以用爆破的方式,由于mov混淆不具有变异特性,验证分支入口特点明显,有以下三个:edx,dwordptr[edx*4+0x81fe260]al,byteptr[0x81fe6e0]dl,byteptr[0x81fe6e4... 查看详情