动调脚本化 — ida_python

从一个reverse-box到python脚本化调试

文章目录

  • 从一个reverse-box到python脚本化调试
  • 题目分析
    • 题目条件
    • 分析
    • 思路
    • ida-python爆破
    • 另外
  • 动调脚本化
    • ida - python

攻防世界 reverse-box 题目

原题来自 mma-ctf-2nd-2016: reverse-box

题目分析

题目条件

Description:

$ ./reverse_box ${FLAG}

95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a

题目flag格式:TWCTF{…}

分析

main函数如下:

运行程序时输入的参数,会和一个数值相加,转化成十六进制,然后打印出来,

我们简单运行下如下:

另外汇编是:

这个位置,是一个查表的过程,函数sub_804858D是一个建立表的过程,

重点在于:


后部分都是设置建立表,但是这个生成表的种子是随机的,也就是我们的表是不固定的。

思路

题目就是对输入进行查表,然后打印出来,并且给出了正确flag查表对应的值,但是这个表是随机生成的,我们需要得到正确的一个表。

方案应该是只能爆破,并且rand()函数调用的返回值,限制为int8即0xff大小,最大为0xff,爆破次数不算太大,直接爆破出来正确的表,就结束了。

这时候我们面对一个问题,这个怎么方便的进行爆破?

下面我们写一下关于爆破的方法,

其中wp有的直接爆破出正确的,有的直接爆破出所有的再交python得到最后工作,这都是差不多的, 我们只讨论爆破的实现。

下面是我搜到一些wp中的解法:

  • gdb 脚本–githubgdb脚本-csdn
  • [gdb - python 脚本](https://github.com/TeamContagion/CTF-Write-Ups/tree/master/TokyoWesterns-2016/Reverse Box))
  • idc脚本
  • uncorn 脚本
  • python 重构这个函数然后爆破
  • [ida-python脚本] (本文)

其中大部分方法都可以看到链接。一个ida python的手段是我们的重点, 也恰好是在wp没有看到的一种方案。

ida-python爆破

相关的文章是这个, 比较详细的介绍了相关的函数。

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import idaapi
import idc

for i in range(1, 256):

    RunTo(0x080485b1)
    GetDebuggerEvent(WFNE_SUSP, -1)
    print i
    SetRegValue(i, "eax")

    RunTo(0x08048704)
    GetDebuggerEvent(WFNE_SUSP, -1)
    bool = GetRegValue("eax")

    RunTo(0x08048746)
    GetDebuggerEvent(WFNE_SUSP, -1)
    SetRegValue(0x080486D4, 'eip')

    if bool  == 0x95:
        print i
        break

RunTo(0x080485b1)
GetDebuggerEvent(WFNE_SUSP, -1)
print i
SetRegValue(i, "eax")

RunTo(0x08048704)
GetDebuggerEvent(WFNE_SUSP, -1)
stack = GetRegValue("esp")

arr_addr = stack + 0x1c
arr = []

for i in range(0xff):
    arr.append(Byte(arr_addr + i))

s = '95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a'
a = []
for i in range(0,len(s), 2):
    a.append(arr.index(int(s[i:i+2], 16)))

print ''.join(map(chr, a))

注意idc定义的的函数可以直接在ida-python中调用。

注意一点:关于GetDebuggerEvent()

这个代码看起来很无用,但是不加会报错,

因为python脚本和调试器会异步执行,我们要获取寄存器的话应该要等待,直接过去是会报错,显示寄存器名错误,可以通过加入GetDebuggerEvent(WFNE_SUSP, -1)来解决这个问题,

注意每次要读取修改数据啥的要加一个这个就可以了。

设置好debug options, 直接运行脚本,ida会自动开始动调,

然后运行脚本得到flag,

ida 内运行直接得到flag

另外

注意两点,

  • 参数

一个是对于这种要求调用时要参数的程序,我们在ida debug options中设置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AZwpjfBD-1586228644870)(image-20200405222503984.png)]

这样调试的时候,对应的参数就是"T"

这个是linux 下wine运行的ida因此为127.0.0.1

  • ida-python 调试不能自己重复多次调试

我们的脚本的循环中每次都是改掉eip调整程序运行的。ida-python 不能够自动循环启动多次调试,但是对于一般题目修改eip已经足够我们的使用。

动调脚本化

在所有的wp解法中, 我认为最简洁和具有通用性的应该是ida-python的解法和gdb-python的解法,

这里简单写一下相关的使用。

gdb - python, 有一个官方文档, 然后个人觉得使用起来略显复杂,主要写下idapython

ida - python

然后在动调主要使用到的函数:

AddBpt(long Address): 在指定的地址设置断点

GetRegValue(string Register):获取一个寄存器的名称

SetRegValue(long Value, string Register)设置寄存器的值

RunTo(long Address): 运行到指定的地址,然后停下。

GetDebuggerEvnt(WFNE_SUSP, -1):应该是获取调试某一过程中信息,一般就在RunTo()后和操作寄存器前使用,解决脚本和调试异步而导致的寄存器读取报错的问题

一般使用的:

  • 用于程序运行, 获取相关位置,设定相关寄存器。
1
2
3
4
RunTo(xxxx)
GetDebuggerEvent(WFNE_SUSP, -1)
SetRegValue(xxx, 'xxx')
xxx = GetRegValue("xxx")
  • 获取栈内数据:
1
2
3
RunTo(xxx)
GetDebuggerEvent(WFNE_SUSP, -1)
stack = GetRegValue("esp")

这样,运行到某个位置,获取esp/rsp的值,为栈顶,通过偏移可以获取相对应的栈内地址,然后使用Byte()/ Dword()来获取相关数据,如果需要的话我们还可以通过PatchByte(long Addr, byte)来进行数据的修改,