这道逆向题我已经解出flag了,根据我的动态调试,程序并不检查1-5以及最后一字节内容,只检查6-2...

Créé le : 24 mars 2026

Répondu en utilisant GPT-5.4 Thinking par Chat01

Question

nssctf_round2_findxenny.exe

这道逆向题我已经解出flag了,根据我的动态调试,程序并不检查1-5以及最后一字节内容,只检查6-27字节
v13 = (_QWORD *)(sub_7FF7699815EB((__int64)flag) + 5);这一行就是把flag的第六字节指针赋值给v13
我输入flag{oh_you_found_our_x3nny1是可以通过检查的,这很奇怪,我看代码,对第28字节是有检查的呀:
debug034:000001D23D75A980 sub_1D23D75A980 proc near
debug034:000001D23D75A980
debug034:000001D23D75A980 var_20 = qword ptr -20h
debug034:000001D23D75A980
debug034:000001D23D75A980 sub rsp, 20h
debug034:000001D23D75A984 mov [rsp+20h+var_20], rcx
debug034:000001D23D75A988 mov r10d, 7C072E27h
debug034:000001D23D75A98E xor r10d, 12345678h
debug034:000001D23D75A995 mov edi, dword ptr [rsp+20h+var_20]
debug034:000001D23D75A998 cmp edi, r10d
debug034:000001D23D75A99B jnz short loc_1D23D75A9BB
debug034:000001D23D75A99D mov edi, dword ptr [rsp+20h+var_20+4]
debug034:000001D23D75A9A1 mov r10d, 87653A4Fh
debug034:000001D23D75A9A7 xor r10d, 87654321h
debug034:000001D23D75A9AE cmp edi, r10d
debug034:000001D23D75A9B1 jnz short loc_1D23D75A9BB
debug034:000001D23D75A9B3 xor rax, rax
debug034:000001D23D75A9B6 add rsp, 20h
debug034:000001D23D75A9BA retn
debug034:000001D23D75A9BB ; ---------------------------------------------------------------------------
debug034:000001D23D75A9BB
debug034:000001D23D75A9BB loc_1D23D75A9BB: ; CODE XREF: sub_1D23D75A980+1B↑j
debug034:000001D23D75A9BB ; sub_1D23D75A980+31↑j
debug034:000001D23D75A9BB add rsp, 20h
debug034:000001D23D75A9BF xor rax, rax
debug034:000001D23D75A9C2 sub rax, 1
debug034:000001D23D75A9C6 retn
debug034:000001D23D75A9C6 sub_1D23D75A980 endp
解释一下

Python

python
print(hex(0x7C072E27 ^ 0x12345678)) print(hex(0x87653A4F ^ 0x87654321))

Python

python
s='flag{oh_you_found_our_x3nny1' print(len(s)) for i,ch in enumerate(s,1): print(i,ch)

Réflexion

Investigating where the chunk starts and aligns

So, if the total length is 28 characters, without a closing brace, the program seems to miss checking the last byte. The comparison might only check through byte 27. Given the pointer and constant values, I think the chunk should align starting at byte 22, but the pattern doesn’t match. This suggests an error with the offset or byte sequence.

Thought for 2m 55s

Réponse

你这段代码的关键误区有两个:

  1. v13 当成了字节指针在看
  2. 把这段函数当成了“检查 rcx 指向内容”在看

其实按你贴的汇编,这个函数本身更像是在比较传入参数 rcx 这个 64 位值本身,而不是比较 rcx 指向的 flag 内容。


先看这段汇编到底干了什么

asm
sub rsp, 20h mov [rsp+20h+var_20], rcx mov r10d, 7C072E27h xor r10d, 12345678h mov edi, dword ptr [rsp+20h+var_20] cmp edi, r10d jnz short loc_1D23D75A9BB mov edi, dword ptr [rsp+20h+var_20+4] mov r10d, 87653A4Fh xor r10d, 87654321h cmp edi, r10d jnz short loc_1D23D75A9BB xor rax, rax ...

先把常量算出来:

  • 0x7C072E27 ^ 0x12345678 = 0x6E33785F
  • 0x87653A4F ^ 0x87654321 = 0x0000796E

所以实际比较的是:

c
low32 == 0x6E33785F high32 == 0x0000796E

最重要的一点:这里取的是“栈上的 rcx 变量”,不是 *rcx

这两句非常关键:

asm
mov [rsp+20h+var_20], rcx mov edi, dword ptr [rsp+20h+var_20]

[rsp+20h+var_20] 这个位置,是局部变量 var_20,里面存的是 rcx 本身

也就是说它等价于:

c
tmp = rcx; if ( *(DWORD*)&tmp != 0x6E33785F ) fail; if ( *((DWORD*)&tmp + 1) != 0x0000796E ) fail;

不是

c
if ( *(DWORD*)rcx != ... ) if ( *(DWORD*)(rcx+4) != ... )

这俩差别非常大。


所以这段函数在比较什么?

它比较的是 传入参数 rcx 这个 64 位数值 的低 4 字节和高 4 字节。

如果把这 8 个字节按小端看:

  • 0x6E33785F -> 5F 78 33 6E -> "_x3n"
  • 0x0000796E -> 6E 79 00 00 -> "ny\0\0"

拼起来是:

text
"_x3nny\0\0"

但注意,这里只是说:

如果把 rcx 当成 8 字节数据来看,它长得像 _x3nny\0\0

不等于“程序在检查 flag 的后 8 个字符”。


为什么你动态调试看起来只检查了 6–27 字节?

因为很可能最后这段并没有真的在检查 flag 内容

你现在的动态结果其实很有说服力:

  • 你输入 flag{oh_you_found_our_x3nny1
  • 依然能通过
  • 说明最后那个 1 没被有效校验

这和你贴的这段汇编并不矛盾,反而说明:

更合理的解释是:

这段函数检查的根本不是“第 28 字节是不是某个值”,而是调用时传进去的 rcx 值


还有一个很容易踩的坑:v13_QWORD *

你前面说:

c
v13 = (_QWORD *)(sub_xxx((__int64)flag) + 5);

这句确实是把 flag + 5 赋给了 v13,也就是指向第 6 个字节

但因为它的类型是 _QWORD *,所以后面如果出现:

c
v13 + 1

它不是往后走 1 个字节,而是往后走 8 个字节

也就是:

c
(char*)v13 == flag + 5 (char*)(v13 + 1) == flag + 13 (char*)(v13 + 2) == flag + 21

所以反编译伪代码里一旦混了 _QWORD *_DWORD *,偏移很容易被你“脑补成字符下标”,最后就会误以为它在查第 28 字节。


你这题里最可能的真实情况

结合你的动态结果,我觉得大概率是下面两种之一:

情况 A:这段函数比较的是“地址值”

也就是传进去的是某个指针,函数只是在比这个指针的数值,和 flag 字符内容无关。

这种情况下,你看到的 _x3nny 只是地址低字节碰巧被构造成这样,或者是出题人故意混淆。

情况 B:你把调用点看偏了

也许你以为传进去的是“flag 后面 8 字节”,但实际上 caller 传的是:

  • 一个指针
  • 一个经过转换的整数
  • 或者一个和 flag 内容不直接对应的中间值

这就会导致你静态看着像“检查第 28 字节”,动态却发现“最后一字节不参与”。


怎么快速验证

你去看这段函数的调用点,只要看 rcx 是怎么来的就行:

如果调用前是这种:

asm
lea rcx, [something] call sub_1D23D75A980

那传进去的是地址,函数比较的是地址值,不是内容。

如果调用前是这种:

asm
mov rcx, [something] call sub_1D23D75A980

那传进去的是8 字节数据本身,这时才是在比内容。

这个判断一锤定音。


结论

你这段汇编里:

  • 不是直接检查第 28 字节
  • 它实际比较的是 rcx 这个 64 位参数本身
  • 你之所以觉得“明明有检查”,是因为:
    • v13 被反编译成了 _QWORD *
    • 指针步长按 8 算
    • 再加上这段函数本身没解引用 rcx
    • 所以静态看起来像在查后几个字符,实际并不是

所以你的动态结果“只检查 6–27 字节,最后一字节不检查”是完全说得通的。

把话说得更直接一点:

你贴的这段代码,看上去像在查 _x3nny,实际上大概率没在查 flag 的最后一个字符

你要是把这段函数的调用点前后 10 来条汇编也贴出来,我可以直接帮你把 rcx 到底是“指针”还是“8 字节内容”给你定死。

Partager cette Q&R