作者:Cornelis Frank April 10, 2000
工具:
GCC 2.7.2.3以上
NASM Version 0.97以上
一、开始
1.什么都没有的C程序
test.c
------------
int main () {
}
编译:
gcc -c test.c
ld -o test -Ttext 0x0 -e main test.o
objcopy -R .note -R .comment -S -O binary test test.bin
或者
gcc -c test.c
ld test.o -o test.bin -Ttext 0x0 -e main -oformat binary
然后可以用下面这个命令查看二进制文件的反编译结果(NASM语法):
ndisasm -b 32 test.bin
显示:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 C9 leave
00000004 C3 ret
很简单,第一列是指令地址,第二列是指令的byte code,第三列是反编译出来的指令
GCC只能产生32位的代码,因此不能用来写引导程序(要用16位的AS86)。
2.使用局部变量
为什么叫局部变量呢?反编译一下就知道了,同上,写一个test.c:
int main () {
int i; /* declaration of an int */
i = 0x12345678; /* hexadecimal */
}
编译:
gcc -c test.c
ld -o test -Ttext 0x0 -e main test.o
objcopy -R .note -R .comment -S -O binary test test.bin
或
gcc -c test.c
ld -o test.bin -Ttext 0x0 -e main -oformat binary test.o
反编译:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 83EC04 sub esp,byte +0x4
00000006 C745FC78563412 mov dword [ebp-0x4],0x12345678
0000000D C9 leave
0000000E C3 ret
原来它先把栈指针ESP减4(sizeof(int)),然后把0x12345678放到栈中了,在这个函数中使用了寄存器EBP并且未改变过值。实际上,它指向在栈中的局部变量,其引用的内容就是0x12345678!所以这就叫局部变量了?嘿嘿。
用于存放局部变量的栈空间通常称为local stack frame,而上文中的寄存器EBP就被称为the frame pointer.
把test.c中的
int i;
i = 0x12345678;
改为
int i = 0x12345678;
也能得到相同的结果。
2.使用可读写的全局变量
为什么叫全局变量呢?同样来一下反编译,再写一个test.c:
int i; /* declaration of global variable */
int main () {
i = 0x12345678;
}
编译:
gcc -c test.c
ld -o test -Ttext 0x0 -e main test.o
objcopy -R .note -R .comment -S -O binary test test.bin
反编译:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 C705101000007856 mov dword [0x1010],0x12345678
-3412
0000000D C9 leave
0000000E C3 ret
我们看到,这里有个:0x1010,它是内存中的某一地址,这是因为编译器LD缺省时page-aligns the
data segment(数据段页对齐?)。给LD加上-N参数后就变这样了:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 C705100000007856 mov dword [0x10],0x12345678
-3412
0000000D C9 leave
0000000E C3 ret
这里0x10指向了代码结束后的内存。
当我们用以下shell命令编译时
gcc -c test.c
ld -o test -Ttext 0x0 -Tdata 0x1234 -e main -N test.o
objcopy -R .note -R .comment -S -O binary test test.bin
反编译结果又是这样子:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 C705341200007856 mov dword [0x1234],0x12345678
-3412
0000000D C9 leave
0000000E C3 ret
现在全局变量被存放到指定的0x1234里去了。也就是说如果LD使用参数-Tdata的话,我们就给出了数据段的地址,否则代码段将 located right after the code.
如果变量存放在main函数外面预留的可访问数据段,那么就叫它全局的了。
4.使用只读全局变量
再像上面那样建个test.c:
const int c = 0x12345678;
int main () {
}
编译:
gcc -c test.c
ld -o test.bin -Ttext 0x0 -e main -N -oformat binary test.o
反编译:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 C9 leave
00000004 C3 ret
00000005 0000 add [eax],al
00000007 007856 add [eax+0x56],bh
0000000A 3412 xor al,0x12
可以看到,它与可读写的全局变量不同,它被直接写在数据段了(5-A反编译的结量没有意义,因为它是数据段)。
我们用如下shell命令查看test.o:
objdump --disassemble-all test.o
将会在屏幕上看到:
test.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 55 pushl %ebp
1: 89 e5 movl %esp,%ebp
3: c9 leave
4: c3 ret
Disassembly of section .data:
Disassembly of section .rodata:
00000000 <c>:
0: 78 56 js 58 <main+0x58>
2: 34 12 xorb $0x12,%al
这里可以更加清楚地看到,<c>被放在了rodata里,是只读的。
把test.c改一下:
int i = 0x12345678;
const int c = 0x12346578;
int main () {
}
用objdump得到:
test.o: file format elf32-i386
Disassembly of section .text:
00000000 <main>:
0: 55 pushl %ebp
1: 89 e5 movl %esp,%ebp
3: c9 leave
4: c3 ret
Disassembly of section .data:
00000000 <i>:
0: 78 56 js 58 <main+0x58>
2: 34 12 xorb $0x12,%al
Disassembly of section .rodata:
00000000 <c>:
0: 78 56 js 58 <main+0x58>
2: 34 12 xorb $0x12,%al
可以看到:int i 在data section 而 constant c 在只读的 read-only data section.