一个简单C程序的反编译。

本代码只作为学习用,没有任何实用价值。限于水平,反编译的分析结果也不够彻底,供初学者参考。

先看C代码:

#include #include #define MAX_LEN 10 int Register( const unsigned char *key, const unsigned char *value ); int main( const int argc, const char *argv[] ) { unsigned char key[MAX_LEN]; unsigned char value[MAX_LEN]; unsigned char *tmp; fflush(stdin); printf("input key(max %d):", MAX_LEN - 2); tmp = fgets(key, MAX_LEN, stdin); key[strlen(tmp) - 1] = '/0'; fflush(stdin); printf("input value(max %d):", MAX_LEN - 2); tmp = fgets(value, MAX_LEN, stdin); value[strlen(tmp) - 1] = '/0'; if (Register(key, value) == 0) { printf("register ok/n"); } else { printf("register ng/n"); } return 0; } int Register( const unsigned char *key, const unsigned char *value ) { const unsigned char SECRET_TAB[10] = { 0x10, // 0 0x1A, // 1 0x1F, // 2 0x40, // 3 0x07, // 4 0x30, // 5 0x09, // 6 0x1E, // 7 0x0A, // 8 0x20, // 9 }; int idx; int key_len = strlen(key); unsigned char really_code[MAX_LEN]; for (idx = 0; idx < key_len; ++idx) { really_code[idx] = (key[idx] ^ SECRET_TAB[idx % 10]) % 26 + 'A'; } really_code[key_len] = '/0'; return strncmp(really_code, value, key_len); }

代码不太多,没有写注释,核心代码只有一句:really_code[idx] = (key[idx] ^ SECRET_TAB[idx % 10]) % 26 + 'A';

编译选项:/Ox 最大限度的最适化。

下面是IDAPRO反编译的结果,只分析两个函数main和Register:

.text:00409E94 ; int __cdecl main(int argc,const char **argv,const char *envp)
.text:00409E94 _main           proc near               ; CODE XREF: ___tmainCRTStartup+15Ap
.text:00409E94
.text:00409E94 var_1E          = byte ptr -1Eh               ; 02h byte 没有用到,不知道有什么用处
.text:00409E94 var_1C          = byte ptr -1Ch               ; 0Bh byte 其实是和下面的1字节连在一起的,还是12字节
.text:00409E94 var_11          = byte ptr -11h               ; 01h byte
.text:00409E94 var_10          = byte ptr -10h               ; 0Ch byte
.text:00409E94 var_4           = dword ptr -4                ; 04h byte 临时变量,供防止缓冲区溢出使用
                                                             ; 请注意上面的字节分配,编译器考虑了内存对齐的问题,分配的大小在程序中不一定会全部用到
