【crackmes.one】———— Needle in Haystack

Language: c/c++ | Arch: x64 | Platform: Windows |Tech: crackme,hash
EXE info
该程序的信息如上图所示
直接进入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并不相同,该结论可以动态调试得出
target_hash
NULL_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'

posted on 2026-11-07 22:32  %HuTao%  阅读(3)  评论(0)    收藏  举报