2012/01/04

反組譯 c/c++ disassembly

這篇不是教人家怎麼反組譯,因為大部份都需要好的工具,就像要反組譯 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 還有一些參數,例如把輸入導到檔案之類的,可以自行上網查。


0 意見: