Start
0x01 信息收集
查壳,跟CrackMe06是同个作者
同样拉进DarkDe4反编译,看看做了哪些修改:
事件为:
- CancellaClick
- AboutClick
- RegisterzClick
- AgainClick
默认的程序中是没有AgainClick这个控件的,随便输入点Nome和Codicei点击注册也并未出现,说明这里应该是注册成功才会出现该控件
0x02 爆破与算法破解
拖进IDA,先看到RegisterzClick的控件做了什么操作:
同样的,导入Delphi相关签名表,然后导出MAP
拖进OD,先将MAP文件导入,由于没有开启PIE,所以可以直接定位到该回调函数的地址:
随便输入点东西:
第一步GetText为Codice的值,第二步调用了VarLong,然后提示You MUST insert a valid long Integer,说明Codice必须是整数型字符串。
重新输入:
第二个jle指令用来判断Codice是否小于等于0,如果小于等于0的话会弹出,Please Code > 0
往下第二次GetText获取的是Nome的值,并且调用了004429A8的函数,调用完之后往下有个je指令,该指令刚好跳过了SetVisible的函数,说明4429A8应该就是做注册码验证的。
跟进0x004429A8:
存在两个局部变量:
一个是Nome的值为test123
一个是Codice的值转成hex -> hex(int(12345,10)),这个位置的值往上跟可以看到是通过ValToLong函数生成的
往下判断Nome的长度是否大于4,那么就可以得到Nome的长度大于等于5就行了
往下进入循环,这里有两层循环
先看到第一次循环的内层循环,第一次循环,取第一个和最后一个字符串的ASCII码相乘,再乘上edi的值,此时edi的值为0,ebx的值也为0,这里ebx应该是存放最终的结果的,第一次的内层循环由于edi的值一直为0,所以此时所有的结果皆为0。
但是在外层的循环,edi的值也未发生改变,那么此时不管内层循环做任何的操作,edi的值一直为0,最终得到的ebx的结果也是一直为0。
回到IDA中:
可以看到v3应该就是ebx,它是该函数传入的第一个参数:
回到函数调用处,可以看到传入为int类型的变量,dword_445830,但是这个变量的唯一赋值的地方是在提示输入insert Integer Codice的位置,那么这里就需要让其执行。
回到OD中,重新执行该回调函数:
nop掉jcc指令,往下看到dword_445830的值还是0:
回到IDA,跟进SelectAll函数,观察会对Codice的长度进行判断,判断他的长度是否小于等于5,如果条件成立,可以看到返回值还是0。这里应该就是问题所在,重新输入Codice:
再回到44298A,此时edi中已经有值了。
那么基本上就得出该注册算法的成立条件:
首先EDI中的值是受Codice控制的:
输入1234567 -> 0x9D2
输入234567 -> 0x691
然后最终与Nome绑定的Codice又是通过EDI与Nome各种算法后得到的
通过调试可以整理整个计算过程:
Codice = '234567'EDI = 0x37Bfor i in range(1, len(Codice)): EDI += (ord(Codice[i]) % 0x11 + 1) * ord(Codice[i-1])
再次回到4429A8这个函数,重新整理两层循环:
整个计算过程为:
Nome = '12345'res = []for i in Nome: index = len(Nome) - 1 while index >= 0: res.append(ord(i) * ord(Nome[index])) index -= 1
每一次相乘得到的结果:
可以发现:
0x31 | 0x32 | 0x33 | 0x34 | 0x35 | |
---|---|---|---|---|---|
0x31 | 0x961 | 0x992 | 0x9C3 | 0x9F4 | 0xA25 |
0x32 | 0x992 | 0x9C4 | 0x9F6 | 0xA28 | 0xA5A |
0x33 | 0x9C3 | 0x9F6 | 0xA29 | 0xA5C | 0xA8F |
0x34 | 0x9F4 | 0xA28 | 0xA5C | 0xA90 | 0xAC4 |
0x35 | 0xA25 | 0xA5A | 0xA8F | 0xAC4 | 0xAF9 |
简化得到:
Nome = '12345'sum = 0EDI = 0x691res = []for i in Nome: for j in Nome: sum += ord(i)*ord(j)*EDI
往下看到IDA:
v8 = abs(v4) % 666666;v15 = v15 % 0x50 + v15 / 0x59 + 1;if ( v8 == v15 ) LOBYTE(v8) = 1;else v8 = 0;
最终处理与比较,那么根据这个就可以写出脚本,这里是无法直接通过Nome算出Codice的,所以需要去爆破,其次,默认的程序无论Nome和Codice是否匹配都是无法注册成功的。
0x03 注册机编写与程序修正
先根据之前的分析写出注册码的生成算法,这里我们知道Codice
是纯数字的,而Nome
是数字或字母都可以,所以这里选择爆破Codice
,然后Codice
是大于等于6
位数的正整数,所以初始化的num
为100000
,然后+1
进行碰撞就行了,这里未追究速度的问题,所以采用单线程进行爆破,速度会比较慢
Nome = ''Codice = ''def generate_name(self): self.Nome = ''.join(random.sample(string.ascii_letters + string.digits, random.randint(6,10)))def generate_serial(self): num = 100000 while True: self.Codice = str(num) sum = 0x37b for i in range(1, len(self.Codice)): sum += (ord(self.Codice[i]) % 0x11 + 1) * ord(self.Codice[i-1]) tmp = 0 for i in self.Nome: for j in self.Nome: tmp += ord(i)*ord(j)*sum res1 = int(self.Codice, 10) % 0x50 + int(self.Codice, 10) // 0x59 + 1 res2 = abs(tmp) % 0xA2C2A if (res1 == res2): break else: num += 1 continue
程序修正的目地是让EBX中的值不为0,所以,需要将这两处的jcc指令修改:
第一处的指令可以直接nop掉,不过因为是输入正确的Nome和Codice,所以这个位置最终是想要调用到WindowDesigner
这个函数,并且不想弹出Dialog,所以需要跳转到0x442F70
这个位置,这里需要了解jcc指令硬编码相关的知识:
可以看到所有的JCC指令的硬编码都是两个字节,第一字节代表指令类型,第二个字节代表的是跳转的位置,这里的74 37
,后面的0x37
指的是从当前指令的下一条指令的EIP开始加上0x37
就是要跳转的位置,此时下一条指令的EIP为0x442F66
,加上0x37
,就是0x442F9D
的位置,那么我们现在需要跳转到0x442F70的位置,就是用后面的0x70 - 0x66
就行了,得到0x0A,那么修改第二个字节为0A就行了:
后面的jmp指令直接nop就可以了
最终可以得到:
ps:其实双击指令位置改就行hhhh,没必要自己计算硬编码
Comments NOTHING