3/18/2009

什麼是反組譯

程式有分高低階
低階有機器語言(0 與 1 的組合), 組合語言(以簡易的名稱/指令來代表 0/1 的組合).
組合語言是唯一與機器語言產生一對一的關係.

當原始碼寫好,要將他轉成機碼時, 在低階(ASM)叫做組譯, 如果是高階(C, BASIC)叫做編譯/直譯...

相對的, 把 ASM 翻成機碼叫組譯, 那把組譯翻成 ASM 叫反組譯

要看你電腦的平台是什麼,理論上是可以,實際上就要看有沒現成的工具給你用,不然只好自己寫,不過有時高階寫出來的一行等於低階的好幾 xxx 行,此時你在閱讀程式碼時就造成了困擾了


假設一段C程式碼如下:
a=b+c;
d=e-f;
經過c語言編譯器轉譯為組合語,再由組譯器轉為機器語言。有些編譯器直接就轉譯為機器語言,這要看編譯器如何設計。
(為了方便解釋,用a,b,c,d,e,f來代暫存器,實際上的寫法並不是如此)
add—起動加法電路
sub-起動減法電路(實際上還是用加法器來做減法)
add a, b,c (將暫存器b,c的值相加放回暫存器a)
sub d,e ,f(將暫存器e,f的值相減放回暫存器d)
cpu計算完後,把結果值放回記憶體。
經過組譯器轉譯後的機器碼為:
以下為假設性的排列:(實際上還是要看cpu的指令集的對應關係,及如何設計)
我用4個位元來表示;
0001 0010 0011
0100 0101 0110
反組譯的意思,就是相辦法把機器語言,用反向操作的方式還原。
必須要相當了解各種平台CPU的指令集機構,及指令集對照表來設計反組譯程式。難度很高,因為各種語的語法都不相同,資料結構也不同。
還有另一位一種方法是透過中介語言來逹成。

範例:
c原始碼:
#include
main()
{
printf("Hello World\n");
}
[root@localhost test]# ls
hello.c
[root@localhost test]# gcc hello.c
[root@localhost test]# ls
a.out hello.c
[root@localhost test]# ./a.out
Hello Wolrd
[root@localhost test]# objdump -d a.out > hello.asm #反組譯
[root@localhost test]# ls
a.out hello.asm hello.c
[root@localhost test]# vi hello.asm #反組譯結果
8048337: 68 38 95 04 08 push $0x8049538
804833c: e8 bf 7c fb f7 call 0 <_init-0x8048250>
8048341: 83 c4 10 add $0x10,%esp
8048344: c9 leave
8048345: c3 ret
8048346: 90 nop
8048347: 90 nop

08048348
:
8048348: 55 push %ebp
8048349: 89 e5 mov %esp,%ebp
804834b: 83 ec 08 sub $0x8,%esp
804834e: 83 e4 f0 and $0xfffffff0,%esp
8048351: b8 00 00 00 00 mov $0x0,%eax
8048356: 29 c4 sub %eax,%esp
8048358: 83 ec 0c sub $0xc,%esp
804835b: 68 40 84 04 08 push $0x8048440
8048360: e8 23 ff ff ff call 8048288 <_init+0x38>
8048365: 83 c4 10 add $0x10,%esp
8048368: c9 leave
8048369: c3 ret
804836a: 90 nop
804836b: 90 nop

0804836c <__libc_csu_init>:
804836c: 55 push %ebp
804836d: 89 e5 mov %esp,%ebp
804836f: 57 push %edi
8048370: 56 push %esi
8048371: 53 push %ebx
8048372: 83 ec 0c sub $0xc,%esp
8048375: e8 00 00 00 00 call 804837a <__libc_csu_init+0xe>
804837a: 5b pop %ebx
804837b: 81 c3 c2 11 00 00 add $0x11c2,%ebx
8048381: e8 ca fe ff ff call 8048250 <_init>
8048386: 8d 93 18 ff ff ff lea 0xffffff18(%ebx),%edx
804838c: 8d 8b 18 ff ff ff lea 0xffffff18(%ebx),%ecx
8048392: 29 ca sub %ecx,%edx
8048394: c1 fa 02 sar $0x2,%edx

a.out: file format elf32-i386

Disassembly of section .init:

08048250 <_init>:
8048250: 55 push %ebp
8048251: 89 e5 mov %esp,%ebp
8048253: 83 ec 08 sub $0x8,%esp
8048256: e8 61 00 00 00 call 80482bc
804825b: e8 bc 00 00 00 call 804831c
8048260: e8 93 01 00 00 call 80483f8 <__do_global_ctors_aux>
8048265: c9 leave
8048266: c3 ret
Disassembly of section .plt:

08048268 <.plt>:
8048268: ff 35 40 95 04 08 pushl 0x8049540
804826e: ff 25 44 95 04 08 jmp *0x8049544
8048274: 00 00 add %al,(%eax)
8048276: 00 00 add %al,(%eax)
8048278: ff 25 48 95 04 08 jmp *0x8049548
804827e: 68 00 00 00 00 push $0x0
8048283: e9 e0 ff ff ff jmp 8048268 <_init+0x18>
8048288: ff 25 4c 95 04 08 jmp *0x804954c
804828e: 68 08 00 00 00 push $0x8
8048293: e9 d0 ff ff ff jmp 8048268 <_init+0x18>
Disassembly of section .text:

08048298 <_start>:
8048298: 31 ed xor %ebp,%ebp
804829a: 5e pop %esi
804829b: 89 e1 mov %esp,%ecx
804829d: 83 e4 f0 and $0xfffffff0,%esp
"hello.asm" 214L, 9749C



先從 compiler 說起, 假設一個程式很簡單就是

int main()
{
printf('hello, world');
}

只是很簡單的一行程式, 在螢幕上 show "hello, world", 在 compile 成執行檔就是一大堆 binary code, 當你用反組譯程式解回來時,當然不會是你上面看到的樣子,實際上類似這樣:

520:0100 0100 ADD [BX+SI],AX
520:0102 CD21 INT 21
520:0104 7217 JB 011D
520:0106 40 INC AX
520:0107 A3A800 MOV [00A8],AX
520:010A 48 DEC AX
520:010B 8EC0 MOV ES,AX
520:010D B449 MOV AH,49
520:010F CD21 INT 21
520:0111 720A JB 011D
520:0113 B80158 MOV AX,5801
520:0116 BB0000 MOV BX,0000
520:0119 CD21 INT 21
520:011B 7303 JNB 0120
520:011D E98901 JMP 02A9
.......

你唯一會看到的是一大堆 assembly code, 道理很簡單, 雖然只是一行程式, 但實際上有很多 I/O & Function Call 在運作, 上面是C程式,算是中階語言,如果換做是其他更高階的電腦語言,如 BASIC -> C -> ASM 中間經過多少道 "轉換" 程序. 這也就是我所謂即使解出來意義也不大了.