.text:00409E94 argc            = dword ptr  8                ; main函数的第1个参数
.text:00409E94 argv            = dword ptr  0Ch              ; main函数的第2个参数
.text:00409E94 envp            = dword ptr  10h              ; main函数的第3个参数,尽管我们没有设置,系统也会设置
.text:00409E94
.text:00409E94                 push    ebp                   ; 入栈ebp
.text:00409E95                 mov     ebp, esp              ; 把esp保存到ebp
.text:00409E97                 sub     esp, 1Ch              ; 在堆栈上空出1Ch字节的空间
.text:00409E9A                 mov     eax, dword_40C284     ; 和防止堆栈溢出有关,dword_40C284是个根据时间和其他东西算出来的一个值,具体要看调用main之前被调用的方法
.text:00409E9F                 xor     eax, ebp              ; 异或运算,结果保存在eax
.text:00409EA1                 mov     [ebp+var_4], eax      ; 把结果保存在临时变量,因为eax后面要被重写
; 以下是程序正式的代码
.text:00409EA4                 call    sub_4010CD            ; 注意这里,eax作为指针指向一块数据区
.text:00409EA9                 push    eax                   ; FILE * 文件指针入栈
.text:00409EAA                 call    _fflush               ; 调用fflush方法,清除缓冲区
.text:00409EAF                 push    8                     ; 入栈8
.text:00409EB1                 push    offset aInputKeyMaxD  ; "input key(max %d):" ; 入栈一个字符串指针
.text:00409EB6                 call    sub_4015A9            ; 这里就是调用printf("input key(max %d", 8),这里提示入力最大8个字符
.text:00409EBB                 call    sub_4010CD            ; 同上面,eax作为指针指向一块数据区
.text:00409EC0                 push    eax                   ; FILE * 文件指针入栈
.text:00409EC1                 lea     eax, [ebp+var_1C]     ; 让eax的值是ebp+var_1C,其实是把eax作为指针使用,寻址到ebp+var_1C地址
.text:00409EC4                 push    0Ah                   ; int 入栈10,这里给fgets的参数是10
.text:00409EC6                 push    eax                   ; char * 入栈指向ebp+var_1C的指针,准备用来接收输入的数组的大小是12,这是看头部的内存分配得来的
.text:00409EC7                 call    _fgets                ; 按照_cdecl规则调用,fgets(char *, 10, FILE *),返回值在eax中
.text:00409ECC                 add     esp, 18h              ; 恢复堆栈
;下面是strlen宏
.text:00409ECF                 lea     ecx, [eax+1]          ; eax是fgets的返回值,取eax指向的char *的第2个字符的位置保存在ecx中
                                                             ; 这里不需要考虑返回值长度为0的情况,直接读取第2个字符,详细请看fgets的说明
