新160个CrackMe042-crackme、043-riijj_cm_20041121、044-tsrh-crackme逆向分析
042
无壳
根据字符串搜索可以知道该文件夹下需要一个名为key.dat的文件,里面包含序列号
暴力破解
关键跳转给改成jmp之后发现该程序打不开,其实已经成功了,只是没有成功提示
算法分析
显示获取了序列号和序列号长度,将序列号的前三位都与其长度进行异或,之后再将第一位与0x54异或、第二位与0x4D异或、第三位与0x47异或
之后该字符串的第四位与第一位进行异或并且取代原来的值,第五位与第二位、第六位与第三位,分别异或之后取代原来的值
接下来进行一个循环,将内存地址从405030位置开始存储的数据与之前异或好的序列号的前三位进行循环异或,直到异或结果为0xFF,之后将位置为405030、405031、405032位置的数相乘,乘积与0x2F8BF4判断是否相等,相等则成功
因为这里面只用到了文件中的前三个字符,所以我们这里只求文件中是三个字符的情况,可以根据暴力搜索成功找到两个正确的序列号,复制到key.dat内,如果点击程序没有窗口弹出说明字符串正确
可以写keygen:
n1 = [0x54, 0x4D, 0x47] n2 = [0x1E, 0xBF, 0xA2] n1[0] = n1[0] ^ n2[0] ^ 0x3 n1[1] = n1[1] ^ n2[1] ^ 0x3 n1[2] = n1[2] ^ n2[2] ^ 0x3 serial = '' for i in range(33, 127): for j in range(33, 127): for k in range(33, 127): val0 = i ^ n1[0] val1 = j ^ n1[1] val2 = k ^ n1[2] if val0 * val1 * val2 == 0x2A8BF4: serial = chr(i) + chr(j) + chr(k) print(f"serial: {serial}") print(f"hex: {i:02X} {j:02X} {k:02X}")043
无壳
绕过反反调试
在x32dbg中点击运行发现一直在处于异常状态,存在反反调试
下载专门针对反反调试的插件Releases · x64dbg/ScyllaHide,放到目录x96dbg\release\x32\plugins下,在x32dbg中设置如图,点击运行即可绕过反反调试,直接选择VMProtect模式,再补齐红框内的
发现程序自带的动态链接库已经载入,右键转到相应位置的反汇编,下断点,动态链接库内存储的是真正算法部分,下断点运行,输入测试用例,继续步过分析
暴力破解
向下查看可以找到一个关键跳转,相等则成功,将该跳转给nop掉即可获取破解版本,保存程序后没办法打开破解版程序,这里只演示了源程序在x32dbg中修改关键跳转之后的结果
算法分析
指令 | 全称 | 作用 | 操作方向 |
rep movsd | Repeat Move DoubleWord (4字节) | 从 esi 指向的地址复制 N 个 4字节 数据到 edi | esi → edi |
rep stosd | Repeat Store DoubleWord (4字节) | 向 edi 指向的地址连续写入 N 个 4字节 数据(值来自 eax) | eax → [edi] |
stosw | Store Word (2字节) | 向 edi 指向的地址写入 1 个 2字节 数据(值来自 ax) | ax → [edi] |
stosb | Store Byte (1字节) | 向 edi 指向的地址写入 1 个 1字节 数据(值来自 al) | al → [edi] |
repne scasb | Repeat Not Equal Scan Byte | 扫描 edi 指向的字符串,查找与 al 不同的字符(常用于计算字符串长度) | 比较 [edi] vs al |
先是初始化,获取一个程序自带的字符串,之后再获取用户名和序列号,并对他们两个算长度,之后有四个判断跳转,来验证用户名长度是否大于4,小于15,序列号长度是否大于4小于30
之后经过一个循环将用户名的每一位都对0x62取余数,作为下标在程序自带的字符串中取出字符拼接到一起,全部拼接完成后,与输入的序列号进行判断是否相等,相等则成功
可以写keygen:
seed="fytugjhkuijonlbpvqmcnxbvzdaeqrwtryetdgfkgphonuivmdbxfanqydexzwztqnkcfkvcpvlbmhotyiufdkdnjxuzyqhfstae" username=input("Input username:").strip() result='' for ch in username: result+=seed[ord(ch)%0x62] result+=seed[(ord(ch)%0x62)+1] print(f"result:{result}")044
无壳,有neg
直接查找x32dbg中的字符串没有收获,找不到相关语句和跳转,在调用堆栈中找到相关位置双击跳转到neg附近,将关键跳转给nop掉即可获取去neg程序
暴力破解
直接进行字符串搜索找不到验证算法的核心位置,在程序刚开始的位置向下可以找到获取输入的类似判断输入的用户名长度是否合格的位置,在此处下断点,之后运行分析
向下可以看到几个跳转,直接运行可以发现前两个不能跳转,第三个跳转成功jmp过去程序才显示正确,所以前两个给nop掉,第三个给改成jmp,即可暴力破解
算法分析
序列号前面必须为tsrh-2008-,只有经过第一个函数,函数内部进行一个循环将用户名的每一个字符都经过算法(a+0xC)^(a+0xC-0x11+a+0xC-len),a代表用户名的每个字符的ASCII码值,len代表字符串“tsrh-2008-”的长度,每次循环之后都将获得的十六进制数转换成两个字符拼接到字符串之后“tsrh-2008-”,len随着字符串程度增加而增加
之后将用户名进行循环,每个字符的ASCII码值先+1,之后如果这是第一次循环那用户名的第一位与上次循环得到的字符串的第十三位字符进行异或,接下来判断ASCII码值是否处于A-Z之间,如果小于A则循环+8,直到该字符处于A-Z之间,如果大于Z则循环-3,直到该字符处于A-Z之间将得到的字符拼接到之后,形成真正的序列号
最后经过一个函数的循环对比,判断输入的序列号和真实序列号是否相等,相等则成功,不相等就失败
可以写出keygen:
username = input("Input username: ").strip() seed_bytes = bytearray(b"tsrh-2008-") length = 10 for ch in username: a = ord(ch) idx = (a + 0xC) ^ (a + 0xC - 0x11 + a + 0xC - length) seed_bytes.append(idx) length += 2 seed_str = seed_bytes.decode('latin-1') hex_str = seed_bytes[len('tsrh-2008-'):].hex().upper() print(f"tsrh-2008-{hex_str}") result = '' for i, ch in enumerate(username): b = ord(ch) + 1 if i == 0: b = b ^ ord(hex_str[2]) print(hex_str[2]) while b < ord('A'): b += 8 while b > ord('Z'): b -= 3 result += chr(b) print(f"serial:{result}")前面还有一个函数和异或的运算,没有用到和序列号生成算法没关系,这里不做分析
