
天书夜读
(试读版)
汇编语言是一门本来是很基础很古老的语言,由于它的代码可读性可移植性较差,现在已经很少有人用。但它的优点也是显而易见的,
很高的效率,不受编译器限制的随意性,对硬件的直接操作(保护模式下需要系统支持),以及逆向工程时不可或缺的反汇编调试等。随
着你越来越深入的了解计算机,你会越来越觉得这个古老的东西是最重要的,是那些时髦的编程语言不可比拟的。
我们每天使用的 Windows 内核部分,几乎完全用 C 语言开发。只可惜 MS 并不公开源代码。
虽然如此,却没有谁能阻止你看汇编的代码。MS 对 Windows 既没有加壳加密,也没有花指令,洋洋洒洒上千万行代码,数千精英
程序员的智慧结晶,如今就在你的电脑内。工作结束,夜晚无聊之时,难道不想读一读,这深不可测的浩瀚天书吗?
以前曾经有人把 Windows 的 dll 反汇编后改写为 C 语言的资料。后来又有 WindowsNT 和 2000 的源代码泄漏。不过到如今 vista
都已经发布了。想要自己随心所欲的阅读,还是要自己掌握 Windows 程序的汇编写法吧。获人之鱼,不如师人之渔。
本文知识初浅,并不是能用于破解,反工程,或者编写病毒等的高级技术。仅供读者业余聊以自娱之用.这本书的文字部分主要由楚
狂人编写。部分代码和技术细节由 wowocock 提供.
现在本版本为完整的试读版本,包括完整的 1-3 节。今后如果需要新的章节,将按读者的意见,加入到正式版本中。
楚狂人,wowocock
2007 年 1 月 于 上海

目录
第一节 入手:基本 C 反汇编···················································································································································································3
1-1. 函数与调用栈 ·························································································································································································3
1-2.循环 ········································································································································································································5
1-3.判断与分支·····························································································································································································7
1-4.数组与结构··························································································································································································· 11
1-5.共用体,枚举类型 ···············································································································································································12
1-6.算法的反汇编·······················································································································································································13
1-7.发行版的反汇编 ···················································································································································································15
1-8.汇编反 C 练习·························································································································································································18
第二节 演习:内核代码阅读 ·················································································································································································21
2-1.认识内核代码,新的函数调用方式·······················································································································································21
2-2.尝试反 C 内核代码·················································································································································································23
2-3.寻找需要的信息 ·····················································································································································································25
2-4.了解内核调用的位置 ···········································································································································································27
2-5.自己实现 XP 的新调用,新的函数调用方式······································································································································29
2-6.没有符号表的反汇编 ···········································································································································································31
第三节 实战:反汇编引擎, HOOK 系统调用·························································································································································31
3-1 反汇编引擎 XDE32 之熟悉指令···························································································································································31
3-2 反汇编引擎 XDE32 之具体实现···························································································································································34
3-3 XP 下 HOOK 系统调用 IoCallDriver··················································································································································37
3-4 Vista 下 IofCallDriver 的跟踪 ····························································································································································39
3-5 Vista 下实现 Hook IofCallDriver ······················································································································································ 41
3-6 总结与展望·····························································································································································································44

第一节 入手:基本 C 反汇编
1-1. 函数与调用栈
函数和堆栈的关系密切,这是因为:我们通过堆栈把参数从外部传入到内部。此外,我们在堆栈中划分区域来容纳函数的内部变量。
调用 push 和 pop 指令的时候,寄存器 esp 用于指向栈顶的位置。栈顶总是栈中地址最小的位置。push 执行的结果,esp 总是减少。
pop 则增加。
C 语言所写的程序中,堆栈用于传递函数参数。这时称为调用栈。
写一个简单的函数如下:
void myfunction(int a,int b)
{
int c = a+b;
}
这是标准的 C 函数调用方式.其过程是:
1) 调用方把参数反序的压入堆栈中。
2) 调用函数。
3) 调用方把堆栈复原。
而被调用函数需要做以下一些事情:
1) 保存 ebp. ebp 总是被我们用来保存这个函数执行之前的 esp 的值。执行完毕之后,我们用 ebp 恢复 esp.同时,调用我们的上
层函数也用 ebp 做同样的事情。所以我们之前先把 ebp 压入堆栈。返回之前弹出,避免 ebp 被我们改动。
2) 保存 esp 到 ebp 中。
上面两步的代码如下:
push ebp ;保存 ebp,并把 esp 放入 ebp 中
;此时 ebp 与 esp 同。
mov ebp,esp ;都是这次函数调用时的栈顶。
3) 在堆栈中腾出一个区域用来保存局部变量。这就是常说的所谓局部变量在栈空间中。方法为把 esp 减少一个数值。这样等于压
入了一堆变量。日后要恢复时,只要把 esp 恢复成 ebp 中保存的数据就可以了.
4) 保存 ebx,esi,edi 到堆栈中。函数调用完后恢复。这是一个编程规范。
对应的代码如下:
sub esp,0cch ;把 esp 往上移动一个范围,等于在堆栈中放出一片新
;的空间用来存局部变量.
push ebx ;下面保存三个寄存器:ebx,esi,edi,这也是 C 规范.
push esi

