unicorn教程二

来看看第一道题目
在这里插入图片描述
下载过来后先使用file查看一下
在这里插入图片描述
是64位可执行文件
尝试运行,如下图所示
在这里插入图片描述
我们可以注意到,它会计算并打印我们的Flag,但这个过程非常缓慢,并且Flag的计算过程会随着字节的增多变得越来越慢。从其最开始的输出可以看到flag为hxp{F
这就意味着,我们需要对程序进行优化,以尽快得到Flag。
在IDA Pro的帮助下,我们可以将代码反编译成像C语言一样的伪代码。
使用IDA x64打开
在这里插入图片描述
先看main函数的汇编
在这里插入图片描述
F5查看伪代码
在这里插入图片描述
再看看关键的fibonacci的汇编
在这里插入图片描述
及其伪代码
在这里插入图片描述
解决这个问题的方式有很多种。例如,我们可以使用一种编程语言重新构建代码,并对新构建的代码进行优化。重建代码的过程并不容易,并且有可能会产生问题或错误,而解决问题、修正错误的这个过程是非常煎熬的。但假如我们使用Unicorn Engine,就可以跳过重建代码的过程,从而避免上面提到的问题

在优化之前,我们首先模拟正常的程序,一旦程序成功运行后,我们再在Unicorn Engine中对其进行优化。
新建一个python脚本,注意与fibonacci同样路径
在这里插入图片描述
导入unicorn,以及x86体系结构的unicorn constant
在这里插入图片描述
read函数用于返回整个文件的内容,u32需要一个4字节的字符串,并将其转换为一个整数,以低字节序表示这个数据。p32正相反,它需要一个数字,并将其转换为4字节的字符串,以低字节序表示。
接下来,初始化我们Unicorn的类,以适应x86-64架构:
在这里插入图片描述
我们需要使用下面的参数来调用函数Uc:
1、主结构分支,其中的Constant以UC_ARCH开始;
2、进一步的架构规范,其中的Constant以UC_MODE开始。
在这里插入图片描述
我们需要手动初始化虚拟内存。对于这个二进制文件,我们需要在其中的某个位置编写代码,并分配一个栈。
二进制的基址是0x400000。我们的栈将从地址0x000000开始,大小为1024*1024。也许我们并不需要那么大的空间,但创建大一些的空间也不会有任何不好的影响。
我们可以通过调用mem_map方法来映射内存。
在这里插入图片描述
在基址加载二进制文件,然后我们需要将RSP设置为指向栈的末尾。
在开始模拟并运行代码之前,我们首先需要知道开始地址在哪里,并且要知道模拟器应该在哪里停止。
我们可以开始模拟位于地址0x4004E0的代码,这是main的第一个地址。如下图所示
在这里插入图片描述
那么结束位置呢?可以选择0x400575这是putc(“n”)的位置
从伪代码中可以看出,会在打印完整个Flag后被调用
在这里插入图片描述在这里插入图片描述

所以加上这句代码开始模拟
在这里插入图片描述
运行后报错
在这里插入图片描述
在mu.emu_start之前,我们可以添加:
在这里插入图片描述
这段代码添加了一个钩子。我们定义了函数hook_code,在模拟每个指令前被调用。该函数需要以下参数:
1、Uc实例
2、指令的地址
3、指令的大小
4、用户数据(我们可以在hook_add()的可选参数中传递这个值)
运行后如下所示
在这里插入图片描述
从报错信息中得知,脚本在执行0x4004ef处指令时失败:
该处指令为
在这里插入图片描述
可以看到该指令从地址0x601038读取内存(可以在IDA Pro中看到)。这是.bss段,并不是由我们分配的。因此我们的解决方案是跳过所有有问题的指令。
在这里插入图片描述
其后的指令为
在这里插入图片描述
我们并不能调用任何glibc函数,因为此前并没有将glibc加载到虚拟内存中。事实上,我们并不需要调用这个函数,所以也可以跳过它。
下面是我们需要跳过的指令列表:
.text:0x4004EF mov rdi, cs:stdout ; stream
.text:0x4004F6 call _setbuf
.text:0x400502 call _printf
.text:0x40054F mov rsi, cs:stdout ; fp
我们可以通过将地址写入下一条指令的RIP寄存器来跳过指令:
在这里插入图片描述
hook_code现在应该是这样的:
在这里插入图片描述
我们还需要对逐字节打印Flag的指令进行一些操作。
在这里插入图片描述
__IO_putc需要一个字节,以打印出第一个参数(即寄存器RDI)。
我们可以从寄存器RDI中读取一个值并打印出来,同时跳过模拟这个指令。此时的hook_code函数如下所示:
在这里插入图片描述
完整代码在4.py,运行后没有报错
在这里插入图片描述
不过一个个字符打印出来的速度很慢
为什么这个程序的运行速度如此之慢?
sub_400670就是fibonacci函数
在这里插入图片描述
查看反编译的代码,我们可以看到main()多次调用了fibonacci()(外层是for循环,内层是永为真的while循环)
在这里插入图片描述
并且fibonacci()是一个递归函数
在这里插入图片描述
具体分析这个函数,我们看到它有两个参数,并返回两个值。第一个返回值通过RAX寄存器传递,而第二个返回值通过第二个参数传递。深入研究main()和fibonacci(),我们注意到其第二个参数只能取0或1的值。
为了优化这个函数,我们可以使用动态编程的方法来记录针对特定参数的返回值。由于第二个参数只可能是两个值,所以我们只需要记录2个MAX_OF_FIRST_ARGUMENT对。
当RIP指向fibonacci函数的开始时,我们可以获得函数的参数。在函数结束时,需要得知函数的返回值。既然目前我们不清楚返回值,所以需要使用一个栈,来帮助我们在函数结束时获得这两个返回值。在fibonacci的入口,我们需要将参数推入栈,并在最后弹出。为了记录其中的对(Pairs),我们可以使用字典。
如何检查对(Pairs)的值?
在函数的开始处,可以检查返回值是否被存储在字典中,以用于这些参数。如果已经被存储,我们可以返回该对。只需要将返回值写入到引用和RAX中即可。此外,我们还将RIP设置为一些RET指令的地址来退出函数。由于这一指令被Hook住了,所以我们不能在fibonacci函数中跳转到RET。如果该返回值不在字典中,我们将参数添加到栈中。在退出函数时,可以保存返回值。我们可以从栈结构中读取参数和引用指针。
描述这一部分逻辑的代码如下所示:
FIBONACCI_ENTRY = 0x0000000000400670
FIBONACCI_END = [0x00000000004006F1, 0x0000000000400709]

stack = [] # Stack for storing the arguments
d = {} # Dictionary that holds return values for given function arguments

def hook_code(mu, address, size, user_data):
#print(’>>> Tracing instruction at 0x%x, instruction size = 0x%x’ %(address, size))

if address in instructions_skip_list:
    mu.reg_write(UC_X86_REG_RIP, address+size)

elif address == 0x400560:                       # That instruction writes a byte of the flag
    c = mu.reg_read(UC_X86_REG_RDI)
    print(chr(c))
    mu.reg_write(UC_X86_REG_RIP, address+size)

elif address == FIBONACCI_ENTRY:                # Are we at the beginning of fibonacci function?
    arg0 = mu.reg_read(UC_X86_REG_RDI)          # Read the first argument. Tt is passed via RDI
    r_rsi = mu.reg_read(UC_X86_REG_RSI)         # Read the second argument which is a reference
    arg1 = u32(mu.mem_read(r_rsi, 4))           # Read the second argument from reference

    if (arg0,arg1) in d:                        # Check whether return values for this function are already saved.
        (ret_rax, ret_ref) = d[(arg0,arg1)]
        mu.reg_write(UC_X86_REG_RAX, ret_rax)   # Set return value in RAX register
        mu.mem_write(r_rsi, p32(ret_ref))       # Set retun value through reference
        mu.reg_write(UC_X86_REG_RIP, 0x400582)  # Set RIP to point at RET instruction. We want to return from fibonacci function

    else:
        stack.append((arg0,arg1,r_rsi))         # If return values are not saved for these arguments, add them to stack.

elif address in FIBONACCI_END:
    (arg0, arg1, r_rsi) = stack.pop()           # We know arguments when exiting the function

    ret_rax = mu.reg_read(UC_X86_REG_RAX)       # Read the return value that is stored in RAX
    ret_ref = u32(mu.mem_read(r_rsi,4))         # Read the return value that is passed reference
    d[(arg0, arg1)]=(ret_rax, ret_ref)          # Remember the return values for this argument pair

完整代码在5.py
运行后很快就得到了flag

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值