Unicorn引擎教程
在该篇教程中,你将通过实际操作来学习Unicorn引擎。接下来将会有4
个练习,我会解决第一个。对于其他的我将提供提示和解决方案。(译注:原网页中的提示和解决方案内容通过按钮可以显示或者隐藏,翻译后的文章不具备此功能,所有内容将直接呈现出来)
FAST FAQ:
目录
- 任务1
- 一些笔记
- 任务2
- 任务3
- 任务4
- 备忘录
- 参考
任务1
该任务是hxp CTF 2017上的一个叫做Fibonacci
(译注:斐波那契)的例子。二进制文件可以从这里下载
当我们运行这个程序的时候,可以注意到这个程序计算和输出Flag非常的慢。Flag的下一个字节计算的越来越慢。
The Flag is:hxp {F
这就意味着有必要优化程序来获取Flag(在合理的时间内)。
在IDA Pro的帮助下,我们得到了像C语言一样的伪代码。虽然反编译代码不一定正确,但是仍然可以知道大致发生了一些事。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
void *v3; // rbp@1
int v4; // ebx@1
signed __int64 v5; // r8@2
char v6; // r9@3
__int64 v7; // r8@3
char v8; // cl@3
__int64 v9; // r9@5
int a2a; // [sp+Ch] [bp-1Ch]@3
v3 = &encrypted_flag;
v4 = 0;
setbuf(stdout, 0LL);
printf("The flag is: ", 0LL);
while ( 1 )
{
LODWORD(v5) = 0;
do
{
a2a = 0;
fibonacci(v4 + v5, &a2a);
v8 = v7;
v5 = v7 + 1;
}
while ( v5 != 8 );
v4 += 8;
if ( (unsigned __int8)(a2a << v8) == v6 )
break;
v3 = (char *)v3 + 1;
_IO_putc((char)(v6 ^ ((_BYTE)a2a << v8)), stdout);
v9 = *((char *)v3 - 1);
}
_IO_putc(10, stdout);
return 0LL;
}
unsigned int __fastcall fibonacci(int i, _DWORD *a2)
{
_DWORD *v2; // rbp@1
unsigned int v3; // er12@3
unsigned int result; // eax@3
unsigned int v5; // edx@3
unsigned int v6; // esi@3
unsigned int v7; // edx@4
v2 = a2;
if ( i )
{
if ( i == 1 )
{
result = fibonacci(0, a2);
v5 = result - ((result >> 1) & 0x55555555);
v6 = ((result - ((result >> 1) & 0x55555555)) >> 2) & 0x33333333;
}
else
{
v3 = fibonacci(i - 2, a2);
result = v3 + fibonacci(i - 1, a2);
v5 = result - ((result >> 1) & 0x55555555);
v6 = ((result - ((result >> 1) & 0x55555555)) >> 2) & 0x33333333;
}
v7 = v6 + (v5 & 0x33333333) + ((v6 + (v5 & 0x33333333)) >> 4);
*v2 ^= ((BYTE1(v7) & 0xF) + (v7 & 0xF) + (unsigned __int8)((((v7 >> 8) & 0xF0F0F) + (v7 & 0xF0F0F0F)) >> 16)) & 1;
}
else
{
*a2 ^= 1u;
result = 1;
}
return result;
}
接下来是main
函数的反汇编代码
.text:0x4004E0 main proc near ; DATA XREF: start+1Do
.text:0x4004E0
.text:0x4004E0 var_1C = dword ptr -1Ch
.text:0x4004E0
.text:0x4004E0 push rbp
.text:0x4004E1 push rbx
.text:0x4004E2 xor esi, esi ; buf
.text:0x4004E4 mov ebp, offset unk_4007E1
.text:0x4004E9 xor ebx, ebx
.text:0x4004EB sub rsp, 18h
.text:0x4004EF mov rdi, cs:stdout ; stream
.text:0x4004F6 call _setbuf
.text:0x4004FB mov edi, offset format ; "The flag is: "
.text:0x400500 xor eax, eax
.text:0x400502 call _printf
.text:0x400507 mov r9d, 49h
.text:0x40050D nop dword ptr [rax]
.text:0x400510
.text:0x400510 loc_400510: ; CODE XREF: main+8Aj
.text:0x400510 xor r8d, r8d
.text:0x400513 jmp short loc_40051B
.text:0x400513 ; ---------------------------------------------------------------------------
.text:0x400515 align 8
.text:0x400518
.text:0x400518 loc_400518: ; CODE XREF: main+67j
.text:0x400518 mov r9d, edi
.text:0x40051B
.text:0x40051B loc_40051B: ; CODE XREF: main+33j
.text:0x40051B lea edi, [rbx+r8]
.text:0x40051F lea rsi, [rsp+28h+var_1C]
.text:0x400524 mov [rsp+28h+var_1C], 0
.text:0x40052C call fibonacci
.text:0x400531 mov edi, [rsp+28h+var_1C]
.text:0x400535 mov ecx, r8d
.text:0x400538 add r8, 1
.text:0x40053C shl edi, cl
.text:0x40053E mov eax, edi
.text:0x400540 xor edi, r9d
.text:0x400543 cmp r8, 8
.text:0x400547 jnz short loc_400518
.text:0x400549 add ebx, 8
.text:0x40054C cmp al, r9b
.text:0x40054F mov rsi, cs:stdout ; fp
.text:0x400556 jz short loc_400570
.text:0x400558 movsx edi, dil ; c
.text:0x40055C add rbp, 1
.text:0x400560 call __IO_putc
.text:0x400565 movzx r9d, byte ptr [rbp-1]
.text:0x40056A jmp short loc_400510
.text:0x40056A ; ---------------------------------------------------------------------------
.text:0x40056C align 10h
.text:0x400570
.text:0x400570 loc_400570: ; CODE XREF: main+76j
.text:0x400570 mov edi, 0Ah ; c
.text:0x400575 call __IO_putc
.text:0x40057A add rsp, 18h
.text:0x40057E xor eax, eax
.text:0x400580 pop rbx
.text:0x400581 pop rbp
.text:0x400582 retn
.text:0x400582 main endp
fibonacci
的反汇编代码:
.text:0x400670 fibonacci proc near ; CODE XREF: main+4Cp
.text:0x400670 ; fibonacci+19p ...
.text:0x400670 test edi, edi
.text:0x400672 push r12
.text:0x400674 push rbp
.text:0x400675 mov rbp, rsi
.text:0x400678 push rbx
.text:0x400679 jz short loc_4006F8
.text:0x40067B cmp edi, 1
.text:0x40067E mov ebx, edi
.text:0x400680 jz loc_400710
.text:0x400686 lea edi, [rdi-2]
.text:0x400689 call fibonacci
.text:0x40068E lea edi, [rbx-1]
.text:0x400691 mov r12d, eax
.text:0x400694 mov rsi, rbp
.text:0x400697 call fibonacci
.text:0x40069C add eax, r12d
.text:0x40069F mov edx, eax
.text:0x4006A1 mov ebx, eax
.text:0x4006A3 shr edx, 1
.text:0x4006A5 and edx, 55555555h
.text:0x4006AB sub ebx, edx
.text:0x4006AD mov ecx, ebx
.text:0x4006AF mov edx, ebx
.text:0x4006B1 shr ecx, 2
.text:0x4006B4 and ecx, 33333333h
.text:0x4006BA mov esi, ecx
.text:0x4006BC
.text:0x4006BC loc_4006BC: ; CODE XREF: fibonacci+C2j
.text:0x4006BC and edx, 33333333h
.text:0x4006C2 lea ecx, [rsi+rdx]
.text:0x4006C5 mov edx, ecx
.text:0x4006C7 shr edx, 4
.text:0x4006CA add edx, ecx
.text:0x4006CC mov esi, edx
.text:0x4006CE and edx, 0F0F0F0Fh
.text:0x4006D4 shr esi, 8
.text:0x4006D7 and esi, 0F0F0Fh
.text:0x4006DD lea ecx, [rsi+rdx]
.text:0x4006E0 mov edx, ecx
.text:0x4006E2 shr edx, 10h
.text:0x4006E5 add edx, ecx
.text:0x4006E7 and edx, 1
.text:0x4006EA xor [rbp+0], edx
.text:0x4006ED pop rbx
.text:0x4006EE pop rbp
.text:0x4006EF pop r12
.text:0x4006F1 retn
.text:0x4006F1 ; ---------------------------------------------------------------------------
.text:0x4006F2 align 8
.text:0x4006F8
.text:0x4006F8 loc_4006F8: ; CODE XREF: fibonacci+9j
.text:0x4006F8 mov edx, 1
.text:0x4006FD xor [rbp+0], edx
.text:0x400700 mov eax, 1
.text:0x400705 pop rbx
.text:0x400706 pop rbp
.text:0x400707 pop r12
.text:0x400709 retn
.text:0x400709 ; ---------------------------------------------------------------------------
.text:0x40070A align 10h
.text:0x400710
.text:0x400710 loc_400710: ; CODE XREF: fibonacci+10j
.text:0x400710 xor edi, edi
.text:0x400712 call fibonacci
.text:0x400717 mov edx, eax
.text:0x400719 mov edi, eax
.text:0x40071B shr edx, 1
.text:0x40071D and edx, 55555555h
.text:0x400723 sub edi, edx
.text:0x400725 mov esi, edi
.text:0x400727 mov edx, edi
.text:0x400729 shr esi, 2
.text:0x40072C and esi, 33333333h
.text:0x400732 jmp short loc_4006BC
.text:0x400732 fibonacci endp
有许多种方法来解决该任务。例如,可以通过某种编程语言重新构建代码,然后在该语言中应用优化。重建代码的过程并不容易,可能会引入一些BUG和错误。盯着代码找错误并不好笑。。通过Unicorn引擎来解决这个任务可以跳过重构代码的过程,避免上面提到的问题。当然也可以通过其他的几种方法来避免重写代码-例如gdb脚本或者使用Frida.
在开始优化之前,我们先来用Unicorn引擎模拟一个正常的程序,不进行优化,成功之后,再开始优化。
Part1:模拟一个程序
首先,创建一个名为fibonacci.py
的脚本,然后将二进制文件放到同一目录下。
先向脚本中添加如下代码:
from unicorn import *
from unicorn.x86_const import *
第一行代码会加载主要的二进制模块和一些Unicorn中的一些基本常量。第二行加载了一些特定的x86和x64的常量。
接下来,加入以下代码:
import struct
def read(name):
with open(name) as f:
return f.read()
def u32(data):
return struct.unpack("I", data)[0]
def p32(num):
return struct.pack("I", num)
这里,我只添加了一些常用的在之后会用到的一些函数
read
仅仅返回文件中的内容
u32
将4字节的string(译注:python2中的string等同于bytes array)转换为integer,以小端序表示这个数据。
p32
与u32
相反,将一个数字转换为以小端序保存的4字节string.
如果你安装了pwntools,可以不创建这两个函数,直接from pwn import*
就可以了。
接下来为x86-64架构初始化一下Unicorn引擎。
mu = Uc (UC_ARCH_X86, UC_MODE_64)
Uc
函数需要一下参数:
- 第一个参数:架构类型。这些常量以
UC_ATCH_
为前缀
- 第二个参数:架构细节说明。这些常量以
UC_MODE_
为前缀
你可以在备忘录中找到这些常量。
正如我之前所写,使用Unicorn引擎之前需要手动初始化内存。针对这个二进制文件来说,我们需要将代码写入到某个地方,并且分配一些栈空间。
二进制文件的基址是0x400000
。栈的话不妨从地址0x0
开始,大小为1024*1024
字节(译注:即1M)。或许并不需要这么大的栈空间,但是也没有多大的影响。
可以使用mem_map
函数来映射内存。
使用如下代码:
BASE = 0x400000
STACK_ADDR = 0x0
STACK_SIZE = 1024*1024
mu.mem_map(BASE, 1024*1024)
mu.mem_map(STACK_ADDR, STACK_SIZE)
此时,我们可以和加载器一样,加载二进制文件到准备好的基址上来了。然后需要设置RSP
指向我们申请的栈空间底部。
mu.mem_write(BASE, read("./fibonacci"))
mu.reg_write(UC_X86_REG_RSP, STACK_ADDR + STACK_SIZE - 1)
可以开始模拟和执行代码了,但是需要先知道从哪开始从哪结束。
我们可以从地址0x00000000004004E0
开始执行代码,这是main
函数开始的地方。结尾的话可以是0x0000000000400575
。这是puts("\n")
函数所在,在Flag输出之后被调用。汇编代码如下:
.text:0x400570 mov edi, 0Ah ; c
.text:0x400575 call __IO_putc
可以开始执行模拟了:
mu.emu_start(0x00000000004004E0, 0x0000000000400575)
此时,可以执行这个脚本:
a@x:~/Desktop/unicorn_engine_lessons$ python solve.py
Traceback (most recent call last):
File "solve.py", line 32, in <module>
mu.emu_start(0x00000000004004E0, 0x0000000000400575)
File "/usr/local/lib/python2.7/dist-packages/unicorn/unicorn.py", line 288, in emu_start
raise UcError(status)
unicorn.unicorn.UcError: Invalid memory read (UC_ERR_READ_UNMAPPED)
oooooops,发生了一些我们不知道的错误呀。在mu.emu_start
之前我们可以加入:
def hook_code(mu, address, size, user_data):
print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))
mu.hook_add(UC_HOOK_CODE, hook_code)
这几行代码加入了一个hook。我们自定的函数hook_code
在执行每一条代码模拟的时候都将被调用。参数如下:
Uc
实例句柄
- 指令的地址
- 执行的长度
- 用户自定义数据(我们可以在
hook_add
的可选参数中传递这个值)
此时,我们的脚本应该是这样子的 solve1.py
运行的时候就可以有以下的输出了:
a@x:~/Desktop/unicorn_engine_lessons$ python solve.py
>>> Tracing instruction at 0x4004e0, instruction size = 0x1
>>> Tracing instruction at 0x4004e1, instruction size = 0x1
>>> Tracing instruction at 0x4004e2, instruction size = 0x2
>>> Tracing instruction at 0x4004e4, instruction size = 0x5
>>> Tracing instruction at 0x4004e9, instruction size = 0x2
>>> Tracing instruction at 0x4004eb, instruction size = 0x4
>>> Tracing instruction at 0x4004ef, instruction size = 0x7
Traceback (most recent call last):
File "solve.py", line 41, in <module>
mu.emu_start(0x00000000004004E0, 0x0000000000400575)
File "/usr/local/lib/python2.7/dist-packages/unicorn/unicorn.py", line 288, in emu_start
raise UcError(status)
unicorn.unicorn.UcError: Invalid memory read (UC_ERR_READ_UNMAPPED)
这些输出表明,脚本执行以下的指令的时候发生了错误:
.text:0x4004EF mov rdi, cs:stdout ; stream
这条指令从地址0x601038
处读取内存(你可以在IDA Pro中看)。这是.bss
区段所在,然而我们并没有来分配这个区段。我的解决方案是跳过所有出现问题的指令。
接下来有一条指令:
.text:0x4004F6 call _setbuf
我们没有办法来调用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
寄存器的方式来跳过这些指令。
mu.reg_write(UC_X86_REG_RIP, address+size)
hook_code
函数现在看起来是这样的:
instructions_skip_list = [0x00000000004004EF, 0x00000000004004F6, 0x0000000000400502, 0x000000000040054F]
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)
同样的针对一个字节一个字节输出Flag的代码也需要做一些处理:
.text:0x400558 movsx edi, dil ; c
.text:0x40055C add rbp, 1
.text:0x400560 call __IO_putc
__IO_putc将一个字节输出到第一个参数
所在(RDI
寄存器)
这里就可以直接读取RDI
寄存器,然后输出,跳过模拟执行这部分代码。这里的hook_code
函数如下:
instructions_skip_list = [0x00000000004004EF, 0x00000000004004F6, 0x0000000000400502, 0x000000000040054F]
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)
此时,整个代码看起来应该是这样的solve2.py.
可以运行以下看一下,尽管它依然很慢。
a@x:~/Desktop/unicorn_engine_lessons$ python solve.py
h
x
Part2:提升速度!
考虑以下速度提升,为什么这个程序跑这么慢?
观察一下反编译代码,我们可以看到main
函数调用了fibonacci
函数若干次,并且fibonacci
函数是个递归函数。
看看这个函数,我们可以看到它有2个参数,返回2个值。第一个返回值存放在RAX
寄存器中,第二个是第二个参数的指针。深入研究一下main
和fibonacci
函数可以发现,第二个参数只能取值为0
或者1
。如果发现不了的话,可以运行gdb
然后在fibonacci
函数的起始地址下个断点观察。
为了优化这个函数,我们可以使用动态编程来记住所给参数的返回值。因为第二个参数只有两种取值,只需记住只记住2 * MAX_OF_FIRST_ARGUMENT
对就足够了。
可以在RIP
指向fibonacci
函数开始的时候获取函数的参数,值的返回在函数退出的时候。因为不能同时获取这两个值,所以我们需要一个栈来帮助我们在函数退出的时候获取这些值-在fibonacci
入口我们需要将参数入栈,最后的时候出栈。可以使用dict
来记住这些值。
如何保存这些成对的值?
- 在函数开始的时候,我们可以检查参数对应的值是否已经被dict记录
- 如果是,直接返回这个key-value就行,只需将返回值写入到
RAX
中,同时设置RIP
为RET
指令的值,退出这个函数。不能在fabonacci
函数内直接跳转到RET
,因为这条指令已经被HOOK了,所以我们跳转到main
中的ret
。
- 如果dict中没有出现参数和对应的值,将参数添加到dict中。
- 当退出函数的时候,保存返回值。可以从我们的栈结构中读取参数和返回值。
代码如下:
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
以防万一,完整的代码可以在这里找到。solve3.py
欢呼吧!我们已经成功地使用Unicorn引擎来优化程序。非常棒!
一些笔记
现在,我强烈建议你做一些小作业。在下边你可以找到我之前说的三个任务,每一个都有提示和可行的解决方案。你可以在解决这些小任务的时候参考一下备忘录
我觉得其中之一的难题是知道一些感兴趣的常量的名字。最好的方法是使用IPython tab completion。当你安装IPyhton后,你可以输入from unicorn import UC_ARCH_
然后使用TAB
键,然后所有的以UC_ARCH_
为前缀的常量都将被列举出来。
任务2
分析一下代码:
shellcode = "\xe8\xff\xff\xff\xff\xc0\x5d\x6a\x05\x5b\x29\xdd\x83\xc5\x4e\x89\xe9\x6a\x02\x03\x0c\x24\x5b\x31\xd2\x66\xba\x12\x00\x8b\x39\xc1\xe7\x10\xc1\xef\x10\x81\xe9\xfe\xff\xff\xff\x8b\x45\x00\xc1\xe0\x10\xc1\xe8\x10\x89\xc3\x09\xfb\x21\xf8\xf7\xd0\x21\xd8\x66\x89\x45\x00\x83\xc5\x02\x4a\x85\xd2\x0f\x85\xcf\xff\xff\xff\xec\x37\x75\x5d\x7a\x05\x28\xed\x24\xed\x24\xed\x0b\x88\x7f\xeb\x50\x98\x38\xf9\x5c\x96\x2b\x96\x70\xfe\xc6\xff\xc6\xff\x9f\x32\x1f\x58\x1e\x00\xd3\x80"
如你所见,代码被混淆了(命令disasm
是pwntools
的一部分):
a@x:~/Desktop/unicorn_engine_lessons$ disasm e8ffffffffc05d6a055b29dd83c54e89e96a02030c245b31d266ba12008b39c1e710c1ef1081e9feffffff8b4500c1e010c1e81089c309fb21f8f7d021d86689450083c5024a85d20f85cfffffffec37755d7a0528ed24ed24ed0b887feb509838f95c962b9670fec6ffc6ff9f321f581e00d380
0: e8 ff ff ff ff call 0x4
5: c0 5d 6a 05 rcr BYTE PTR [ebp+0x6a], 0x5
9: 5b pop ebx
a: 29 dd sub ebp, ebx
c: 83 c5 4e add ebp, 0x4e
f: 89 e9 mov ecx, ebp
11: 6a 02 push 0x2
13: 03 0c 24 add ecx, DWORD PTR [esp]
16: 5b pop ebx
17: 31 d2 xor edx, edx
19: 66 ba 12 00 mov dx, 0x12
1d: 8b 39 mov edi, DWORD PTR [ecx]
1f: c1 e7 10 shl edi, 0x10
22: c1 ef 10 shr edi, 0x10
25: 81 e9 fe ff ff ff sub ecx, 0xfffffffe
2b: 8b 45 00 mov eax, DWORD PTR [ebp+0x0]
2e: c1 e0 10 shl eax, 0x10
31: c1 e8 10 shr eax, 0x10
34: 89 c3 mov ebx, eax
36: 09 fb or ebx, edi
38: 21 f8 and eax, edi
3a: f7 d0 not eax
3c: 21 d8 and eax, ebx
3e: 66 89 45 00 mov WORD PTR [ebp+0x0], ax
42: 83 c5 02 add ebp, 0x2
45: 4a dec edx
46: 85 d2 test edx, edx
48: 0f 85 cf ff ff ff jne 0x1d
4e: ec in al, dx
4f: 37 aaa
50: 75 5d jne 0xaf
52: 7a 05 jp 0x59
54: 28 ed sub ch, ch
56: 24 ed and al, 0xed
58: 24 ed and al, 0xed
5a: 0b 88 7f eb 50 98 or ecx, DWORD PTR [eax-0x67af1481]
60: 38 f9 cmp cl, bh
62: 5c pop esp
63: 96 xchg esi, eax
64: 2b 96 70 fe c6 ff sub edx, DWORD PTR [esi-0x390190]
6a: c6 (bad)
6b: ff 9f 32 1f 58 1e call FWORD PTR [edi+0x1e581f32]
71: 00 d3 add bl, dl
73: 80 .byte 0x80
注意:代码基于x86-32
架构。syscall的调用号所对应的功能可以在这里找到
提示:
你可以HOOKint 80h
指令,二进制代码是cd 80
,然后读取寄存器和内存。请记住,shellcode是可以在任何地址被加载执行的代码,绝大多数的shellcode使用栈来执行。
解决方案:
在本贴回复中
任务3
可以从这里下载这个二进制文件。该代码使用了以下的编译选项:
gcc function.c -m32 -o function
源码如下:
int strcmp(char *a, char *b)
{
//get length
int len = 0;
char *ptr = a;
while(*ptr)
{
ptr++;
len++;
}
//comparestrings
for(int i=0; i<=len; i++)
{
if (a[i]!=b[i])
return 1;
}
return 0;
}
__attribute__((stdcall))
int super_function(int a, char *b)
{
if (a==5 && !strcmp(b, "batman"))
{
return 1;
}
return 0;
}
int main()
{
super_function(1, "spiderman");
}
任务是通过某种方法调用super_function
使它返回1
.
反汇编代码如下:
.text:0x8048464 super_function proc near ; CODE XREF: main+16p
.text:0x8048464
.text:0x8048464 arg_0 = dword ptr 8
.text:0x8048464 arg_4 = dword ptr 0Ch
.text:0x8048464
.text:0x8048464 push ebp
.text:0x8048465 mov ebp, esp
.text:0x8048467 call __x86_get_pc_thunk_ax
.text:0x804846C add eax, 1B94h
.text:0x8048471 cmp [ebp+arg_0], 5
.text:0x8048475 jnz short loc_8048494
.text:0x8048477 lea eax, (aBatman - 804A000h)[eax] ; "batman"
.text:0x804847D push eax
.text:0x804847E push [ebp+arg_4]
.text:0x8048481 call strcmp
.text:0x8048486 add esp, 8
.text:0x8048489 test eax, eax
.text:0x804848B jnz short loc_8048494
.text:0x804848D mov eax, 1
.text:0x8048492 jmp short locret_8048499
.text:0x8048494 ; ---------------------------------------------------------------------------
.text:0x8048494
.text:0x8048494 loc_8048494: ; CODE XREF: super_function+11j
.text:0x8048494 ; super_function+27j
.text:0x8048494 mov eax, 0
.text:0x8048499
.text:0x8048499 locret_8048499: ; CODE XREF: super_function+2Ej
.text:0x8048499 leave
.text:0x804849A retn 8
.text:0x804849A super_function endp
提示:
根据STDCALL
调用约定,当模拟开始的时候栈应该开起来像下边的这张图图片一样。在这张图片中,RET
仅仅是返回某个地址(可以是任何值)。

