逆向练习(六)Andrénalin.3/4
最近沉迷挖洞,然后七月份干活去了。回来接着学学底层的东西,赶紧把Andrénalin这个东西的后面两个给弄掉,然后玩新程序去。
Andrénalin.3
老样子,看程序,看pe
爆破
还是和之前的一样,搜关键词,找跳转,爆破
算法
从爆破断点向上找起始点,下断,运行后输入123开始调试
一直到出现函数,然后来看这些函数
上面这一长条和2简直一模一样,就是在做字符判断和字符提取,然后下面有一个循环从00402026回到00401F68,由此可以知道,这和2一样是一个循环加码的过程。
接下来就是在这个范围内逐步调试查找关键变化的点,下断,然后记录下变化值。
上面整个过程可以概述为:
00401F70-00401FAA:I4、ordinal、varval,提取字符
add A:1-> 31+A=3B-> ;
00401FB4-00402026 free、fornext,循环
我们输入的是123,所以输出结果就应该是31+A 32+A 33+A=3B3C3D -> ;<=
OK,验证一下,最终我们输出的结果是在00402050进行序列码比较,相同就成功,不同就失败,所以上面的00402036就是序列码本身,因为这个是从程序中获得的字符串,push的两个中必有一个是我们的上面计算的结果。
所以,我们最终的目的是要求这个:kXy^rO|*yXo*m\\kMuOn*+进行逆向计算的结果。这里有一个问题,这串符号多了一个\,因为\本质上是一种转义字符,当单独使用时会导致编译器跳过它将其和k一起识别为k,所以需要在前方再添加一个\使得编译器可以将\认识为\而不是直接跳过。因此要去内存部分看它实际的ASCII编码是多少。这个从我们反向计算的结果也能看出来:aNoThEr oNe cRRaCkEd !如果懂英语的话就会发现这里多了个R。这些特殊英语的写法可能有形状相似数字字母变化、元音重写延长读音,但很少出现辅音重写造成断读的情况,除非是个日本人写的拗音。
无论什么语言中遇到了斜杠和0这些极有可能带有特殊意义的字符都要多一个心眼,防止转义或跳过。
最后,解码程序:
def final_key(serial):key = []for i in serial:k = ord(i) - 10k1 = chr(k)key.append(k1)return keyuser_input = input('serial: ')
output = final_key(user_input)
print('key: ', ''.join(output))
Andrénalin.4
先说结论,这个程序用OD分析比较麻烦,它的判断是和按键一起进行的,需要不断的取消断点、输入、下断,关键点只要一断就会导致程序完全点不动,遇到这种操作麻烦的程序时,最好使用某些特殊反编译工具让我们能直接查看源码,就和前面的delphi一样。不要头铁硬分析。
爆破
爆破方法非常简单,拉进OD,看字符,随便点一个进去,修改跳转就搞定。
算法
由于它没有壳子,这里直接使用VB decompiler
由上面图示分析可知,程序总共有13个按钮和4个计时器,其中数字1234...分别对应command1234...,*->10、0->11、#->12、删除->13
查看一下这几个按钮,基本是一样的,区别只在于输入的字符。结合OD分析时内存窗口不断运行可以猜测,所有的程序运行、判断、算法全都在计时器里。
经过查看下面的计时器,可以发现不同计时器的结构也是一样的。第一部分代码调用的是command13,所以这个是删除字符后的计时判断,我们不需要这个。因此着重分析第二部分。
这里涉及几个VB函数
- Ctsr 字符串
- Clng 长整型
- Mid(string, start[, length]) 从某字符串的起始位置start返回长度length的字符串
- $ 以ASCII码处理
- Asc 转换为ASCII格式
- Hex 转换为16进制字符
- Left 从字符串左侧返回指定长度的字符
- Val 返回数字直到非数字字符停止
然后我们来分析一下这个循环计算:
var_44 = Form.Text1.Text //输入字符
For var_24 = 1 To Len(var_44) Step 1 //循环读取字符If var_F8 Then //如果当前不为0var_34 = var_34 & Hex$((Asc(Mid$(CStr(var_44), CLng(var_24), 1)) + Val(CStr(Left(var_44, 1))))) //计算Next var_24 //1++GoTo loc_0040492F //返回循环
End If //结束
计算部分:从输入的字符中当前位置取1位(也就是本位),转换为ASCII值,将这个值和输入字符左边第一位数字相加,最后转换成16进制格式并循环拼接。
然后这个循环拼接出来的字符串要规定的字符串相等:
If (var_34 = "0817E747D7AFF7C7F82836D74RR7A7F7E7B7C7D826D81KE7B7C") ThenSet var_54 = Form1.Label3var_D0 = var_54var_54.Caption = "REGISTRIERT"
End If
所以从算法我们可知,这个var_34的字符串一定是个16进制字符串,所以只要它里面存在其它特殊符号和F以后的字母全都不符合。从OD里面把所有的字符串给复制出来,然后写个脚本来判断一下:结果就一个。
def is_hex(s):try:int(s, 16) return Trueexcept ValueError:return Falsewith open('D:/Andrnalin.4/str.txt', 'r') as file:for line_num, line in enumerate(file, 1):line = line.strip() if not line: continueif is_hex(line):print(f"Line {line_num}: '{line}' 是")else:print(f"不是")
然后我们就需要使用这个字符来进行逆向计算,注意一下,现在这个字符有51位,是单数,数量不对,所以应该去掉一个被填充的字符0,然后看第一位数字81=129,这就是Val(CStr(Left(var_44, 1))),然后循环用后面的数字减去这个值并转换为ascii字符,最后拼接起来:
hexlist = "0123456789ABCDEF"
enc = "0817E747D7A7D7C7F82836D74747A7F7E7B7C7D826D817E7B7C"# 用10进制计算
def hexstr_to_dec(s):a = hexlist.index(s[0])b = hexlist.index(s[1])return a * 16 + b# 提取密钥的第二、三位并转换
key_str = enc[1:3]
key_value = hexstr_to_dec(key_str)# 计算左边的数字
kb = "0123456789*#"
base_value = None
for x in range(1, 10):for y in range(0, 10):if y + x * 10 + ord(kb[x]) == key_value:base_value = x * 10 + ybreakif base_value is not None:breakprint(f"开头数字: {base_value}")# 逆向计算
dec = ""
for i in range(1, len(enc), 2): # 从8开始,每2字符处理key_str = enc[i:i+2]key_value = hexstr_to_dec(key_str)p = chr(key_value - base_value) # ASCII转换dec += pprint(f"serial: {dec}")
总结
看OD不能只盯着CPU窗口,内存和寄存器同样重要,有时候关键的变化就在这些地方,因为不是所有的工具都会在CPU窗口显示出字符。脑子里要随时记住各种编码转换的形式和结果,要对长度敏感。另外,OD、dbg、ida这些工具只是通用但并不是万能的,有时候针对性的反汇编工具可以帮大忙,毕竟好看方便的界面可以大大提升效率。不要迷信个人技术(真有技术干嘛不自己手搓电脑),善用工具本身就是技术能力的一部分。