我正在学习逆向我正在学习一个代码,它会自我修改(实际上它是一个crackme)。
它包含以下语句:movw $0xc031, 0x(%edx)
,其中edx
包含语句的地址。通过断点,我可以停止并检查修改后的代码。我将其保存为二进制文件并反汇编objdump
以查看新语句是什么。
逐行执行此操作有点慢。有没有更好的方法来反转这段代码?也许还有其他gdb
我不知道的工具或功能。
我正在学习逆向我正在学习一个代码,它会自我修改(实际上它是一个crackme)。
它包含以下语句:movw $0xc031, 0x(%edx)
,其中edx
包含语句的地址。通过断点,我可以停止并检查修改后的代码。我将其保存为二进制文件并反汇编objdump
以查看新语句是什么。
逐行执行此操作有点慢。有没有更好的方法来反转这段代码?也许还有其他gdb
我不知道的工具或功能。
只是为了踢我下载了crackme并将其提供给windows box中的radare不需要动态分析来解决这个crackme
雷达2面
让这个二进制串串起来
[0x080495e0]> iz
vaddr=0x080483d0 paddr=0x000003d0 ordinal=000 sz=41 len=40 section=.rodata type=
a string=run as ./prog a_number (e.g. ./prog 90)\n
vaddr=0x080483f9 paddr=0x000003f9 ordinal=001 sz=7 len=6 section=.rodata type=a
string=good.\n
vaddr=0x08048400 paddr=0x00000400 ordinal=002 sz=6 len=5 section=.rodata type=a
string=bad.\n
因此,该文件似乎将单个参数作为输入,并根据字符串输出中的某些标准打印好或坏
让我们继续前进,看看入口点有什么
[0x080495e0]> pd 14
;-- entry0:
;-- section..text:
0x080495e0 31ed xor ebp, ebp ; [19]
0x080495e2 5e pop esi
0x080495e3 89e1 mov ecx, esp
0x080495e5 83e4f0 and esp, 0xfffffff0
0x080495e8 50 push eax
0x080495e9 54 push esp
0x080495ea 52 push edx
0x080495eb 68a0980408 push 0x80498a0
0x080495f0 6840980408 push 0x8049840
0x080495f5 51 push ecx
0x080495f6 56 push esi
0x080495f7 6850970408 push 0x8049750
0x080495fc e86fedffff call sym.imp.__libc_start_main
^- 0x08048370(unk, unk, unk, unk, unk, unk, unk, unk) ; sym.imp._
0x08049601 f4 hlt
[0x080495e0]> pd 1 @0x8048370
;-- sym.imp.__libc_start_main:
0x08048370 ff25c8950408 jmp dword [reloc.__libc_start_main_200]
[0x080495e0]>
__libc_start_main
在网络上搜索将产生该函数的原型
(__libc_start_main) (int (*main) (int, char **, char **),
int argc,
char *__unbounded *__unbounded ubp_av,
void (*init) (void),
void (*fini) (void),
void (*rtld_fini) (void),
void *__unbounded stack_end)
所以8049750
一定是main的地址,我们来反汇编一下:
[0x080495e0]> pd @ 0x8049750
0x08049750 55 push ebp
0x08049751 89e5 mov ebp, esp
0x08049753 53 push ebx
0x08049754 83ec44 sub esp, 0x44
0x08049757 e800000000 call 0x804975c
^- 0x0804975c() ; entry0
0x0804975c 58 pop eax
0x0804975d 81c060feffff add eax, 0xfffffe60
,=< 0x0804977d 0f8422000000 je 0x80497a5 <--other_branch
--------------------
0x0804977a 8945e4 mov dword [ebp - 0x1c], eax
它将返回地址从堆栈中移除eax
并添加到-1a0
,eax = 804975c + (-1a0 ) = 80495bc
并将其存储在局部argc
变量中,
并进一步读取反汇编我们可以推断它检查是否等于 2(即仅向程序提供一个参数),如果不等于,它用它来打印一些东西
0x08049783 8b45e4 mov eax, dword [ebp - 0x1c]
0x08049786 8d8814eeffff lea ecx, [eax - 0x11ec]
0x0804978c 890c24 mov dword [esp], ecx
0x0804978f 89c3 mov ebx, eax
0x08049791 e8faebffff call sym.imp.printf
0x08049796 c745f8000000. mov dword [ebp - 8], 0
0x0804979d 8945e0 mov dword [ebp - 0x20], eax
0x080497a0 e983000000 jmp 0x8049828
[0x080495e0]> pd @ 0x8049828
0x08049828 8b45f8 mov eax, dword [ebp - 8]
0x0804982b 83c444 add esp, 0x44
0x0804982e 5b pop ebx
0x0804982f 5d pop ebp
0x08049830 c3 ret
让我们看看打印到控制台的内容
eax- 0x11ec = 80483d0
所以我们在字符串输出中看到了这个地址,不是吗?不记得和懒惰向上滚动?
[0x080495e0]> psz @0x80483d0
run as ./prog a_number (e.g. ./prog 90)
所以我们可以调用这个分支 usage()
if(argc != 2){ usage();return 0;}
让我们关注other_branch
看来它需要从命令行传递的参数,并使用strtol
并调用函数将其转换为数字,如果返回值不为 0,则打印good else, bad。
[0x080495e0]> pd @ 0x80497a5
0x080497a5 31c0 xor eax, eax
0x080497a7 b90a000000 mov ecx, 0xa
0x080497ac 8b55f0 mov edx, dword [ebp - 0x10] <- char* argv[]
0x080497af 8b5204 mov edx, dword [edx + 4] ; [0x4
0x080497b2 891424 mov dword [esp], edx
0x080497b5 c74424040000. mov dword [esp + 4], 0 ; [0x4
0x080497bd c74424080a00. mov dword [esp + 8], 0xa ; [0x8
0x080497c5 8b5de4 mov ebx, dword [ebp - 0x1c] this is 80495bc
0x080497c8 8945dc mov dword [ebp - 0x24], eax
0x080497cb 894dd8 mov dword [ebp - 0x28], ecx
0x080497ce e8cdebffff call sym.imp.strtol
^- 0x080483a0() ; sym.imp.strtol
0x080497d3 8945ec mov dword [ebp - 0x14], eax
0x080497d6 8b45ec mov eax, dword [ebp - 0x14]
0x080497d9 890424 mov dword [esp], eax
0x080497dc 8b5de4 mov ebx, dword [ebp - 0x1c]
0x080497df e8fcfeffff call 0x80496e0
0x080497e4 8945e8 mov dword [ebp - 0x18], eax
0x080497e7 837de800 cmp dword [ebp - 0x18], 0
,=< 0x080497eb 0f841b000000 je 0x804980c
| 0x080497f1 8b45e4 mov eax, dword [ebp - 0x1c]
| 0x080497f4 8d883deeffff lea ecx, [eax - 0x11c3]
| 0x080497fa 890c24 mov dword [esp], ecx
| 0x080497fd 89c3 mov ebx, eax
| 0x080497ff e88cebffff call sym.imp.printf
`11c3 处的字符串可能是好孩子之一:
[0x080495e0]> psz @0x80495bc - 0x11c3
good.
[0x080495e0]>
让我们看看函数的其余部分:
[0x080495e0]> pd 13 @ 0x804980c
0x0804980c 8b45e4 mov eax, dword [ebp - 0x1c]
0x0804980f 8d8844eeffff lea ecx, [eax - 0x11bc]
0x08049815 890c24 mov dword [esp], ecx
0x08049818 89c3 mov ebx, eax
0x0804981a e871ebffff call sym.imp.printf
^- 0x08048390() ; sym.imp.printf
0x0804981f 8945d0 mov dword [ebp - 0x30], eax
0x08049822 8b45e8 mov eax, dword [ebp - 0x18]
0x08049825 8945f8 mov dword [ebp - 8], eax
0x08049828 8b45f8 mov eax, dword [ebp - 8]
0x0804982b 83c444 add esp, 0x44
0x0804982e 5b pop ebx
0x0804982f 5d pop ebp
0x08049830 c3 ret
[0x080495e0]>
字符串在 11bc
[0x080495e0]> psz @0x80495bc - 0x11bc
bad.
[0x080495e0]>
所以我们需要分析函数在 0x80496e0
[0x08049644]> pdi @0x80496e0
0x080496e0 c7054b9704088b1424c3 mov dword [0x804974b], 0xc324148b
0x080496ea e85c000000 call 0x804974b
0x080496ef 66c7420631c0 mov word [edx + 6], 0xc031
0x080496f5 ebe9 jmp 0x80496e0
[0x08049644]>
因此,它写入0xc324148b
到804974b
并调用它(自我修改代码]什么是执行将被写入那里(通知字节序)的指令。
让我们使用rasm2
反向流来获取将要执行的反汇编
[0x08049644]> !rasm2 -d 0x8b1424c3
mov edx, dword [esp]
ret
所以它将返回地址移动到edx
( edx
= 0x080496ef
)
edx+6 = 0x80496f65
,所以jmp 0x80496e0
会变成xor eax eax
[0x08049644]> !rasm2 -d 31c0
xor eax, eax
[0x08049644]>
我希望你能从这里更进一步,rasm2
从流上的radare执行以获取修改后的指令并构造一个新的修改函数并对其进行分析。
您需要知道如何从该函数返回 1 才能获得良好的效果,这意味着您需要知道在命令行中传递给该函数的内容很可能是一个xor
伪代码
function selfie() { if ((argv[0] xor someconstant) == 0) {return 1} else { return 0; }
感谢 jvoisin 的编辑
找了几分钟用radare静态分析函数
===============================================================================
0x8049750 is main so disassembling 0x70 bytes (750-6e0)
pDi shows the bytes
instead of executing rasm –d we can use pad too
[0x080495e0]> pDi 0x70 @0x80496e0
===============================================================================
0x080496e0 c7054b9704088b1424c3 mov dword [0x804974b], 0xc324148b
[0x080495e0]> pad 8b1424c3
mov edx, dword [esp]
ret
so until further modification the following two instructions
will be executed on call 0x804974b
0x804974b 8b1424 mov edx , dword [esp]
0x804974e c3 ret
===============================================================================
0x080496ea e85c000000 call 0x804974b
will execute edx = [esp] and retn back
so edx will hold the return address of this call
which is 0x80496ef
===============================================================================
0x080496ef 66c7420631c0 mov word [edx + 6], 0xc031
0x80496ef+6 = 0x080496f5 overwrites next instruction with
[0x080495e0]> pad 31c0
xor eax, eax
===============================================================================
0x080496f5 ebe9 jmp 0x80496e0 over written
0x080496f5 31c0 xor eax,eax new instruction
===============================================================================
0x080496f7 c7420f66b9b001 mov dword [edx + 0xf], 0x1b0b966
0x80496ef+f = 0x080496fe overwrites next instruction with
[0x080495e0]> pad 66b9b001
mov cx, 0x1b0
...
===============================================================================
0x080496fe 335c241a xor ebx, dword [esp + 0x1a]
0x080496fe 66b9b001 mov cx, 0x1b0 first mod
0x080496fe 8b442408 mov eax, dword [esp + 8] second mod junk
===============================================================================
0x08049702 66bbb000 mov bx, 0xb0 executed as it is first time
0x08049702 3b442412 cmp eax, dword [esp + 0x12] second mod junk
===============================================================================
0x08049706 e840000000 call 0x804974b
Same logic as earlier edx will hold 0x804970b return address
and will be returning to Next instruction
===============================================================================
0x0804970b c742f38b442408 mov dword [edx - 0xd], 0x824448b
0x804970b-0xd = 0x080496fe
[0x080495e0]> pad 8b442408
mov eax, dword [esp + 8]
===============================================================================
0x08049712 c7421466837c24 mov dword [edx + 0x14], 0x247c8366
0x804970b+0x14 = 0x0804971f
0x08049719 66c742180407 mov word [edx + 0x18], 0x704
0x804970b+0x18 = 0x08049723
[0x080495e0]> pad 66837c240407
cmp word [esp + 4], 7
.
===============================================================================
0x0804971f 66837c24100d cmp word [esp + 0x10], 0xd over written
0x0804971f 66837c240407 cmp word [esp + 4], 7 new instruction
===============================================================================
0x08049725 660f44d9 cmove bx, cx exec as it is 1st time
0x08049725 66894c240f mov word [esp + 0xf], cx junk
===============================================================================
0x08049729 c742f73b442412 mov dword [edx - 9], 0x1224443b
0x804970b-0x9 = 0x08049702 see above it is junk
===============================================================================
0x08049730 e816000000 call 0x804974b
Same logic as earlier edx will hold 0x8049735 return address
and will be returning to Next instruction
===============================================================================
0x08049735 c742f1894c240f mov dword [edx - 0xf], 0xf244c89
0x0804973c c742f78944240f mov dword [edx - 9], 0xf244489
0x8049735-0x9 = 0x0804972c both writes to middle of sequnce junk mods
===============================================================================
0x08049743 66895a16 mov word [edx + 0x16], bx <------
0x08049747 c64218c3 mov byte [edx + 0x18], 0xc3 <--------
0x8049735+0x16/18 = 804974b,804974d
bx will hold 0x00b0 or 0x01b0 based on cmove based on cmp word [esp+4],7 Zf flag
and will be executed
[0x08049720]> pad b000
mov al, 0
[0x08049720]> pad b001
mov al, 1
[0x08049720]>
and the function will return back with either 0 or 1 :)
目前,处理自修改代码的最佳方法是使用动态分析。大多数静态分析技术都无法检测和找出代码的修改部分。
因此,您需要一个工具来提取软件执行的完整跟踪信息(我的意思是一条一条跟踪指令)。
你至少可以考虑两种情况,要么软件没有任何反调试保护,然后你可以使用gdb
Python API 来保存和显示每条指令的完整跟踪。或者,该软件具有一些反调试保护,您可以使用诸如Intel Pin 框架之类的工具来记录指令跟踪。
我gdb
今天有时间挖掘Python API 的方式。这是一个小脚本,它逐步执行程序,收集所有汇编指令并将其发送到stdout
of gdb
(我做得很快而且非常脏......有很多方法可以改进它),
import gdb
gdb.execute('break main')
gdb.execute('run')
while (True):
gdb.write (gdb.execute('x /i $pc', to_string=True).rstrip('\n'), gdb.STDOUT)
gdb.execute('stepi', to_string=False)
gdb.flush ()
然后,只需执行:
$> gdb -x ./script.py ./main 1> log.txt
我真的很想while
通过检测到程序结束来改进结束循环的方式gdb
。而且,为了更好地控制将输出干净地写入文件(而不是扔掉stdout
)……但是,我没有多余的时间来完成它。
有关所有内容的更多信息,请参见:
gdb
使用 Python扩展,在gdb
文档中。gdb
Python API,在gdb
文档中。gdb
教程,作者:Tom Tromey。