這篇不是教人家怎麼反組譯,因為大部份都需要好的工具,就像要反組譯 android apk 可以用 smali 一樣。
在 android 上要反組譯 c/c++ 的執行檔是相當麻煩的,不過常用的 Linux 工具可以提供我們一個思考方向:nm, objdump, 甚至是 gdb。
寫在前面:其實是有你看這篇時心中想要的工具,不過好用的是要錢的,不要錢的我自己都編譯不了,但是還是提供一下資訊供大家參考:
boomerang
objdump 是很常見的,網路上也有相當多的文件,有興趣的人可以自行找找看,或是
參考這篇
我舉個簡單的例子來說明 nm, objdump
#include
int sum(int a, int b){
return a+b;
}
int main(int argc, char* argv[])
{
int i, j;
i = 10;
j = i*2;
printf ("Hello, %d + %d == %d\n", i, j, sum(i, j));
return 0;
}
編譯: 通常拿到執行檔的時候,它的編譯選項是不可能加 -g 的(
gcc -g hello.c -o hello),如果有的話,算是你幸運,可以直接下
objdump -S hello 就可以得到下面的結果(大部份都略過):
00000000004004f4 :
#include
int sum(int a, int b){
4004f4: 55 push %rbp
4004f5: 48 89 e5 mov %rsp,%rbp
4004f8: 89 7d fc mov %edi,-0x4(%rbp)
4004fb: 89 75 f8 mov %esi,-0x8(%rbp)
return a+b;
4004fe: 8b 45 f8 mov -0x8(%rbp),%eax
400501: 8b 55 fc mov -0x4(%rbp),%edx
400504: 8d 04 02 lea (%rdx,%rax,1),%eax
}
400507: c9 leaveq
400508: c3 retq
上面可以見到 sum() 的程式碼......其實 main() 的也有,往下找....
0000000000400509 :
int main(int argc, char* argv[])
{
400509: 55 push %rbp
40050a: 48 89 e5 mov %rsp,%rbp
40050d: 48 83 ec 20 sub $0x20,%rsp
400511: 89 7d ec mov %edi,-0x14(%rbp)
400514: 48 89 75 e0 mov %rsi,-0x20(%rbp)
int i, j;
i = 10;
400518: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp)
j = i*2;
40051f: 8b 45 fc mov -0x4(%rbp),%eax
400522: 01 c0 add %eax,%eax
400524: 89 45 f8 mov %eax,-0x8(%rbp)
printf ("Hello, %d + %d == %d\n", i, j, sum(i, j));
400527: 8b 55 f8 mov -0x8(%rbp),%edx
40052a: 8b 45 fc mov -0x4(%rbp),%eax
40052d: 89 d6 mov %edx,%esi
40052f: 89 c7 mov %eax,%edi
400531: e8 be ff ff ff callq 4004f4
400536: 89 c1 mov %eax,%ecx
400538: b8 4c 06 40 00 mov $0x40064c,%eax
40053d: 8b 55 f8 mov -0x8(%rbp),%edx
400540: 8b 75 fc mov -0x4(%rbp),%esi
400543: 48 89 c7 mov %rax,%rdi
400546: b8 00 00 00 00 mov $0x0,%eax
40054b: e8 a0 fe ff ff callq 4003f0
return 0;
400550: b8 00 00 00 00 mov $0x0,%eax
}
不過,問題是,我們拿到的執行檔應該是沒有加 -g 這個 debug 選項的,事實上,在沒有更好的工具時,有一個方法可以「觀察」,就是
nm -s hello (PS: 假設
gcc hello.c -o hello)
...
0000000000600e24 d __init_array_end
0000000000600e24 d __init_array_start
00000000004005f0 T __libc_csu_fini
0000000000400560 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000601020 A _edata
0000000000601030 A _end
0000000000400638 T _fini
00000000004003c8 T _init
0000000000400410 T _start
000000000040043c t call_gmon_start
0000000000601020 b completed.6557
0000000000000000 a crtstuff.c
0000000000000000 a crtstuff.c
0000000000601010 W data_start
0000000000601028 b dtor_idx.6559
00000000004004d0 t frame_dummy
0000000000000000 a hello.c
0000000000400509 T main
U printf@@GLIBC_2.2.5
00000000004004f4 T sum
此時可以見到程式中定義了 main, 使用(呼叫)了 printf(), 及 sum()
PS: 上面有很多底線開頭的符號,都可以直接略過,因為那都是「內建」符號
講到這邊,有人可能要大聲抗議,這怎麼叫反組譯啊!!!!我可沒說反組譯回 C/C++ source code.....若你能讀懂組合語言的話.....當然,正常程式寫作上,光函式名稱, 也就是符號表就常常暗示了很大的一部份程式運作了。
還有一個命令叫 strings, 上面無 -g 的 hello 也可以用
strings hello 得到下面結果:
/lib64/ld-linux-x86-64.so.2
__gmon_start__
libc.so.6
printf
__libc_start_main
GLIBC_2.2.5
fff.
=p
l$ L
t$(L
|$0H
Hello, %d + %d == %d
看到最後一行的 hello, %d + %d == %d 了嗎?那就是出現在程式中的字串
最後最後,提供一個 debug 的工具叫 strace,我會提它是因為它也是 busybox 有提供的工具。
strace 的功能是「追蹤命令執行時所呼叫到的系統函式」,這一段說明恐怕你不容易了解,但是能追蹤系統呼叫通常也是解密的最重要線索,用法如下:
strace ./hello
PS: strace 後面要接的是執行的命令,因此是 ./hello, 而不是 hello
PS2: strace 還有一些參數,例如把輸入導到檔案之類的,可以自行上網查。