.text:00409ED2
.text:00409ED2 loc_409ED2:                             ; CODE XREF: _main+43j
.text:00409ED2                 mov     dl, [eax]             ; 取eax指向的char *的第一个字符
.text:00409ED4                 inc     eax                   ; eax加1,因为是指针,所以结果是指向了下一个元素的位置
.text:00409ED5                 test    dl, dl                ; 判断取得字符是否是0,就是字符串的结尾'/0'
.text:00409ED7                 jnz     short loc_409ED2      ; 不是0,循环
.text:00409ED9                 sub     eax, ecx              ; 到这里,dl已经是0了,eax指向了0后面的位置,ecx是第2个字符的位置,相减的结果是取eax指向的数组的长度
;到这里strlen宏结束
.text:00409EDB                 mov     [ebp+eax-1Dh], dl     ; 这里代码的意思要看内存来计算的,堆栈是从大到小的,即FF->00
                                                             ; 数组开始地址是var_1C,数组在堆栈中是顺着堆栈方向,第一个元素比最后一个元素更靠近0地址
                                                             ; 假设数组长度占用了n个字节,数组在内存中的布局如下:注意,var_1C本身是负值
                                                             ; ebp+var_1C+n-1 是最后1个字符
                                                             ; ebp+var_1C + 0 是第1个字节
                                                             ; ebp-1Dh => ebp-1Ch-1 => ebp+var_1C-1 再加字符串的长度,就是ebp+var_1C+n-1
                                                             ; 结果是把最后1个字符(不是/0)设置为0,假设字符串是ABC,n=3,那么就是第3个字符设置为0,最后结果是AB,看起来吃掉了1个字符。
                                                             ; fgets的参数是10,程序提示printf(input key(max %d", 8),不考虑内存对齐的情况,用来接收输入的数组的长度可能是12,但是最小也应该是是10,
                                                             ; 差2的原因请自己分析fget函数的特点。其实ebp+var_1C+n-1处的字符是个换行符A,后面跟着字符串结束符/0,这里是把这个换行符设置为/0,让字符串在这里结束。
; 从这里开始的分析和上面的类似,同样的地方就不分析了
.text:00409EDF                 call    sub_4010CD
.text:00409EE4                 push    eax             ; FILE *
.text:00409EE5                 call    _fflush
.text:00409EEA                 push    8
.text:00409EEC                 push    offset aInputValueMaxD ; "input value(max %d):"
.text:00409EF1                 call    sub_4015A9
.text:00409EF6                 call    sub_4010CD
.text:00409EFB                 push    eax             ; FILE *
.text:00409EFC                 lea     eax, [ebp+var_10]      ; 请注意,这里第2个字符串的起始位置是ebp+var_10
                                                              ; 这里第1个字符的地址ebp+var_1C,搬着手指数一数,可以看出分配了2块12字节的内存
                                                              ; 第1块从(-04,-10],第2块(-10,-1C]
.text:00409EFF                 push    0Ah             ; int
.text:00409F01                 push    eax             ; char *
.text:00409F02                 call    _fgets
.text:00409F07                 add     esp, 18h
;下面是strlen宏
.text:00409F0A                 lea     ecx, [eax+1]
.text:00409F0D
.text:00409F0D loc_409F0D:                             ; CODE XREF: _main+7Ej
.text:00409F0D                 mov     dl, [eax]
.text:00409F0F                 inc     eax
.text:00409F10                 test    dl, dl
.text:00409F12                 jnz     short loc_409F0D
.text:00409F14                 sub     eax, ecx
;到这里strlen宏结束
.text:00409F16                 mov     [ebp+eax+var_11], dl  ; 这里的计算类似于上面的计算,把最后一个字符设置为/0
                                                             ; ebp+var_11=>ebp+var_10-1=>ebp_var_10+n-1,正好是最后一个字符
.text:00409F1A                 lea     eax, [ebp+var_10]     ; 参数入栈,eax作为指针使用,eax中的值是ebp+var_10
.text:00409F1D                 push    eax                   ; 入栈参数
.text:00409F1E                 lea     ecx, [ebp+var_1C]     ; ecx作为指针使用,ecx中的值是ebp+var_1C
.text:00409F21                 call    sub_409DE4            ; 调用sub_409DE4方法,该方法中做了一个简单的加密运算
                                                             ; 这里看出sub_409DE4只push了一个参数,而且只是个地址,相当于是个指针参数,
                                                             ; 上面输入的另一个值,保存在ecx寄存器中,在sub_409DE4中进行访问,
                                                             ; 从这里可以看到,高级语言中反编译后已经看不清子函数原来的样子了,
                                                             ; 最起码调用子函数时参数数目已经无法从堆栈上去对应了
.text:00409F26                 test    eax, eax              ; 看返回值是否为0
.text:00409F28                 pop     ecx                   ; 这时候堆栈上是什么,我没有分析,似乎只是为了平衡call sub_409DE4前的push操作
.text:00409F29                 jnz     short loc_409F32      ; 不是0,注册失败
.text:00409F2B                 push    offset aRegisterOk ; "register ok/n" ; 为printf准备参数
.text:00409F30                 jmp     short loc_409F37      ; 跳到调用printf地方
.text:00409F32 ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
.text:00409F32
.text:00409F32 loc_409F32:                             ; CODE XREF: _main+95j
.text:00409F32                 push    offset aRegisterNg ; "register ng/n" ; 为printf准备参数
.text:00409F37
.text:00409F37 loc_409F37:                             ; CODE XREF: _main+9Cj
.text:00409F37                 call    sub_4015A9            ; 调用printf
.text:00409F3C                 pop     ecx                   ; 同样的问题,这时候堆栈上是什么,我没有分析,似乎只是为了平衡call sub_4015A9前的push操作
; 和防止堆栈溢出有关
.text:00409F3D                 mov     ecx, [ebp+var_4]
.text:00409F40                 xor     ecx, ebp
.text:00409F42                 xor     eax, eax
.text:00409F44                 call    sub_401000
.text:00409F49                 leave                         ; 应该是和enter指令对应的,用于堆栈平衡和帧指针的恢复,对于enter指令和leave指令,具体要看IA-32指令集的说明
.text:00409F4A                 retn                          ; main结束,跳回到调用main的地方
.text:00409F4A _main           endp


.text:00409DE4 sub_409DE4      proc near               ; CODE XREF: _main+8Dp
.text:00409DE4
.text:00409DE4 var_24          = dword ptr -24h              ; 04h byte
.text:00409DE4 var_20          = dword ptr -20h              ; 04h byte
.text:00409DE4 var_1C          = byte ptr -1Ch               ; 0Ch byte
.text:00409DE4 var_10          = byte ptr -10h               ; 注意2 注意1和注意2直接是一段地址连续的字节,共0Ah个字节
.text:00409DE4 var_F           = byte ptr -0Fh
.text:00409DE4 var_E           = byte ptr -0Eh
.text:00409DE4 var_D           = byte ptr -0Dh
.text:00409DE4 var_C           = byte ptr -0Ch
.text:00409DE4 var_B           = byte ptr -0Bh
.text:00409DE4 var_A           = byte ptr -0Ah
.text:00409DE4 var_9           = byte ptr -9
.text:00409DE4 var_8           = byte ptr -8
.text:00409DE4 var_7           = byte ptr -7                 ; 注意1
.text:00409DE4 var_4           = dword ptr -4                ; 04h byte 临时变量,供防止缓冲区溢出使用
                                                             ; 请注意上面的字节分配,编译器考虑了内存对齐的问题,分配的大小在程序中不一定会全部用到
.text:00409DE4 arg_0           = dword ptr  8                ; 传入的唯一的参数
.text:00409DE4
.text:00409DE4                 push    ebp                   ; 保存前面的栈帧
.text:00409DE5                 mov     ebp, esp              ; 保存本次的esp,供恢复用
.text:00409DE7                 sub     esp, 24h              ; 在堆栈上空出24h的字节
.text:00409DEA                 mov     eax, dword_40C284     ; 这里和main中一样,防止缓冲区溢出
.text:00409DEF                 xor     eax, ebp
.text:00409DF1                 mov     [ebp+var_4], eax
.text:00409DF4                 mov     eax, [ebp+arg_0]
.text:00409DF7                 push    ebx                   ; 这里真正的代码开始了,入栈ebx
.text:00409DF8                 mov     ebx, ecx              ; ecx此刻的值是main函数中设置好的,就是指向main.ebp+var_1C的数组指针
.text:00409DFA                 mov     [ebp+var_24], eax     ; 把临时变量ebp+var_24作为指针,把eax的值(main.ebp+var_10)放入ebp+var_24
.text:00409DFD                 push    esi                   ; 入栈esi
.text:00409DFE                 mov     eax, ebx              ; eax的值设置为main.ebp+var_1C
.text:00409E00                 push    edi                   ; 入栈edi
.text:00409E01                 mov     [ebp+var_10], 10h     ; 从这里开始,给10个临时变量赋值,对应C语言其实是定义了一个局部变量的数组,并且赋初值
.text:00409E05                 mov     [ebp+var_F], 1Ah
.text:00409E09                 mov     [ebp+var_E], 1Fh
.text:00409E0D                 mov     [ebp+var_D], 40h
.text:00409E11                 mov     [ebp+var_C], 7
.text:00409E15                 mov     [ebp+var_B], 30h
.text:00409E19                 mov     [ebp+var_A], 9
.text:00409E1D                 mov     [ebp+var_9], 1Eh
.text:00409E21                 mov     [ebp+var_8], 0Ah
.text:00409E25                 mov     [ebp+var_7], 20h
;下面是strlen宏
.text:00409E29                 lea     ecx, [eax+1]
.text:00409E2C
.text:00409E2C loc_409E2C:                             ; CODE XREF: sub_409DE4+4Dj
.text:00409E2C                 mov     dl, [eax]
.text:00409E2E                 inc     eax
.text:00409E2F                 test    dl, dl
.text:00409E31                 jnz     short loc_409E2C
.text:00409E33                 sub     eax, ecx
;到这里strlen宏结束
.text:00409E35                 mov     edi, eax              ; 把strlen返回值保存在edi
.text:00409E37                 xor     esi, esi              ; esi清零
.text:00409E39                 test    edi, edi              ; 检查edi是否是0
.text:00409E3B                 jle     short loc_409E70      ; 有符号判断,<=0,跳转loc_409E70
.text:00409E3D                 mov     [ebp+var_20], ebx     ; 临时变量ebp+var_20的值等于main.ebp+var_1C,即临时变量ebp+var_20是个字符指针,指针的值是main.ebp+var_1C
.text:00409E40                 lea     eax, [ebp+var_1C]     ; eax的值是ebp+var_1C,即eax是个字符指针,指针的值是ebp+var_1C,注意:不是main.ebp_var_1C,是本函数的ebp_var_1C
.text:00409E43                 sub     [ebp+var_20], eax     ; 这里是用ebp+var_20中保存的值和eax中保存的值做减法,结果是两个地址间的差,
                                                             ; 这里详细分析一下,main中的一个地址A和本函数中的一个地址B,A和B具有相对于所在函数同样的偏移,这里都是1C,
                                                             ; 所以这里B-A的差就是,两个函数的开始地址之间的差,这里更有意义的是两个变量之间的差,假设这两个变量都是数组,
                                                             ; 这里实际隐含了这样一种情况,main函数中的一个数组需要传递到本函数中读取,此时,编译器并没有复制整个数组,而
                                                             ; 是把数组的地址告诉了本函数,本函数通过数组的地址 + B(调用函数)和A(本函数)间的差 + 偏移来访问数组中的每个元素,
                                                             ; 目的就是减少不必要的内存复制,提升运行时候的速度。
.text:00409E46
.text:00409E46 loc_409E46:                             ; CODE XREF: sub_409DE4+8Aj
.text:00409E46                 mov     eax, esi              ; esi现在是0,这里相当于eax清零
.text:00409E48                 cdq                           ; 把eax的符号位符号扩展到edx,这里就是edx清零
.text:00409E49                 push    0Ah                   ; 入栈0Ah
.text:00409E4B                 pop     ebx                   ; 出栈到ebx,就是ebx=0Ah
.text:00409E4C                 idiv    ebx                   ; 有符号除,被除数的高32位在edx,低32位在eax,除数是ebx,做了除法以后,余数在edx,商在eax
.text:00409E4E                 lea     ecx, [ebp+esi+var_1C] ; esi是循环变量,每次加1,直到和edi(前面用strlen取得的字符串长度)相等
                                                             ; 循环的第1次,esi=0,就是把ebp+var_1C这个地址放入eax,以后每次esi加1,就是循环读取ebp+var_1C字符串的每个元素的地址放入ecx
.text:00409E52                 push    1Ah                   ; 入栈1Ah
.text:00409E54                 pop     ebx                   ; 出栈到ebx,就是ebx=1Ah
.text:00409E55                 movzx   eax, [ebp+edx+var_10] ; 取得ebp+edx+var_10的内容放入eax(ebp+edx+var_10中取得的是1个字节,0扩展为4字节后保存到eax),
                                                             ; edx是esi循环变量被10整除后的余数,循环计数从0到0Ah,就是ebp+var_10开始的10个字节依次放到eax中
.text:00409E5A                 mov     edx, [ebp+var_20]     ; 注意:[ebp+var_20]中的内容是main函数和本函数的堆栈差值,把这个值放入edx
.text:00409E5D                 movzx   edx, byte ptr [edx+ecx] ; 这里先看ecx的内容,ecx中放的是本函数中的ebp+esi+var_1C,esi是循环变量,意思就是依次取本地的ebp+var_1C开始的esi个字节,
                                                             ; 那么edx+ecx的意思是地址指向了main函数中的main.ebp+var_1C,依次取得main.ebp+var_1C开始的esi个字节,0扩展为4字节后保存到edx中
.text:00409E61                 xor     eax, edx              ; eax的内容是本函数中的ebp+var_10,这里就是ebp+var_10开始的esi个字节依次和main.ebp+var_1C开始的esi个字节做xor操作,
                                                             ; 结果保存在eax中
.text:00409E63                 cdq                           ; 把eax的符号位符号扩展到edx
.text:00409E64                 idiv    ebx                   ; 有符号除,ebx的值是1A,被除数的高32位在edx,低32位在eax,除数是ebx,做了除法以后,余数在edx,商在eax
.text:00409E66                 add     dl, 41h               ; 这行代码要结合上面的idiv ebx考虑,idiv以后,余数在edx中,所以dl=dl+'A',其实就是edx=edx+'A'
                                                             ; 再结合ebx的值(1Ah)考虑,这个代码翻译成高级语言就是 eax 模 26 + 'A'
                                                             ; eax是个数组指针,假设叫X,edx也是个数组指针,假设叫Y,那么就是 X[idx] = X[idx] 异或 Y[idx]
                                                             ; 所以上面的代码用C表示就是:(X[idx] ^ Y[idx]) % 26 + 'A',但是上面还有一段对Y的下标运算符idx以0Ah为基数做模运算的操作
                                                             ; 把C代码再进一步就是:Z[idx] = (X[idx] ^ Y[idx % 10]) % 26 + 'A' 假设ebp+var_1C在程序中对应的变量名是Z,
                                                             ; 注意这里是本函数的ebp+var_1C
.text:00409E69                 inc     esi                   ; 循环变量加1
.text:00409E6A                 cmp     esi, edi              ; 比较循环变量和edi,edi中保存的是strlen的长度,就是通过ecx得到的数组的长度
.text:00409E6C                 mov     [ecx], dl             ; [ebp+var_1C+esi] = 前面运算的结果,esi是循环变量,把运算结果保存在ebp+var_1C数组的每个元素中
.text:00409E6E                 jl      short loc_409E46      ; 这里是有符号比较,如果esi比edi小(SF<>OF),跳到循环的开始,继续
.text:00409E70
.text:00409E70 loc_409E70:                             ; CODE XREF: sub_409DE4+57j
.text:00409E70                 push    edi             ; size_t ; 入栈字符串的长度
.text:00409E71                 push    [ebp+var_24]    ; char * ; 入栈第一个参数
.text:00409E74                 lea     eax, [ebp+var_1C]
.text:00409E77                 push    eax             ; char * ; 入栈本地的ebp+var_1C
.text:00409E78                 mov     [ebp+edi+var_1C], 0      ; 这里是对本地的[ebp+var_1C]操作,edi是main通过ecx传入的数组的长度,通过上面的代码分析,可以得出,本地的[ebp+var_1C]的长度和edi是相同的
                                                                ; 所以这里就是把ebp+var_1C指向的数组的最后一个元素赋值为0
.text:00409E7D                 call    _strncmp                 ; strncmp(ebp+var_1C, ebp+var_24, edi),结果在eax中,作为本函数的返回值
                                                                ; 写成C语言就是: return strncmp(ebp+var_1C, ebp+var_24, edi);
; 和防止堆栈溢出有关
.text:00409E82                 mov     ecx, [ebp+var_4]
.text:00409E85                 add     esp, 0Ch
.text:00409E88                 pop     edi
.text:00409E89                 pop     esi
.text:00409E8A                 xor     ecx, ebp
.text:00409E8C                 pop     ebx
.text:00409E8D                 call    sub_401000
.text:00409E92                 leave
.text:00409E93                 retn                          ; 结束,返回main
.text:00409E93 sub_409DE4      endp

你可能感兴趣的:(asm,c,byte,input,编译器,file,代码分析)