逆向自修改代码

逆向工程 拆卸 linux
2021-06-30 09:34:52

我正在学习逆向我正在学习一个代码,它会自我修改(实际上它是一个crackme)。

它包含以下语句:movw $0xc031, 0x(%edx),其中edx包含语句的地址。通过断点,我可以停止并检查修改后的代码。我将其保存为二进制文件并反汇编objdump以查看新语句是什么。

逐行执行此操作有点慢。有没有更好的方法来反转这段代码?也许还有其他gdb我不知道的工具或功能

3个回答

只是为了踢我下载了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并添加到-1a0eax = 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]>

因此,它写入0xc324148b804974b并调用它(自我修改代码]什么是执行将被写入那里(通知字节序)的指令。

让我们使用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 :)

目前,处理自修改代码的最佳方法是使用动态分析。大多数静态分析技术都无法检测和找出代码的修改部分。

因此,您需要一个工具来提取软件执行的完整跟踪信息(我的意思是一条一条跟踪指令)。

你至少可以考虑两种情况,要么软件没有任何反调试保护,然后你可以使用gdbPython API 来保存和显示每条指令的完整跟踪。或者,该软件具有一些反调试保护,您可以使用诸如Intel Pin 框架之类的工具来记录指令跟踪。

gdb今天有时间挖掘Python API 的方式。这是一个小脚本,它逐步执行程序,收集所有汇编指令并将其发送到stdoutof 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)……但是,我没有多余的时间来完成它。

有关所有内容的更多信息,请参见:

我通常建议使用英特尔的 PIN 框架,但是,对于单个 PE 文件来说,它有点过头了。

为什么不使用 Radare2 调试功能?您可以在此处了解如何使用它来解决破解问题