本代码只作为学习用,没有任何实用价值。限于水平,反编译的分析结果也不够彻底,供初学者参考。
先看C代码:
#include
代码不太多,没有写注释,核心代码只有一句: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