首先,为了方便我们后面的逆向,我们选择一个简单的编译开发环境。
这里,我选择TC2.0
TC2.0是一个集成的开发环境,它集成了以下文件:
TC.EXE 集成编译器
EMU.LIB 8087仿真库 ,作用:系统将同8087仿真器连接,得到exe文件,只能用于程序的编译。
CS.LIB 不同模式运行库 ,作用:对于windows下程序的运行模式有很多种,普通用户模式、管理员模式,不同的运行模式,可能决定用户获得的不同的信息,该库决定了程序在不同模式下的运行机制。
C0S.OBJ 不同模式的共同启动代码
MATHS.LIB MATH(C、H、L、M、S).lib是编译器的五种存储模式,该maths.lib规定了tc2.0编译器的存储模式,连接成.exe的前提条件之一。
GRAPHICS.LIB C语言图形编程 图形库文件:
因为在编写C的图形程序时,用到了字体,文字库等库文件,生成的exe文件必须与这些库文件放在一起,程序才能正确跑起来,为了方便,我将这些文件都连接到了GRAPHICS.LIB这个文件中,以后编译好自己的程序后,只需一个exe文件就可以运行了,不需要别的什么文件了。
**OK,我们最简易的集成环境搭建完成,接下来,我们将在这里写出.c,编译链接成.exe文件
**
在我们程序运行之前,系统大多会为我们程序将要使用的内存资源进行分配,然后运行我们的程序工作,结束后程序在对分配给我们的资源进行释放。
资源分配程序->我们写的程序->资源释放程序
TC2.0开发环境下资源分配程序与资源释放程序都C0S.OBJ中。
C0S.OBJ中代码结构类似:
start;
运行资源分配程序
调用main函数
运行资源释放程序
end;
于是,我们重写了C0S.OBJ。
红色部分是为我们.c程序申请栈空间与数据存储空间相当于资源分配程序
蓝色部分等价于main函数调用,调用的是s:这个地址后面储存的指令。相当于运行main函数
绿色部分为结束我们的程序进程,cpu的使用权交付给cpu,相当于资源释放程序好,我们写一个c程序编译连接必要的库和启动代码。
c程序如下:
这里面我们没用main函数,因为我们自己重写了启动代码,所以有没有main函数已经无所谓了,我们这个代码链接的时候会自动的插入启动代码的S:后面。被当做main函数调用。
ds:0X100默认保存为数组的内存空间(这里搞了好久才发现,具体原因未知,留个疑问,待以后解决吧)
0xb800:0000为显存的地址,把字符放到这里会自动显示到显示屏上,单位2Byte,结构为(字符,属性),即低位存字符,高位存属性,0x2是字符的属性,绿色。
一个简单的输出KISS程序就写好了。
我们运行试一试:
看,程序的第一行显示了绿色的KISS,成功。
于是,重点来了:(这里才是重点!!!)我们用windows自带的debug反汇编一下,得到如下汇编指令:
我把这些指令全部翻译了一遍。
0B57:0000 B84F0B MOV AX,0B4F
0B57:0003 8ED0 MOV SS,AX
0B57:0005 BC8000 MOV SP,0080
0B57:0008 E80500 CALL 0010 ;调用jazzi函数
0B57:000B B8004C MOV AX,4C00
0B57:000E CD21 INT 21 ;资源分配资源销毁 的启动程序
0B57:0010 55 PUSH BP
0B57:0011 8BEC MOV BP,SP ;保存进入函数前的栈顶地址
0B57:0013 83EC02 SUB SP,+02 ;进入函数后,int i=0中的i内存保存在以原栈顶为栈底的栈中
0B57:0016 56 PUSH SI ;si在子函数中值可能改变,所以原si值入栈
0B57:0017 33F6 XOR SI,SI ;si=0
0B57:0019 C746FE0000 MOV WORD PTR [BP-02],0000 ;给i=0
0B57:001E 33F6 XOR SI,SI ;si=0
0B57:0020 EB32 JMP 0054
0B57:0022 8BDE MOV BX,SI ;等价于temp=i
0B57:0024 81C30001 ADD BX,0100 ;等价于temp=0x100+i
0B57:0028 8A07 MOV AL,[BX] ;把ds:[0x100+i]中的值赋给AL,即‘K’'I''S''S'ascll码的十
;六进制
0B57:002A 50 PUSH AX ;+++++++++++++保存AX的值,因为以下代码要改变AX
0B57:002B 8BC6 MOV AX,SI ;ax=i
0B57:002D D1E0 SHL AX,1 ;ax=2*i
0B57:002F 99 CWD ;将ax变为双字节,高16位都为0
0B57:0030 050000 ADD AX,0000 ;ax=i*2+0
0B57:0033 81D200B8 ADC DX,B800 ;显存的段地址
0B57:0037 8BD8 MOV BX,AX ;把ax的低16位赋给bx,即bx存着显存的最初偏移地址0000
0B57:0039 8EC2 MOV ES,DX ;把显存的段地址赋给ES
0B57:003B 58 POP AX ;+++++++++++++恢复AX的值
0B57:003C 26 ES: ;改变[]的段地址罢了
0B57:003D 8807 MOV [BX],AL ;因为显存中低位装着字符,所以把字符赋给这个显存地址
0B57:003F 8BC6 MOV AX,SI ;-----------------------------------
0B57:0041 D1E0 SHL AX,1 ;与上面套路相同
0B57:0043 99 CWD
0B57:0044 050100 ADD AX,0001
0B57:0047 81D200B8 ADC DX,B800 ;ax=i*2+1
0B57:004B 8BD8 MOV BX,AX
0B57:004D 8EC2 MOV ES,DX ;------------------------------------
0B57:004F 26 ES:
0B57:0050 C60702 MOV BYTE PTR [BX],02 ;只不过这里是吧02这个值赋给显存地址的高位,02表示绿色
0B57:0053 46 INC SI ;等价于i++
0B57:0054 83FE04 CMP SI,+04 ;比较si是否为4,如果是4则退出
0B57:0057 7CC9 JL 0022 ;如果si小于4,转移到22处
0B57:0059 5E POP SI ;原si值出栈,si恢复刚开始的值
0B57:005A 8BE5 MOV SP,BP ;将子函数的栈中元素全部出栈,栈顶恢复为原栈顶的位置
0B57:005C 5D POP BP ;栈底恢复为原栈底的位置
0B57:005D C3 RET ;程序交付启动函数处理后续