【crackmes.one】———— mothlight
Language: c/c++ | Arch: x86-64 | Platform: Windows |Tech: keygenme,FNV-1a,djb2,CRC32,Base64,RC4,TEA

程序行为

它会提示你输入名称和序列号,并对长度和格式做了限定,接下来开始静态分析
验证逻辑分析
先看一下strings列表是否有作者文档里写的The moth lands. Lantern CMO{........}

然而并没有,那就一步一步分析
进入到main函数

这个程序的输入输出函数是通过WriteFile/ReadFile搭配GetStdHandle实现的
对main函数简单分析一下,发现主要逻辑在54行的if语句

条件中除了关于长度的判断还有一个函数,它的作用是把序列号四位一组转换成16进制数
stringTohex

图中三个子函数用于把三组字符串转换成十六进制

这里又有一个子函数,它是将char转为16进制
__int64 __fastcall sub_40153C(int n48)
{
if ( n48 >= '0' && n48 <= '9' )
return n48 - '0';
if ( n48 >= 'A' && n48 <= 'F' ) //'A'的ASCII码为65,减去15得到10,也就是十六进制A,下面小写字母同理
return (n48 - 55);
if ( n48 < 'a' || n48 > 'f' )
return 0xFFFFFFFFLL;
return (n48 - 87); //小写字母
}
关键代码是v5 = v3 | (16 * v5);第一眼可能看不懂,需要翻译一下。
从二进制角度看v5 * 16,这里需要把16转换成2^4,也就是把v5左移4位。
这四位就是0了,或运算就可以把v3搬到这四位,也就顺利完成了十六进制数拼接
最终,stringTohex做的事情就是把字符串"XXXX"转换为十六进制数0xXXXX存储\
回到if判断中,这个函数根据serial得到了三个值,下边sub_401336根据name也得到了三个值,那可想而知sub_4018EC的作用就是把这两组数比较并输出flag。
sub_401336

三个值对应三个算法,从上往下分别为FNV-1a,黄金分割旋转哈希,魔改的djb2。
需要注意一点n1836020840 = 'moth';中moth最终需要作为name传入程序,因为这个程序中出现最多的也是moth。
sub_4018EC
int __fastcall sub_4018EC(int n1, int n2, int n3, int sy, int sz, int sx)
{
unsigned int v7; // eax
int i; // [rsp+40h] [rbp-70h]
int j; // [rsp+40h] [rbp-70h]
__int64 lpBuffer_; // [rsp+44h] [rbp-6Ch] BYREF
_BYTE Buffer[32]; // [rsp+4Ch] [rbp-64h] BYREF
unsigned int v12[8]; // [rsp+6Ch] [rbp-44h] BYREF
unsigned int v13[8]; // [rsp+8Ch] [rbp-24h] BYREF
int v14; // [rsp+ACh] [rbp-4h]
unsigned __int16 _n1; // [rsp+C0h] [rbp+10h]
unsigned __int16 _n2; // [rsp+C8h] [rbp+18h]
unsigned __int16 _n3; // [rsp+D0h] [rbp+20h]
unsigned __int16 _sy; // [rsp+D8h] [rbp+28h]
_n1 = n1;
_n2 = n2;
_n3 = n3;
_sy = sy;
v14 = sx ^ n3 | sz ^ n2 | sy ^ n1;
if ( v14 != 0 )
// "The moth circles and does not land.\r\n"
return output(Format: Format);
sub_4017DF(Y: sy, Z: sz, n3: sx, a4: v13, n32: 32);
sub_4017DF(Y: _n1, Z: _n2, n3: _n3, a4: v12, n32: 32);
for ( i = 0; i < 28; ++i )
Buffer[i + 4] = *(v12 + i) ^ *(v13 + i) ^ byte_402000[i] ^ 0x6B;
for ( j = 0; j < 4; ++j )
Buffer[j] = *(&v12[7] + j) ^ *(&v13[7] + j) ^ byte_40201C[j] ^ 0x6B;
v7 = sub_4017AD(Y: _sy, Z: sz, n3: sx);
sub_401872(lpBuffer: &lpBuffer_, a2: v7);
sub_401000(lpBuffer: &Buffer[4], n28: 0x1Cu);
sub_401000(lpBuffer: &lpBuffer_, n28: 8u);
return sub_401000(lpBuffer: Buffer, n28: 4u);
}
这个函数比较关键的就是前面的比较,只要不触发// "The moth circles and does not land.\r\n"分支后面程序就会自动生成flag了
关键在于v14 = sx ^ n3 | sz ^ n2 | sy ^ n1。异或是相异输出True,如果相同,则输出False。因此可以用这条性质得出 a ^ b == 0,当且仅当a == b。最终需要v14 == 0,也就是两组数需要对应相等。
到这里可以得出验证的逻辑了
验证逻辑
输入name 输出serial
↓ ↓
根据name生成 根据serial生成
三个值 三个值
\ /
一一对应相等
↓
输出flag
因此根据相等条件可以求解出序列号\
keygen
主要是根据name生成三个值的算法,只需要把sub_401336翻译为对应的编程语言即可,这里以python为例,不过需要注意与掩码做与运算以及运算符优先级的问题。
另外还需要注意name生成三个值和serial生成的三个值的对应关系,需要主动追踪调用链一一证实。
完整代码如下:
import sys
M = 0xFFFFFFFF
def check(c) -> int:
if c < "a" or c > "z":
return ord(c)
else:
return ord(c) - 32
def keygen(name: str, len: int) -> str:
v12 = 0x811C9DC5
n = 0x6D6F7468
for c in name:
v12 = 0x1000193 * ((check(c) ^ v12) & M) & M
a3 = ((40503 * (v12 ^ 0x4D4C)) >> 16) & 0xFFFF
for c in reversed(name):
v6 = check(c)
x = ((v6 + 32) ^ n) & M
n = ((x >> 27) | (x << 5)) & M
n = (n - 1640531527) & M
a4 = ((v12 >> 16) ^ n) & 0xFFFF
v10 = (check(name[0]) + (len << 8)) & M
for k in range(len):
v10 = (33 * ((k + 3) * check(name[k]) + v10) + 23) & M
a5 = (v12 ^ v10 ^ 0x17A1) & 0xFFFF
return f"ML-{a5:04X}-{a3:04X}-{a4:04X}"
if __name__ == "__main__":
name = input("name> ").strip()
if not (3 <= len(name) <= 20):
sys.exit("name must be 3..20 chars (letters/digits/space/hyphen, >=1 letter)")
serial = keygen(name, len(name))
print(f"serial> {serial}")
浙公网安备 33010602011771号