push edi
5) 把局部变量区域初始化成 0cccccccch。0cccccccch 实际是 INT 3.这是一个中断指令。因为局部变量不可能被执行,如果执行
了必然程序有错,这时发生中断来提示开发者。这是 VC 编译 DEBUG 版本的特有操作。
相关代码如下:
lea edi,[ebp-0cch] ;本来是要 mov edi,ebp-0cch,但是 mov 不支持-操作。所
;以对 ebp-0cch 取内容,而 lea 把内容的地址也就是 ebp
;-0cch加载到 edi 中.目的是把保存局部变量的区域(从
;ebp-0cch开始的区域)初始化成全部 0cccccccch.
mov ecx,33h
mov eax,0cccccccch
rep stos dword ptr [edi] ;拷贝字符串
6) 然后做函数里应该做的事情。参数的获取是 ebp+8 字节为第一个参数,ebp+12 为第二个参数,依次增加。ebp+4 字节处是
要返回的地址。
7) 恢复 ebx,esi,edi ,esp,ebp,最后返回。
代码如下:
pop edi ;恢复 edi,esi,ebx
pop esi
pop ebx
mov esp,ebp ;恢复原来的 ebp 和 esp,让上一个调用的函数正常使用.
pop ebp
ret
为了简单起见,我的函数没有返回值。如果要返回值,函数应该在返回之前,把返回值放入 eax 中。外部通过 eax 得到返回值。
所以用 VC2003 编译 Debug 版本,反汇编代码如下:
void myfunction(int a,int b)
{
push ebp ;保存 ebp,并把 esp 放入 ebp 中。此时 ebp 与 esp 同。
mov ebp,esp ;都是这次函数调用时的栈顶。
sub esp,0cch ;把 esp 往上移动一个范围,等于在堆栈中放出一片新
;的空间用来存局部变量.
push ebx ;下面保存三个寄存器:ebx,esi,edi,这也是 C 规范.
push esi
push edi
lea edi,[ebp-0cch] ;本来是要 mov edi,ebp-0cch,但是 mov 不支持-操作。所
;以对 ebp-0cch 取内容,而 lea 把内容的地址也就是 ebp
;-0cch加载到 edi 中.目的是把保存局部变量的区域(从
;ebp-0cch开始的区域)初始化成全部 0cccccccch.
mov ecx,33h
mov eax,0cccccccch
rep stos dword ptr [edi] ;拷贝字符串
int c = a+b;

mov eax,dword ptr [a] ;简单的相加操作.这里从堆栈中取得从外部传入的参数。那么
add eax,dword ptr[b] ;a 和 b 到底是怎么取得的呢,通过 ida 反汇编可以看到,其实
;这两条指令是 mov eax, [ebp+8] ,add eax, [ebp+0Ch],参数是
;通过 ebp 从堆栈中取得的。这里看到的是 VC 调试器的显示结
;果,为了阅读方便直接加上了参数名。
mov dword ptr[c],eax
}
pop edi ;恢复 edi,esi,ebx
pop esi
pop ebx
mov esp,ebp ;恢复原来的 ebp 和 esp,让上一个调用的函数正常使用.
pop ebp
ret
而这个函数的调用方式是:
mov eax,dword ptr[b] ; 把 b,a 两个参数压入堆栈
push eax
mov ecx,dword ptr[a]
push ecx
call myfunction ; 调用函数 myfunction.
add esp,8 ; 恢复堆栈.
这样一来,函数调用的过程就很清楚了。
1-2.循环
下面我把函数改得复杂一点,增加了一个循环,来看反汇编的结果:
int myfunction(int a,int b)
{
int c = a+b;
int i;
for(i=0;i<50;i++)
{
c = c+i;
}
return c;
}
前面的反汇编结果和前一节的一样了,现在从 for 的地方开始反汇编,结果如下:
for(i=0;i<50;i++)
00412BC7 mov dword ptr [i],0
00412BCE jmp myfunction+39h (412BD9h)
评论2