文章目录
反编译的流程
依然是 CSAPP bomblab phase_1 的小白视角的理解。先前已经根据汇编代码,手工翻译到了C代码,但是步骤上显得很吃力。为什么呢?汇编代码比较短的时候(比如10行之内),能够记住每个寄存器的值; 汇编代码超过10行时,很容易忘记每个寄存器里的值。虽然可以用注释的方式,把变量和寄存器名字绑定起来,但是每翻译一小段汇编代码后,就需要更新对应的C代码,过于繁琐了。例如 strings_not_equal 大概分成了10小段汇编代码。而且这个划分未必是合理的,还容易漏掉内容。
本篇则是前面两篇的尝试手工反汇编的重构版本: 使用了标准的4个步骤,让反汇编变得容易:
- 先获取汇编代码
- 在汇编代码上,以注释的形式,逐句翻译为C代码,期间会多次写出goto,也就是 je,jne,jmp 这些的跳转
- 抄写注释里写出的C代码,并增加用到了的 label,使得 goto 能正确跳转。此时也许直接用C编译器编译,能编译通过
- 消除goto和label:可能有很多goto,可以分两次、三次消除
- 简化代码: 在消除 goto 的时候, 对于 for 循环、while循环的原始C代码,一定会看到重复代码,可能上一步没消除干净,这一步处理掉
string_length 的反编译,第二次尝试
第0步: 打开 gdb, 查看函数汇编代码:
gdb bomb
(gdb) disas string_length
第1步: 复制汇编代码到文本文件,在汇编代码右侧,逐句写出对应的C代码
Dump of assembler code for function string_length: // int string_length(char* str)
0x000000000040131b <+0>: cmp BYTE PTR [rdi],0x0 // if (*str==0)
0x000000000040131e <+3>: je 0x401332 <string_length+23> // goto label23
0x0000000000401320 <+5>: mov rdx,rdi // char* p = str;
0x0000000000401323 <+8>: add rdx,0x1 // p += 1;
0x0000000000401327 <+12>: mov eax,edx // int ret = p;
0x0000000000401329 <+14>: sub eax,edi // ret = ret - str;
0x000000000040132b <+16>: cmp BYTE PTR [rdx],0x0 // if (!(*p == 0))
0x000000000040132e <+19>: jne 0x401323 <string_length+8> // goto label8
0x0000000000401330 <+21>: repz ret // return ret
0x0000000000401332 <+23>: mov eax,0x0 // ret = 0
0x0000000000401337 <+28>: ret // return ret
第2步:抄写C代码,并且添加用到的label,这些 label 是 goto 的目的位置
int string_length(char* str)
{
if (*str == 0)
{
goto label23;
}
char* p = str;
label8:
p += 1;
int ret = p - str;
if (*p != 0)
{
goto label8;
}
return ret;
label23:
ret = 0;
return ret;
}
第3步:消除 goto 和 label:
int string_length(char* str)
{
if (*str == 0)
{
return 0;
}
char* p = str;
while (true)
{
p += 1;
int ret = p - str;
if (*p == 0)
{
return ret;
}
}
}
第4步: 消除冗余C代码
int string_length(char* str)
{
if (*str == 0)
{
return 0;
}
char* p = str;
int ret = 1;
while (true)
{
p += 1;
ret = p - str;
if (*p == 0)
{
break;
}
}
return ret;
}
第5步: 消除冗余C代码
int string_length(char* str)
{
for (char* p = str; ; p++)
{
int ret = p - str;
if (*p == 0)
{
return ret;
}
}
}
strings_not_equal 反编译,第二次尝试
第0步: gdb 加载可执行文件, 使用 disas 命令查看反汇编代码。
gdb bomb
(gdb) disas strings_not_equal:
第1步: 在汇编代码右侧,逐句写出对应的C代码:
Dump of assembler code for function strings_not_equal: // int strings_not_equal(const char* s1, const char* s2) {
0x0000000000401338 <+0>: push r12 //
0x000000000040133a <+2>: push rbp //
0x000000000040133b <+3>: push rbx //
0x000000000040133c <+4>: mov rbx,rdi // char* p1 = s1; 根据后面 BYTE PTR 能确定类型是 char*
0x000000000040133f <+7>: mov rbp,rsi // char* p2 = s2;
0x0000000000401342 <+10>: call 0x40131b <string_length> // int len1 = string_length(s1);
0x0000000000401347 <+15>: mov r12d,eax //
0x000000000040134a <+18>: mov rdi,rbp // char* p = s2;
0x000000000040134d <+21>: call 0x40131b <string_length> // int len2 = string_length(p);
0x0000000000401352 <+26>: mov edx,0x1 // int ret = 1;
0x0000000000401357 <+31>: cmp r12d,eax // if (len1 != len2)
0x000000000040135a <+34>: jne 0x40139b <strings_not_equal+99> // goto label99
0x000000000040135c <+36>: movzx eax,BYTE PTR [rbx] // char c1 = *p1;
0x000000000040135f <+39>: test al,al // if (c1 == '\0')
0x0000000000401361 <+41>: je 0x401388 <strings_not_equal+80> // goto label80
0x0000000000401363 <+43>: cmp al,BYTE PTR [rbp+0x0] // if (c1 == *p2)
0x0000000000401366 <+46>: je 0x401372 <strings_not_equal+58> // goto label58
0x0000000000401368 <+48>: jmp 0x40138f <strings_not_equal+87> // goto label87
0x000000000040136a <+50>: cmp al,BYTE PTR [rbp+0x0] // if (c1 != *p2)
0x000000000040136d <+53>: nop DWORD PTR [rax] //
0x0000000000401370 <+56>: jne 0x401396 <strings_not_equal+94> // goto label94
0x0000000000401372 <+58>: add rbx,0x1 // p1++;
0x0000000000401376 <+62>: add rbp,0x1 // p2++;
0x000000000040137a <+66>: movzx eax,BYTE PTR [rbx] // c1 = *p1;
0x000000000040137d <+69>: test al,al // if (c1 != '\0')
0x000000000040137f <+71>: jne 0x40136a <strings_not_equal+50> // goto label50
0x0000000000401381 <+73>: mov edx,0x0 // ret = 0
0x0000000000401386 <+78>: jmp 0x40139b <strings_not_equal+99> // goto label99
0x0000000000401388 <+80>: mov edx,0x0 // ret = 0
0x000000000040138d <+85>: jmp 0x40139b <strings_not_equal+99> // goto label99
0x000000000040138f <+87>: mov edx,0x1 // ret = 1
0x0000000000401394 <+92>: jmp 0x40139b <strings_not_equal+99> // goto label99
0x0000000000401396 <+94>: mov edx,0x1 // ret = 1
0x000000000040139b <+99>: mov eax,edx // return ret
0x000000000040139d <+101>: pop rbx
0x000000000040139e <+102>: pop rbp
0x000000000040139f <+103>: pop r12
0x00000000004013a1 <+105>: ret
第2步: 逐句抄写 C 代码, 并且添加对应的 label, 用于 goto:
int strings_not_equal(const char* s1, const char* s2)
{
char* p1 = s1;
char* p2 = s2;
int len1 = string_length(s1);
int len2 = string_length(s2);
int ret = 1;
if (len1 != len2)
{
goto label99
}
char c1 = *p1;
if (c1 =='\0')
{
goto label80;
}
if (c1 == *p2)
{
goto label58;
}
else
{
goto label87;
}
label50:
if (c1 != *p2)
{
goto label94;
}
label58:
p1++;
p2++;
c1 = *p1;
if (c1 != '\0')
goto label50
ret = 0;
goto label99;
label80:
ret = 0;
goto label99;
label87:
ret = 1;
goto label99;
label94:
ret = 1;
goto label99;
label99:
return ret;
}
第3步: 消除用于返回返回值的 goto:
int strings_not_equal(const char* s1, const char* s2)
{
char* p1 = s1;
char* p2 = s2;
int len1 = string_length(s1);
int len2 = string_length(s2);
if (len1 != len2)
{
return 1;
}
char c1 = *p1;
if (c1 =='\0')
{
return 0;
}
if (c1 == *p2)
{
goto label58;
}
else
{
return 1;
}
label50:
if (c1 != *p2)
{
return 1;
}
label58:
p1++;
p2++;
c1 = *p1;
if (c1 != '\0')
goto label50
return 0;
}
第4步: 消除最后的 goto, 消除重复代码, 转为 while 循环:
int strings_not_equal(const char* s1, const char* s2)
{
char* p1 = s1;
char* p2 = s2;
int len1 = string_length(s1);
int len2 = string_length(s2);
if (len1 != len2)
{
return 1;
}
while (true)
{
char c1 = *p1;
if (c1 =='\0')
{
return 0;
}
if (c1 != *p2)
{
return 1;
}
p1++;
p2++;
}
}
一些“定式”的整理
围棋里有定式。反汇编也有定式。
定式1: cmp 和 je/jne
cmp + je: if( A == B) goto C
0x000000000040131b <+0>: cmp BYTE PTR [rdi],0x0 // if (*str==0)
0x000000000040131e <+3>: je 0x401332 <string_length+23> // goto label23
cmp + jne: if (A != B) goto C
0x000000000040132b <+16>: cmp BYTE PTR [rdx],0x0 // if (!(*p == 0))
0x000000000040132e <+19>: jne 0x401323 <string_length+8> // goto label8
更多例子:
0x0000000000401357 <+31>: cmp r12d,eax // if (len1 != len2)
0x000000000040135a <+34>: jne 0x40139b <strings_not_equal+99> // goto label99
0x0000000000401363 <+43>: cmp al,BYTE PTR [rbp+0x0] // if (c1 == *p2)
0x0000000000401366 <+46>: je 0x401372 <strings_not_equal+58> // goto label58
0x0000000000401368 <+48>: jmp 0x40138f <strings_not_equal+87> // goto label87
0x000000000040136a <+50>: cmp al,BYTE PTR [rbp+0x0] // if (c1 != *p2)
0x000000000040136a <+50>: cmp al,BYTE PTR [rbp+0x0] // if (c1 != *p2)
0x000000000040136d <+53>: nop DWORD PTR [rax] //
0x0000000000401370 <+56>: jne 0x401396 <strings_not_equal+94> // goto label94
定式2:test A,A 和 je/jne
test A,A + je: if (A == 0) goto B
0x000000000040135f <+39>: test al,al // if (c1 == '\0')
0x0000000000401361 <+41>: je 0x401388 <strings_not_equal+80> // goto label80
test A, A + jne: if (A != 0) goto B
0x000000000040137d <+69>: test al,al // if (c1 != '\0')
0x000000000040137f <+71>: jne 0x40136a <strings_not_equal+50> // goto label50