【crackmes.one】———— Needle in Haystack
Language: c/c++ | Arch: x64 | Platform: Windows |Tech: crackme,hash

该程序的信息如上图所示
直接进入ida静态分析
target_hash
这部分位于puts函数之前
mov edx, cs:dword_14000A154
.text:0000000140008B94 xor edx, 47F28A6Ch
.text:0000000140008B9A add edx, 56h ; 'V'
.text:0000000140008B9D imul edx, 80BE9C3Fh
.text:0000000140008BA3 mov eax, edx
.text:0000000140008BA5 shr eax, 0Dh
.text:0000000140008BA8 xor eax, edx
.text:0000000140008BAA lea rdx, off_14000A3E8 ; "strawberry_fields"
.text:0000000140008BB1 lea r8, [rdx+148h] ; size:0x148
.text:0000000140008BB8 nop dword ptr [rax+rax+00000000h]
.text:0000000140008BB8
.text:0000000140008BC0
.text:0000000140008BC0 loc_140008BC0: ; CODE XREF: sub_140008B80+5C↓j
.text:0000000140008BC0 mov rcx, [rdx]
.text:0000000140008BC3 add rdx, 8
.text:0000000140008BC7 movzx ecx, byte ptr [rcx]
.text:0000000140008BCA add eax, ecx
.text:0000000140008BCC imul eax, 80BE9C3Fh
.text:0000000140008BD2 mov ecx, eax
.text:0000000140008BD4 shr ecx, 0Dh
.text:0000000140008BD7 xor eax, ecx
.text:0000000140008BD9 cmp r8, rdx
.text:0000000140008BDC jnz short loc_140008BC0
.text:0000000140008BDC
.text:0000000140008BDE lea rcx, Buffer ; "======================================="...
.text:0000000140008BE5 lea ebx, [rax+13E04BCCh]
.text:0000000140008BEB imul ebx, cs:dword_14000A158
.text:0000000140008BF2 xor ebx, cs:dword_14000A150
.text:0000000140008C04 rol ebx, 0Dh
观察这部分代码,它有几个显著特征:
- 对字符的处理指令有
add / imul / xor / shr / rol - movzx逐字节处理相关字符
- 出现Magic Number(三个dword)
- 最后处理结果是固定的32位,用于后续比较
因此可以判断该算法是hash算法
它处理数据的首地址是off_14000A3E8,大小是0x148,内容特征如下:
off_14000A3E8 dq offset aStrawberryFiel ; DATA XREF: sub_140008B80+2A↑o
.rdata:000000014000A3E8 ; "strawberry_fields"
.rdata:000000014000A3F0 dq offset aAdminSecure99 ; "Admin#Secure99"
.rdata:000000014000A3F8 dq offset aP3nnywiseIt ; "p3nnywise_IT"
.rdata:000000014000A400 dq offset aSilenthill2026 ; "SilentHill_2026"
.rdata:000000014000A408 dq offset aGravityfalls06 ; "GravityFalls_06"
......
这是一张表,存储指向特定字符串的8字节指针,因此add rdx, 8是用来访问每个字符串,而mov rcx, [rdx]是取每个字符串的第一个字符。这些字符构成了新的字符串,该部分计算的就是这个新字符串的hash
password_hash
lea rdx, Control ; "\r\n"
lea rcx, [rsp+138h+Buffer] ; Str
call strcspn
mov r9d, cs:dword_14000A15C
mov [rsp+rax+138h+Buffer], 0
movzx eax, [rsp+138h+Buffer]
test al, al
jz loc_140008DE0
除去字符串中\r和\n后,判断字符串是否为空,为空则计算一个固定hash
xor r9d, 0DACC22A3h
imul r9d, cs:dword_14000A158
mov edx, 3C6ABC72h
mov eax, r9d
shr eax, 10h
xor r9d, eax
mov eax, 1E355h
xor edx, r9d
xor edx, eax
cmp ebx, edx
jnz short loc_140008DBD
结果与target_hash并不相同,该结论可以动态调试得出


然后正常输入字符串会走另一个分支,仍然是计算输入字符串的hash,最终与target_hash比对。
已知hash算法不可逆,那怎么找到一个与target_hash匹配的输入呢?答案在题目里
One of the strings inside this program is
the real password. Everything is visible.
strings指的是off_14000A3E8,这里一共41个字符串,只需要一个一个试就可以了
首先需要把这41个字符串提取出来,python脚本如下:
scripts
import ida_bytes, idc, io
TBL = 0x14000A3E8 # off_14000A3E8
COUNT = 0x148 // 8 # 41
cands = []
for i in range(COUNT):
p = ida_bytes.get_qword(TBL + i * 8) # 取第 i 个指针
t = idc.get_strlit_contents(p) # 按 C 字符串解引用
if t is None: # 万一没被标成字面量,退回原始读
t = ida_bytes.get_bytes(p, 64).split(b"\x00")[0]
cands.append(t.decode("latin-1"))
print(len(cands), cands[16]) # 41 kafkaesque_99
with io.open(r"cands.py", "w", encoding="utf-8") as f:
f.write("CANDS = [\n")
for c in cands:
f.write(" %r,\n" % c)
f.write("]\n")
最终导出的list放在了另一个python脚本里,这个脚本负责向程序依次传入41个字符串
scripts
CANDS = [] #以上一个脚本结果为准
EXE = "crackme.exe"
for i,cand in enumerate(CANDS):
p = subprocess.run(
[EXE],
input=cand + "\n", # 只喂一行,之后 stdin 关闭 -> EOF -> 程序退出
capture_output=True,
text=True,
timeout=5,
)
if "Correct!" in p.stdout: # 命中特征串
print(f"[HIT] #{i}: {cand!r}")
break
else:
print("not found")
结果[HIT] #16: 'kafkaesque_99'
浙公网安备 33010602011771号