解决方案:
在本贴回复中
任务4
这个任务和第一个很像。区别是目标架构不是x86
而是小端ARM32
a@x:~/Desktop/unicorn_engine_lessons$ file task4
task4: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=3dbf508680ba3d023d3422025954311e1d8fb4a1, not stripped
可以在这里下载到二进制文件
ARM调用约定可能会帮助到你
正确答案:
2635833876
提示:
- 函数的第一个参数通过
R0
传递(UC_ARM_REG_R0
)
- 返回值同样在
R0
中
- 函数的第二个参数通过
R1
传递(UC_ARM_REG_R1
)
- 可以通过
mu = Uc (UC_ARCH_ARM,UC_MODE_LITTLE_ENDIAN)
来初始化Unicorn。
解决方案:
在本贴回复中
备忘录
from unicorn import *
- 加载Unicorn库。包含一些函数和基本的常量。
from unicorn.x86_const import*
- 加载 X86 和X64架构相关的常量
所有unicorn
模块中的常量
UC_API_MAJOR UC_ERR_VERSION UC_MEM_READ UC_PROT_ALL
UC_API_MINOR UC_ERR_WRITE_PROT UC_MEM_READ_AFTER UC_PROT_EXEC
UC_ARCH_ARM UC_ERR_WRITE_UNALIGNED UC_MEM_READ_PROT UC_PROT_NONE
UC_ARCH_ARM64 UC_ERR_WRITE_UNMAPPED UC_MEM_READ_UNMAPPED UC_PROT_READ
UC_ARCH_M68K UC_HOOK_BLOCK UC_MEM_WRITE UC_PROT_WRITE
UC_ARCH_MAX UC_HOOK_CODE UC_MEM_WRITE_PROT UC_QUERY_MODE
UC_ARCH_MIPS UC_HOOK_INSN UC_MEM_WRITE_UNMAPPED UC_QUERY_PAGE_SIZE
UC_ARCH_PPC UC_HOOK_INTR UC_MILISECOND_SCALE UC_SECOND_SCALE
UC_ARCH_SPARC UC_HOOK_MEM_FETCH UC_MODE_16 UC_VERSION_EXTRA
UC_ARCH_X86 UC_HOOK_MEM_FETCH_INVALID UC_MODE_32 UC_VERSION_MAJOR
UC_ERR_ARCH UC_HOOK_MEM_FETCH_PROT UC_MODE_64 UC_VERSION_MINOR
UC_ERR_ARG UC_HOOK_MEM_FETCH_UNMAPPED UC_MODE_ARM Uc
UC_ERR_EXCEPTION UC_HOOK_MEM_INVALID UC_MODE_BIG_ENDIAN UcError
UC_ERR_FETCH_PROT UC_HOOK_MEM_PROT UC_MODE_LITTLE_ENDIAN arm64_const
UC_ERR_FETCH_UNALIGNED UC_HOOK_MEM_READ UC_MODE_MCLASS arm_const
UC_ERR_FETCH_UNMAPPED UC_HOOK_MEM_READ_AFTER UC_MODE_MICRO debug
UC_ERR_HANDLE UC_HOOK_MEM_READ_INVALID UC_MODE_MIPS3 m68k_const
UC_ERR_HOOK UC_HOOK_MEM_READ_PROT UC_MODE_MIPS32 mips_const
UC_ERR_HOOK_EXIST UC_HOOK_MEM_READ_UNMAPPED UC_MODE_MIPS32R6 sparc_const
UC_ERR_INSN_INVALID UC_HOOK_MEM_UNMAPPED UC_MODE_MIPS64 uc_arch_supported
UC_ERR_MAP UC_HOOK_MEM_VALID UC_MODE_PPC32 uc_version
UC_ERR_MODE UC_HOOK_MEM_WRITE UC_MODE_PPC64 unicorn
UC_ERR_NOMEM UC_HOOK_MEM_WRITE_INVALID UC_MODE_QPX unicorn_const
UC_ERR_OK UC_HOOK_MEM_WRITE_PROT UC_MODE_SPARC32 version_bind
UC_ERR_READ_PROT UC_HOOK_MEM_WRITE_UNMAPPED UC_MODE_SPARC64 x86_const
UC_ERR_READ_UNALIGNED UC_MEM_FETCH UC_MODE_THUMB
UC_ERR_READ_UNMAPPED UC_MEM_FETCH_PROT UC_MODE_V8
UC_ERR_RESOURCE UC_MEM_FETCH_UNMAPPED UC_MODE_V9
一些unicorn.x86_const
中的常量
UC_X86_REG_EAX
UC_X86_REG_RIP
UC_X86_REG_RAX
mu = Uc(arch,mode)
- 获取Uc
实例。在这里指定目标架构,例如:
mu = Uc(UC_ARCH_X86,UC_MODE_64)
- 获取X86-64
架构的实例。
mu = Uc(UC_ARCH_X86,UC_MODE_32)
- 获取X86-32
架构的实例。
mu.mem_map(ADDRESS,4096)
- 映射一片内存区域
mu.mem_write(ADDRESS,DATA)
- 向内存中写入数据
tmp = mu.mem_read(ADDRESS,SIZE)
- 从内存中读取数据
mu.reg_write(UC_X86_REG_ECX,0X0)
- 设置ECX值。
r_esp = mu.reg_read(UC_X86_REG_ESP)
- 读取ESP的值。
mu.emu_start(ADDRESS_START,ADDRESS_END)
- 开始执行模拟。
命令追踪:
def hook_code(mu, address, size, user_data):
print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))
mu.hook_add(UC_HOOK_CODE, hook_code)
这段代码添加了一个HOOK(向Unicorn引擎中),我们定义的函数会在执行每一条命令之前被执行。参数含义如下:
Uc
实例
- 指令的地址
- 指令的长度
- 用户定义数据(通过
hook_add()
函数传递)
参考:
原文链接:
http://eternal.red/2018/unicorn-engine-tutorial/
本文由看雪翻译小组zplusplus翻译
IDA插件开发入门