160CM-017
从打包文件中下载下来的016与014的附件是一样的,不知道是不是打包的时候弄错了,因此跳过016了。
017这个题前后分析过程花了不少时间,由于“vb decompiler”反编译的结果比较差,另外时间比较碎片,分析一点过一段时间又忘了。中间还犯了一个低级错误,把VB变量体的数据位置记成5~8位了,结果中间有段时间OD跟踪函数的结果,郁闷的不行,怎么都对不上。
1.爆破
爆破很简单,直接搜索字符串,就能定位到关键跳转的位置。
00404E30 /0F84 AD000000 je BJCM30A.00404EE3
把这行代码nop掉,就可以了。
2.算法分析
因为这个是VB程序,还是先用“vb decompiler”反编译看看,结果V11.1反编译出来的hexfunc代码完全看不懂。不得已又用V10.1试了下,发现低版本的结果正常很多。看来有时候还是不能盲目认为新版本好啊,对比结果如下:
先不管这个,接着分析,反编译对程序各子函数和事件函数的名称和地址识别都是没问题的,可以很清晰的知道要去分析哪里。
2.13 Command1_Click()分析
我们需要先分析一下主事件函数,这回V11.1立功了,V10.1反编译的代码连调用的子函数都没识别出来。从V11.1反编译的代码来看,前面一段代码是反调试检测代码,如果调试过程在这段代码区域暂停了的话,就会报错。接着检查了一下输入字符串的长度以及输入字符串中的字符是否相同。再后面调用了两次hexfunc函数,并以第二次调用函数的结果进行判断,来确定注册码是否正确。
2.2 hexfunc()分析
接下来在分析子函数之前,我们需要先了解一些VB子函数的基础知识。VB的用户定义函数分两种,一种叫“子过程”(sub),这是不带返回值的,有点类似C里面的void;另外一种叫子函数(function),这个是带返回值的,可以直接将函数赋值给变量。
另外我们还需要了解一下VB编译程序的参数压栈方式,是从右到左的,对于function,第一个压栈的参数是返回值,然后是参数n,n-1,…,1,最后是函数本身的指针,可以称为this。在此以hexfunc函数的调用代码为例进行说明,在函数调用前,一共push了5个参数,根据“vb decompiler”反编译识别出来的函数声明Public Function hexfunc(firsthex, secondhex, operation)
可以看出,这是一个子函数,共有3个参数,因此压栈的5个参数从前到后一次是返回值、参数3、参数2、参数1、this。另外,VB自定义函数调用后,一般紧跟着一个异常处理函数,分析的时候可以直接忽略。
参数识别清楚了,后面的分析才能更轻松。由于“vb decompiler”对hexfunc这个函数的反编译结果不太好,这里切换到IDA中进行分析。IDA6.8中不能自动识别这个自定义函数,需要先定位到函数代码区,右键选择“create function”创建就可以。然后按F5生成伪代码。
在IDA中,会自动将函数的内部变量统一列出在函数代码前,如下图:
从图中可以看出,arg_0~arg_10这些变量是函数调用前push到堆栈中的,其变量地址为ebp+XX,其他的地址为ebp-XX的是内部变量。为了便于读懂程序,建议将arg_0~arg_10分别改名为this,firsthex,secondhex,operation,return。
这些名称改好以后,切换到伪代码页面,会发现伪代码页的变量名也自动改好名字了,看起来就舒服多了(也会经常出现伪代码中变量名不自动更新的情况,不知道什么原因)。在这里可以看到有一长串(v9+1788)的,看起来有点懵,不知道是什么。这里其实右键,选择“hide casts”就会发现,代码变成了v10 = (*(v9 + 1788))(this, firsthex, &v46);
,这样看起来就好很多了,再根据汇编代码中的call dword ptr ds:[eax+0x6FC]
就可以发现,1788就是0x6FC的十进制表示,这一行其实就是hextodec的调用。
将伪代码进行适当的简化,变量更名,可以得到比较好读懂的代码,如下图:
从代码中可以看出,hexfunc函数中主要分别计算了一下firsthex和secondhex的hextodec值,然后根据operation的不同执行不同的操作。对于这道题,只用到了“”和“=”这两种操作,因此只要看这两部分就可以了。
结合OD跟踪分析,不难得出operation为`时,将firsthex和secondhex相乘,然后返回值;operation为
=`时,判断firsthex和secondhex是否相等,然后返回判断结果。
2.3 hextodec()分析
接着分析hextodec函数,“vb decompiler”反编译出来的代码还可以,因此直接看vb代码。由函数名和大致的代码静态分析可以初步确定,这个函数应该就是将十六进制字符串转换成10进制数值,与CInt("&h" + hexvalue)
实现的功能是一样的。
Public Sub hextodec(hexvalue, decresult) '403D30
loc_00403DB9: var_18 = UCase$(hexvalue) '转换为大写字母
loc_00403E20: For var_30 = Len(var_18) To 1 Step -1
loc_00403E2C:
loc_00403E2E: If var_88 Then '识别不准确,var_88应该是for循环的实际技术,为0就退出
loc_00403E38: var_1C = var_1C + 0001h
loc_00403E46: var_1C = var_1C
loc_00403E49: If var_1C >= 3 Then
loc_00403E4E: var_20 = var_20 * 16 '实际为var_20=16
loc_00403E57: var_20 = var_20
loc_00403E5A: End If
loc_00403E8D: If (var_30 = Len(var_18)) Then
loc_00403EB1: var_48 = Mid$(var_18, Len(var_18), 1)
loc_00403ECA: If Asc(var_48) >= 48 Then '48为“0”的ASCII码
loc_00403ED0: If Asc(var_48) > 57 Then GoTo loc_00403EDE '57为“9”的ASCII码
loc_00403ED8: Asc(var_48) = Asc(var_48) - 0030h '30h即48,得到数值字符的大小
loc_00403EDC: GoTo loc_00403F06
loc_00403EDE: End If
loc_00403EE2: If Asc(var_48) < 65 Then GoTo loc_0040409F
loc_00403EEC: If Asc(var_48) > 70 Then GoTo loc_0040409F
loc_00403EF8: Asc(var_48) = Asc(var_48) - 0041h
loc_00403F02: Asc(var_48) = Asc(var_48) + 000Ah '得到十六进制A~F字母对应数值大小
loc_00403F1C: var_78 = decresult
loc_00403F2B: GoTo loc_00404093
loc_00403F30: End If
loc_00403F4E: var_34 = Mid$(var_18, CLng(var_30), 1)
loc_00403F67: var_60 = Len(var_18)
loc_00403F82: If (var_30 = Len(var_18)) Then
loc_00403F8E: If Asc(var_48) >= 48 Then
loc_00403F94: If Asc(var_48) > 57 Then GoTo loc_00403FA2
loc_00403F9C: Asc(var_48) = Asc(var_48) - 0030h
loc_00403FA0: GoTo loc_00403FC2
loc_00403FA2: End If
loc_00403FA6: If Asc(var_48) < 65 Then GoTo loc_00403FF3
loc_00403FAC: If Asc(var_48) > 70 Then GoTo loc_00403FF3
loc_00403FB4: Asc(var_48) = Asc(var_48) - 0041h
loc_00403FBE: Asc(var_48) = Asc(var_48) + 000Ah
loc_00403FF1: ecx = decresult + Asc(var_48)
loc_00403FF3: End If
loc_00403FFD: If Asc(var_34) >= 48 Then
loc_00404003: If Asc(var_34) > 57 Then GoTo loc_00404042
loc_0040400B: Asc(var_34) = Asc(var_34) - 0030h
loc_0040401A: Asc(var_34) = Asc(var_34) * var_20 '高一位的乘16
loc_00404031: var_78 = decresult
loc_00404040: GoTo loc_00404093
loc_00404042: End If
loc_00404046: If var_58 < 65 Then GoTo loc_0040409F
loc_0040404C: If var_58 > 70 Then GoTo loc_0040409F
loc_00404054: Asc(var_34) = Asc(var_34) - 0041h
loc_00404061: Asc(var_34) = Asc(var_34) + 000Ah
loc_00404075: Asc(var_34) = Asc(var_34) * var_20
loc_0040407F: var_60 = Asc(var_34)
loc_0040409D: ecx = decresult + Asc(var_34)
loc_0040409F:
loc_004040B1: Next var_30
loc_004040B7: GoTo loc_00403E2C
loc_004040BC: End If
loc_004040C1: GoTo loc_004040CD
loc_004040CC: Exit Sub
loc_004040CD: 'Referenced from: 004040C1
loc_0040410B: Exit Sub
End Sub
2.4 综合分析
从2.1~2.3的分析我们可以看出,注册码的要求大致如下:
- 字符串长度必须≥5;
- 字符串中的字符不能都相同;
- 字符串首字符的ASCII码值×字符串的长度与字符串所有字符的ASCII码值的和相等。
根据上述规则,我们可以构建不同的注册码,比如bacbb、546464646等,经测试均可通过注册。
3.总结
- 了解掌握了VB编译代码对自定义函数参数的传递规律。VB的sub和function的区别,目前我只能借助“vb decompiler”来区分。网上有些说法说是可以根据函数调用后有没有使用返回值的赋值操作或是函数内部有没有对返回值进行赋值的操作来判断。为了验证这个,自己特意下载安装了一下VB6.0(第一次知道vs 2015中的VB其实是.net),构造了以下几个函数编译了一下。发现sub函数调用后也访问了返回值,function函数内部也可以不对返回值赋值。IDA生成的伪代码也函数都是带返回值的,只是操作参数数量sub比function少一个。但是这个应该不是关键,其实不管sub还是function动态跟踪一下也都能确定各个参数的含义。
Private Sub Command1_Click()
Dim nValue As Integer, nValue2 As Integer
Call hextodec1("FF", nValue)
MsgBox (nValue)
End Sub
Private Sub Command2_Click()
Dim nValue As Integer, nValue2 As Integer
nValue2 = hextodec2("FF", nValue)
MsgBox (nValue2)
End Sub
Private Sub Command3_Click()
Dim nValue As Integer, nValue2 As Integer
Call hextodec3("FF", nValue)
MsgBox (nValue)
End Sub
Public Sub hextodec1(ByVal hexvalue As String, ByRef decresult As Integer)
decresult = CInt("&h10" + hexvalue)
End Sub
Public Function hextodec2(hexvalue As String, ByRef decresult As Integer)
decresult = CInt("&h10" + hexvalue)
hextodec2 = decresult
End Function
Public Function hextodec3(hexvalue As String, ByRef decresult As Integer)
decresult = CInt("&h10" + hexvalue)
End Function
-
逆向的时候需要多试试不同的工具,有时候一个工具对某个程序效果不好的时候,换个工具可能就解决了很多问题。
-
关于OD的一点使用技巧:OD分析VB函数的时候,由于VB变量体变量的原因,很多堆栈地址无法直接显示其指向的变量的值,之前都是傻乎乎的在数据区Ctrl+G手动输入地址跳转到位置后,再Ctrl+G手动输入地址进行跳转,这次发现原来可以在地址上右键选择“数据窗口跟随地址”直接跳过去,在数据区也可以直接选中某个地址同样右键进行跳转,方便了很多。
-
有个VB库函数__vbaVarVargNofree
,网上找了个遍也没确定这个函数的具体功能,但是对这道题的整个逆向分析过程没多大影响。