- C++反汇编与逆向分析技术揭秘(第2版)
- 钱林松 张延清
- 2132字
- 2021-09-27 17:05:17
4.5 一次算法逆向之旅
前面我们了了解各种运算的基础知识,接下来进行一次简单的逆向之旅:通过分析程序的反汇编代码来了解程序的算法,巩固所学知识。
这里要分析的程序为简单的CrackMe程序,是用VC++编写的控制台程序。程序功能是验证输入密码,显示密码验证结果(密码为命令行输入方式),如图4-5所示。
图4-5 CrackMe程序的运行结果
为了尽量减少分析的工作量,程序中没有设置任何错误检查,只有密码加密与密码检查。输入正确的密码后,程序将会显示“密码正确”的字样。本次将使用IDA进行静态分析。使用IDA加载分析程序后,IDA会直接定位到main()函数的入口处,省去了查找main()函数的过程。如果使用OllyDbg进行动态分析,需要先查找并定位到main函数的入口处。分析程序为Release版,如代码清单4-24所示。
代码清单4-24 CrackMe程序分析片段1(Release版)
var_10= byteptr-10h ;从var_10到var_3都是连续的1字节大小的局部变量 var_F= byte ptr-0Fh var_E= byte ptr -0Eh var_D= byte ptr -0Dh var_C= byte ptr -0Ch var_B= byte ptr -0Bh var_A= byte ptr -0Ah var_9= byte ptr -9 var_8= byte ptr -8 var_7= byte ptr -7 var_6= byte ptr -6 var_5= byte ptr -5 var_4= byte ptr -4 var_3= byte ptr -3 argc= dword ptr 4 argv= dword ptr 8 envp= dword ptr0Ch
代码清单4-24为CrackMe程序在main()函数中的参数以及局部变量定义。在IDA中,正偏移为参数,负偏移为局部变量,详细讲解见第7章。从标号var_3到var_10的变量都占byte(1字节)的连续局部变量,将其暂时看作数组,找到起始标号var_10的地址,按*键转换14个字节数据为数组。转换数组后将标号名称var_10重命名,按N键修改名称为“charNumber14”,如图4-6所示。
图4-6 数据转换数组
按Esc键返回反汇编视图窗口,在反汇编视图窗口的反汇编代码中,所有引用var_3~var_10的地方都被替换成了数组访问方式,如代码清单4-25所示。
代码清单4-25 CrackMe程序分析片段2(Release版)
charNumber14= byte ptr -10h ;转换后的数组 ; main()函数的三个参数:argc命令个数、argv命令行信息、envp环境变量信息 argc= dword ptr 4 argv= dword ptr 8 envp= dword ptr0Ch sub esp,10h ; 取参数argv数据放入ecx中 mov ecx,[esp+10h+argv] mov al,1 mov [esp+10h+charNumber14+6],al ;将数组charNumber14第6项赋值为al,即1 mov [esp+10h+charNumber14+7],al ;将数组charNumber14第7项赋值为al,即1 ;即edx中保存为argv[1] mov edx,[ecx+4] ;在ecx中保存的参数为argv,根据argv类型,这里为 ;argv[1]操作 ;即edx中保存为argv[1] mov [esp+10h+charNumber14+0Ah],al ;将数组charNumber14第10项赋值为al,即1 mov al, byte ptr[esp+10h+argc] ;将al赋值为命令行参数个数argc push ebx ;保存环境 mov bl,al ;将bl赋值为al,即命令行参数个数argc push esi ;保存环境 dec bl ;对bl执行减等于1操作,等同argc减1 push edi ;保存环境 or [edx],bl ;在edx中保存为argv[1],这步操作为argv[1] ;[0]|=argc-1 ① mov edx,[ecx+4] ;在edx中保存为argv[1] mov [esp+1Ch+charNumber14], 77h ;将数组charNumber14第0项赋值为0x77 mov [esp+1Ch+charNumber14+1], 76h ;将数组charNumber14第1项赋值为0x76 xor [edx+1],bl ;在edx中保存为argv[1],这步操作为argv[1] ;[1]^=argc-1 ② mov dl,6 ;修改dl为数值6 imul dl ;对dl做有符号乘法,乘以al,al中保存的数据为命令 ;行参数个数 ;结果存入al中 mov esi,[ecx+4] ;在esi中保存为argv[1] sub al,dl ;使用al减等于dl mov [esp+1Ch+charNumber14+2],0Cah ;将数组charNumber14第2项赋值为0xCA mov [esp+1Ch+charNumber14+3],0F9h ;将数组charNumber14第3项赋值为0xF9 imul byte ptr[esi+2] ;在esi中保存argv[1],此句指令为argv[1] ;[2]*al,al中保存的值 ;为命令行参数个数乘以6后,再减去6。转换为al = ;argv[1][2] * (argc - 1) * 6 mov [esi+2],al ;esi+2中的数据等于al,即argv[1][2]=argv[1] ;[2]*(argc-1)*6 ③ mov esi,[ecx+4] ;在esi中保存为argv[1] mov [esp+1Ch+charNumber14+4],0A8h ;将数组charNumber14第4项赋值为0xA8 mov [esp+1Ch+charNumber14+5], 0Ch ;将数组charNumber14第5项赋值为0x0C movsx eax, byte ptr[esi+2] ;取argv[1][2]数据存到eax中 cdq ;扩展高位到 edx and edx,3 ;使用eax扩展后的高位edx与3进行位与运算 mov [esp+1Ch+charNumber14+8],0Feh ;将数组charNumber14第8项赋值为0xFE add eax,edx ;使用eax加扩展高位edx mov [esp+1Ch+charNumber14+9],0DBh ;将数组charNumber14第9项赋值为0xDB sar eax,2 ;将eax右移动2位,此数可套用除法公式,移动次数为 ;2的幂,因此除数为4转换后变为:eax=argv[1][2]/4 mov [esi+3],al ;将al赋值到esi+3,即argv[1][3]=argv[1][2]/4 ④ mov eax,[ecx+4] ;使用eax保存argv[1] mov [esp+1Ch+charNumber14+0Bh],0E0h;将数组charNumber14第11项赋值为0xE0 mov [esp+1Ch+charNumber14+0Ch],0FBh;将数组charNumber14第12项赋值为0xFB mov dl,[eax+4] ;在eax中保存argv[1],即:argv[1][4]数据存入dl mov [esp+1Ch+charNumber14+0Dh],0 ;将数组charNumber14第13项赋值为0x00 ;到此所有的数组成员都被赋值
在代码清单4-25中,对数组charNumber14中的每一项进行赋值,并对命令行参数进行一些计算,这就是在对我们输入的密码信息进行加密。charNumber14数组中保存的就是加密后的密码,分析后得到charNumber14中:“0x77、0x76、0xCA、0xF3、0xA8、0x0C、0x01、0x01、0xFE、0xDB、0x01、0xE0、0xFB、0x00”,共14个字节的数据。这个数组为密码比较数组,这里保存的数据可以为密码加密信息,因此可知密码长度,以及加密后的密文字符串信息。代码清单4-25为CrackMe程序部分的反汇编信息,继续向下分析程序,获取完整的加密过程,如代码清单4-26所示。
代码清单4-26 CrackMe程序分析片段3(Release版)
通过代码清单4-26对命令行参数argv[1]的层层运算,最终得到一个加密后的字符串,与程序中的密文进行比较,如果转换结果一样,表示密码正确,反之密码错误。
在转换过程中,遇到了没有接触过的对2取模运算。2的取模运算相对特殊,由于取模就是求余,所有有符号数对2求余只有3种结果:“-1”“0”“1”。因此编译器进行了优化,对十六进制数0x80000001做位与运算,无论这个数字是多少,只会保留最高位与最低位。如果数字为奇数,则最低位必然为1,会被保留下来,同理,符号位也会被保留。
使用跳转指令JNS判断正负标记位SF。edx和十六进制数0x80000001做位与运算后,如果为负数,则结果为0x80000001,由于存放编码方式为补码,而这个数字并不是补码的-1,需要进行补码转换。如果是正数,则不存在转换问题,直接跳过负数处理部分即可。分析代码清单4-25和代码清单4-26的加密过程可以得出,加密运算步骤共有13步。对这13步加密步骤进行分析并还原后可以得出整个加密过程,如代码清单4-27所示。
代码清单4-27 还原成源码的加密过程
代码清单4-27为CrackMe程序加密算法还原后的代码,使用了大量的位运算。由于这些运算不可逆,所以无法推算回正确的密码。这里给出程序的正确密码:www.51asm.com。读者可自己将CrackMe程序的反汇编代码翻译成对应的C++代码,使用此密码进行程序验证。