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

Tạo vào: 24 tháng 3, 2026

Trả lời bằng GPT-5.4 Thinking bởi Chat01

Câu hỏi

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)

Suy nghĩ

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

Trả lời

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

  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 字节内容”给你定死。

Chia sẻ Q&A này