帮我看下这道逆向题,题解说sub_557E8B201180是tea加密,我当时没看出来,我只完成了s...

Criado em: 3 de março de 2026

Respondido usando GPT-5.2 Pro por Chat01

Pergunta

babyre

帮我看下这道逆向题,题解说sub_557E8B201180是tea加密,我当时没看出来,我只完成了sub_557E8B200C00的分析,是根据输入生成十六进制,后面还说sub_557E8B2013D0(v5, v9) == 27106是自制的crc校验,我不太懂,原题有个提示就是给了md5值,hint md5('rctf{'+input+'}').digest.hex() == '5f8243a662cf71bf31d2b2602638dc1d'
不用提示能写出来吗?
我没分析出来这个是tea加密,虽然IDA的findcrypt插件识别出来了,但是我觉得不能依赖这个,万一是变种就不好了,这个算法识别有什么诀窍吗,或者说我对一个sub_557E8B201180(ptr, v4, &unk_557E8B402010, 16, &v9)这样的函数分析应该怎么做更好呢,后面的crc校验看着也挺复杂的,我该怎么做呢
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char *v3; // kr00_8
int v4; // eax
char *v5; // rax
char *v6; // rbx
__int64 i; // rax
int v9; // [rsp+4h] [rbp-124h] BYREF
void *ptr; // [rsp+8h] [rbp-120h] BYREF
char flag[264]; // [rsp+10h] [rbp-118h] BYREF
unsigned __int64 v12; // [rsp+118h] [rbp-10h]

v12 = __readfsqword(0x28u);
setbuf(MEMORY[0x7F58EA1AE6B0], 0LL);
setbuf(MEMORY[0x7F58EA1AE6A8], 0LL);
setbuf(MEMORY[0x7F58EA1AE6A0], 0LL);
ptr = 0LL;
v9 = 0;
memset(flag, 0, 0x100uLL);
__printf_chk(1LL, "Input right flag you can got 'Bingo!' :");
__isoc99_scanf("%31s", flag);
v3 = &flag[strlen(flag)];
if ( (v3 - flag) > 16 )
{
puts("input is too long!");
}
else if ( v3 - flag == 16 ) // 长度正确
{
v4 = sub_557E8B200C00(flag, 16, &ptr);
if ( v4
&& (v5 = sub_557E8B201180(ptr, v4, &unk_557E8B402010, 16, &v9), (v6 = v5) != 0LL)
&& v9 > 0
&& sub_557E8B2013D0(v5, v9) == 27106 )
{
for ( i = 0LL; v9 > i; ++i )
v6[i] ^= 0x17u;
puts(v6);
if ( ptr )
free(ptr);
free(v6);
}
else
{
puts("input flag is wrong!");
}
} // 长度正确
else
{
puts("input is too short!");
}
return 0LL;
}

Pensamento

Decoding hex strings

Analyzing hex strings; examining TEA encryption, CRC check, and md5 hash to solve the challenge. Hm, that's interesting!

Employment termination policy

Valid reasons include employee performance, misconduct, reduction-in-force, and expiring agency term limits.

Revealing binary components

OK, let me see: eyes peeled for ELF PIE options. This involves examining sections and strings with tools like objdump and radare2.

Assessing recovery pathways

I'm evaluating dynamic and symbolic analysis for reversing binary encryption. Considering TEA decryption, CRC validation, and byte manipulation.

Evaluating analysis methods

Mapped out techniques to verify input to decrypt the expected message and how the CRC check ensures the encrypted data's integrity.

Evaluating alternatives

I'm mapping out objdump, llvm-objdump, and ndisasm as options for disassembling and analyzing executable binaries in radare2 and gdb.

Examining tools

I'm weighing options like llvm-objdump and pwntools, plus using Python with capstone for disassembly. Considering ways to identify subfunctions within the stripped binary.

Pinpointing memory segments

I’m identifying the key’s location in memory segments, focusing on .data and .rodata. Using readelf -x, I explore segment overlaps and base addresses in the decompiled code.

Analyzing hex conversion

Examining the table at 0x1620 and 0x1630, it seems to map ASCII digits and invalid values. This might assist in hex decoding.

Discerning the key

I'm digging into the 16-byte key at 0x202010, aligning it with TEA's 128-bit requirement. The CRC function's 27106 value is also under review.

Weighing alternatives

Considering z3 as a potential option.

Mapping options

I’m piecing together how to run the program without gdb, considering LD_PRELOAD hooking, manual disassembly, and dynamic analysis with babyre or ltrace.

Decoding virtual addresses

I’m piecing together the virtual address of "Input right flag you can got 'Bingo!':" by examining the hexdump offsets. This involves locating the .text disassembly around main.

Assessing function start
OK, let's see. I'm thinking about the start of main, potentially around 0x860. This involves inspecting the printf call and the rsi register.

Deciding on the next steps

I’m figuring out how to proceed next.

We're examining functions

Taking a closer look at disassemble sub_200C00 at 0xc00, analyzing main and helper functions. Progressing through disassembly to pinpoint exact locations and functionality.

Noticing function nuances

At 0xb30, a function converts bytes to hex strings. Main calls c00, which might decode hex to bytes while using relative addresses.

Disassembling the process

I'm thinking through the possibility of sub_200C00 converting ASCII to hex or encoding bytes before detailing the disassembly.

Piecing together

OK, let me see. I’m pulling together info on NLP usage, emphasizing ethical guidelines for bias and fairness. It’s interesting to see how clarity and non-discrimination shape effective communication.

Examining XXTEA

OK, so I’m looking at XXTEA with its variable length and shifting operations. The assembly code seems to handle q*delta, focusing on constant 0x34 and idiv esi.

Evaluating calculations

I’m exploring arithmetic steps: dividing 52 by n, multiplying by delta, subtracting 0x4ab325aa, and examining the influence of 0x9e3779b9 on these calculations.

Analyzing the equation

I’m working through δ×6mod232=0xB54CDA56\delta \times 6 \mod 2^{32} = 0xB54CDA56 and then subtracting 0x4AB325AA0x4AB325AA.

Piecing together details

I’m working through calculating 0x4AB325AA and trying to determine if it equals delta*. The function at ce0 involves pushing registers and comparing values.

Hex decoding functionality

I’m mapping out a hex decoding function, which validates length, employs *be0 to convert hex to nibbles, stores bytes, and finally returns output length.

Assessing hex decoding

I looked at sub_200C00's role in decoding hex strings into bytes, matching expectations of length 16 and 8 bytes. This confirms analyzing the correct function.

Decoding hex

Noticing the confusion between decoding and generating hex. The disassembly at 1060 seems to wrap the TEA, checking and validating input parameters. Progressing through to 1180 for clarity.

Identifying the function

I’m noting that the subroutine at 1060 might be preparing or handling data for the TEA function at 1180.

Interpreting function behavior

I'm mapping out 1180's expected input length as a multiple of 4, suggesting decryption with a negative n to utilize ce0.

Examining encryption steps

I’m looking at a process that uses encryption, padding, and null terminators to manage data lengths and support decryption, ensuring consistent data handling.

Analyzing the decryption process

I’m exploring how 1180 uses btea algorithm with sign for decryption. Checking PKCS#7 padding, CRC validation, and XOR10 decoding.

Decrypting the message

Is the idea of decrypting a ciphertext that's verified by a CRC 27106 valid? Considering many solutions and how CRC 16-bit and plaintext length impact unpadding.

Decoding ASCII mapping

It seems this mapping converts ASCII values to integers 0-36, covering digits '0' to '9' and letters 'A' to 'F'. Lowercase letters 'a' to 'f' aren't included.

Piecing together clues

I'm gathering hints and noticing the input might not be hex but a 16-length string.

Reversing encryption methods

I’m thinking through methods like decryption, XOR operation, and examining markers for potential insights into the unique flag.

Breaking down the process

I’m thinking through whether the prompt "Input right flag you can got 'Bingo!'" means the flag is "Bingo!" or if it prints the flag after a specific function. Many challenges print the flag directly after input.

Analyzing input possibilities

I'm noting that the input might be the content within the braces, which aligns with the hashlib's requirement for input being the flag's content.

Decoding process

I'm examining a 16-character input by decoding a hex string with an algorithm that involves decrypting, unpadding, and checking with CRC16 to produce XORed bytes, possibly revealing a meaningful message.

Piecing together the problem

OK, let's decrypt the ciphertext to get the plaintext. Calculating which ciphertext matches the CRC target is the next step.

Assessing encryption feasibility

I'm examining if using a 16-bit CRC check can efficiently figure out the right plaintext length for decryption. Hermitically, this method might sustain security.

Analyzing TEA rounds

I'm mapping out how TEA's 32 rounds are tied to block size and encryption parameters. It's interesting to see the standard approach here.

Analyzing encryption behavior

I’m thinking through how the encryption wrapper adjusts byte length and padding, ensuring decryption matches the padding scheme's multiple of 4.

Examining padding strategy

Wondering why padding involves an extra word. The encryption algorithm uses a uint32 array to store the original length in the last element.

Noting encryption clues

I’m thinking through cmovns setting esi=r12d and sar esi,0x2, resulting in n=-(len/4). This suggests the encryption wrapper may involve an extra word for length.

Examining assembly operations

I'm looking at encrypted values. The process iterates to alter them, focusing on shifts and comparisons with constants.

Breaking down encryption mechanics

I’m curious how the code sums values incrementally, using a constant sum with each round, matching the encryption method.

Analyzing padding techniques
I'm pulling together details on btea encryption with PKCS7 padding to ensure the ciphertext length is a multiple of 4, followed by decryption, unpadding, and CRC computation on the resultant plaintext.

Mapping encryption

I'm examining TEA encryption and CRC checks, noting potential specifics like "Bingo!" as a fixed message.

Clarifying the mechanism

XORing ciphertext and puts likely yields random text, not a fixed message like "Bingo!".

Rethinking the approach

I'm piecing together sub_201180's functionality, considering encryption and CRC check on hex input. This leads me to explore the interaction between sub_200C00 and sub_201180.

Focusing on sub_2013D0

I am examining sub_2013D0 specifically, as it is likely to contain crucial information to solve the problem.

Tracing the trail

I simplified the trail from Bridal Veil Falls to Eagle Point and Eagle Creek. It was a careful journey, considering the waterfall's location and the ecological connections.

Unraveling CRC intricacies

Piecing together CRC function 13d0 with polynomial 0x1021, bitwise operations, and reversals. Analyzing bit reflection for bytes, words, and dwords to compute CRC-16/CCITT-FALSE.

Focusing on CRC calculation

I’m looking into the CRC calculation loop. It shifts bits and XORs with 0x1021. The sign flag in ax influences partial results, ensuring accurate CRC per the MSB of the previous XOR result.

Analyzing block operations

I'm working through eight iterations involving shifts, conditional XORs, and sign tests. Extra branches are checked early on for initial stage adjustments.

Examining polynomial and initialization

I’m piecing together the specifics of CRC-16/KERMIT: polynomial 0x1021, initial value 0x0000, and bit reflection. Comparing the computed CRC to 0x69E2.

Referencing encryption methods

I’m piecing together ciphertexts using TEA with CRC 0x69E2. Applying MD5 on each to pinpoint the unique input. Progressing nicely through these steps.

Laying out the options

Taking a closer look at encryption methods like MD5 and XOR, centering on padding and unpadding with PKCS7, and examining XOR transformations. Progressing through cryptographic processes methodically.

Calculating CRC

I'm thinking through the CRC calculation on the unpadded bytes of "Bingo!" and verifying the expected CRC16 value of 0x69E2.

Mapping out the next steps

I verified the CRC to ensure the plaintext after XOR is "Bingo!" and found a way to input ciphertext for TEA encryption without using MD5.

Encrypting data

I’m preparing to encrypt 8-byte ciphertext using PKCS7 padding and TEA encryption with 32-bit words, ensuring little-endian packing and confirming key word values.

Encrypting with Python

I explored the concept of encrypting words with bTEA in Python, converting them to bytes, and then outputting as uppercase hex.

Encrypting padded text
I’m digging into the encryption of padded plaintext, a key process in data security.

Packing data efficiently

Totally digging into how to pack data into two uint32 values using little-endian format, ensuring both clarity and efficiency. This method keeps data well-organized.

Breaking down the bytes

I’m examining the hexadecimal bytes 'x6\x02\x02', identifying them as 0x02, 0x02, 0x36, 0x78 in little-endian format, resulting in a value of 0x02023678, which is 337.

Converting byte order

เปลี่ยนลำดับ byte เป็น little-endian.

Converting and testing

I’m transforming ciphertext bytes into a hex string, converting to uppercase, and planning to test the input with a binary program.

Confidently validating input
Verifying the MD5 hash of "rctf{input}" against the hint by ensuring the correct input.

I’m thinking through the issue of different plaintexts passing the CRC check, leading to various outputs.

Piecing together clues

I'm examining the MD5 hint, XOR decryption of messages, and the CRC constant to pinpoint the correct flag. The output might be "Congratulations" or another honorary message.

Analyzing user message

I’m parsing the user's message about MD5 hashing. The challenge involves matching the MD5 hash of 'rctf{input}' with a given hash.

Noticing md5 verification

I'm piecing together the likely use of md5 verification to secure the right flag, especially since the binary might accept any input that prints "Bingo!".

Mapping limitations

I’m gathering info on the constraints of md5 and TEA encryption, focusing on the huge space of plaintext. Searching over plaintext length 6 with ascii might be a practical approach.

Testing possibilities

I'm examining shorter printable strings to match the CRC constant after XOR. Considering dictionary words and specific phrases, but the md5 hint mismatch still puzzles me.

Piecing together the solution

Interestingly enough, the lowercase hex digest aligns with the hint, guiding us to use it in the final submission. This confirms our progress.

Analyzing function signatures

Examining how to analyze function signatures, recognizing CRC algorithms, and determining valid inputs for implementation and problem-solving.

Crafting an efficient solution
I’m breaking down CRC-16/KERMIT, focusing on bit reversal and simplification. The goal is to model a practical approach with all necessary details.

Decoding patterns

Examining parameter types, constant comparisons, memory allocations, bit shifts, encryption functions, loops, CRC16 datatypes, and dynamic analysis.

Analyzing encryption methods

I’m mapping out the labeling and decryption processes. This includes Polynomial CRC-16/KERMIT, TEA encryption, and what arrays might reveal about the Lua JIT byte code.

Looking at patterns

I'm identifying differential analysis steps to recognize variant encryption methods. This includes noting specific constants, key schedules, and cipher functionalities.

Aligning the explanation
I'm crafting a cohesive explanation, ensuring it aligns with current findings and focusing on sub_200C00 analysis for generating hex.

Piecing together

Decrypting with TEA, padding with PKCS7, and validating with CRC16, I ensure the output matches "Bingo!".

Adjusting pad sizes

I’m padding the hex string to 8 bytes and using TEA encryption. It’s interesting to see the ciphertext emerge.

Thought for 21m 10s

Resposta

你这题其实不需要依赖 FindCrypt也能稳稳写出来,而且题解里说 “TEA” 其实更准确地说是 XXTEA / Block TEA(btea) 那一支(TEA 家族),你没看出来很正常——但它在汇编里有非常“露骨”的指纹。

下面我按你给的 main 流程,把关键点和“怎么更好分析这种函数”的套路都写清楚,并且给出不靠提示也能直接推出 input 的解法(最后再用 MD5 hint 验证大小写)。


1) 先把 main 的链路理顺:这是一个“从输出倒推输入”的题

你 main 里关键逻辑是:

  1. 输入长度必须 16
  2. sub_200C00(flag,16,&ptr)
  3. sub_201180(ptr, v4, key, 16, &v9) 得到 v5/v6
  4. crc(v5, v9) == 27106
  5. v6[i] ^= 0x17 然后 puts(v6) 输出

所以这是典型的:

输入(16字节字符串) → 变换A → 解密/解码B → CRC校验 → XOR解码 → 打印

这类题最爽的解法就是从打印内容倒推

  • 最终要打印什么?(提示里就写了 Bingo!)
  • XOR 前是什么?
  • CRC 常量能否验证这个猜测?
  • 然后把 XOR 前的内容当作“明文”,用解密函数的逆(也就是加密)求出输入。

2) 你说你分析了 sub_200C00:这里其实是“hex 解码”,不是生成 hex

看你贴的 main,sub_200C00(flag, 16, &ptr) 返回的 v4 会作为长度传进 sub_201180(ptr, v4, ...)

这题二进制里 sub_200C00 的行为是:

  • 要求长度是偶数
  • 每两个字符当作一个 byte
  • 分配 len/2 字节
  • "0"-"9","A"-"F","a"-"f" 映射为 nibble
  • 拼成 byte 写入输出

也就是说:输入 16 个 hex 字符 → 输出 8 个字节(v4=8)。

这点非常关键:后面 TEA/XXTEA 那坨就是按 32-bit word 操作的,8 字节正好 2 个 word。


3) sub_201180(ptr, v4, key, 16, &v9) 为啥能认出是 XXTEA/BTEA(不靠插件)

你不想靠 FindCrypt,这思路对。那怎么“人工识别”?

看这种函数你可以按下面顺序做:

3.1 先看参数约束(这是最快的语义标注)

它检查了这些东西:

  • keylen == 16 ✅(128-bit key,非常像 TEA/XXTEA)
  • len > 0
  • len % 4 == 0 ✅(按 32-bit word 对齐)
  • malloc(len+1) + memcpy + 末尾补 0 ✅(当作字符串返回)

这就很像:decrypt(bytes, len, key[16]) -> printable string

3.2 再看“内层核心循环”的常量指纹:0x9E3779B9 和 0x61C88647

你在内层(它调用的那个大函数)里会看到:

  • 0x9E3779B9(TEA 家族的 delta)
  • 0x61C88647(实际上就是 -0x9E3779B9 mod 2^32

这俩一出现,基本就可以锁定是 TEA-family

3.3 再用结构确认是 “XXTEA/BTEA”,不是经典 TEA

经典 TEA 是每块 64-bit、固定 32 轮,常见结构是两条 32-bit 来回加减。

但这题的核心循环有两个非常“XXTEA”的特征:

  1. 轮数公式6 + 52/n
    你会在汇编里看到用 0x34(=52) 去除 n,再加 6 的影子。

  2. key 索引形态k[(p & 3) ^ e],其中 e = (sum >> 2) & 3
    这是 XXTEA/BTEA 的标配写法。

另外还有一个超级关键点:wrapper 里把 len/4 取负传进去(n = -(len/4)
这正是 btea 参考实现的约定:

  • n > 1:加密
  • n < -1:解密

所以 sub_201180 本质是:

拿输入当密文 → XXTEA 解密 → 根据最后一个字节做 padding 截断 → 返回明文 + out_len

3.4 padding 也能看出来

它解密完会读最后一个字节 pad = buf[len-1],要求 pad <= 4pad < len,然后:

  • out_len = len - pad
  • buf[out_len] = 0

这就是“去 padding”的逻辑(虽然它没有校验 padding 内容,算个小 bug)。


4) CRC 那段怎么认:看见 0x1021 基本就赢了

sub_2013D0(v5, v9) == 27106

你看 CRC 那坨如果觉得乱,其实抓一个点就够:

  • CRC-16 常见多项式:0x10210xA0010x8005
    这题里硬编码 XOR 的就是 0x1021CRC-16/CCITT 系

另外它还调用了两个很像“bit reverse”的小函数(一个对 byte,一个对 word),说明这是 RefIn/RefOut 都开启的版本。
配上 init=0、xorout=0 的行为,很像 CRC-16/KERMIT 那个配置。

你不用背名字,写脚本照着逻辑实现即可。


5) 不用 hint 也能直接解:从 Bingo! 倒推 input

关键观察:

  • 只有通过校验才会 puts(v6)
  • 且打印前每个字节都 ^= 0x17
  • 题面/提示语明确说:能得到 'Bingo!'

所以我们直接假设输出就是 "Bingo!"(这不是 md5 hint,是程序字符串在明示)。

5.1 先把 XOR 还原

打印前做 v6[i] ^= 0x17
所以 XOR 前(也就是 CRC 校验的对象)应该是:

text
pre = b"Bingo!" ^ 0x17 = b"U~ypx6"

5.2 用 CRC 常量验证你猜的对不对

b"U~ypx6" 按它的 CRC 算法算一下,结果就是 0x69E2 = 27106
这一步等于“自校验”:说明你对输出的推断完全正确。

这就是你问的“不用提示能写出来吗”:
可以,因为 CRC 常量本身就能反证你是否猜对了明文。

5.3 组 8 字节明文(因为输入解码后是 8 字节)

解密输出原本是 8 字节(len=8),但它会按最后一个字节 pad 截短。

"U~ypx6" 长度 6,所以 pad 应该是 2 → 最后一个字节放 0x02,并且标准 padding 会再补一个 0x02

text
plain8 = b"U~ypx6" + b"\x02\x02" = 55 7e 79 70 78 36 02 02

(注意:程序实际上没校验倒数第二个 padding 字节,但按常见实现应当是 02 02,这也让解唯一,并且刚好匹配题目的 md5 hint。)

5.4 用 XXTEA 加密(因为程序里用的是解密)得到密文 8 字节

key 在 .data 里那 16 字节:

c7e0c7e0 d7d3f1c6 d3c6d3c6 ced2d0c4

plain8 用 btea(n=2) 加密,得到密文:

05 e8 a3 76 e4 e0 44 6e

转成 hex 输入就是:

  • 大写:05E8A376E4E0446E
  • 小写:05e8a376e4e0446e

二进制两者都接受,但 md5 hint 指向小写,所以最终 flag 是:

rctf{05e8a376e4e0446e}


6) 一份完整可复现的脚本(包含 CRC + XXTEA + MD5 校验)

python
import hashlib DELTA = 0x9E3779B9 def reflect_bits(x, width): r = 0 for i in range(width): if (x >> i) & 1: r |= 1 << (width - 1 - i) return r def crc16_like_bin(data: bytes) -> int: # 对应题目 sub_2013D0:byte 反转 + poly 0x1021 + 最终 16bit 反转 crc = 0 for b in data: b = reflect_bits(b, 8) crc ^= (b << 8) for _ in range(8): if crc & 0x8000: crc = ((crc << 1) & 0xFFFF) ^ 0x1021 else: crc = (crc << 1) & 0xFFFF crc = reflect_bits(crc, 16) return crc def btea(v, n, k): # 标准 Block TEA / XXTEA 参考写法 def MX(z, y, sum_, p, e): return (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)) ^ ((sum_ ^ y) + (k[(p & 3) ^ e] ^ z))) & 0xFFFFFFFF if n > 1: rounds = 6 + 52 // n sum_ = 0 z = v[n - 1] while rounds: sum_ = (sum_ + DELTA) & 0xFFFFFFFF e = (sum_ >> 2) & 3 for p in range(n - 1): y = v[p + 1] v[p] = (v[p] + MX(z, y, sum_, p, e)) & 0xFFFFFFFF z = v[p] y = v[0] v[n - 1] = (v[n - 1] + MX(z, y, sum_, n - 1, e)) & 0xFFFFFFFF z = v[n - 1] rounds -= 1 elif n < -1: n = -n rounds = 6 + 52 // n sum_ = (rounds * DELTA) & 0xFFFFFFFF y = v[0] while rounds: e = (sum_ >> 2) & 3 for p in range(n - 1, 0, -1): z = v[p - 1] v[p] = (v[p] - MX(z, y, sum_, p, e)) & 0xFFFFFFFF y = v[p] z = v[n - 1] v[0] = (v[0] - MX(z, y, sum_, 0, e)) & 0xFFFFFFFF y = v[0] sum_ = (sum_ - DELTA) & 0xFFFFFFFF rounds -= 1 return v # key: 从 .data: c7e0c7e0 d7d3f1c6 d3c6d3c6 ced2d0c4 key_bytes = bytes.fromhex("c7e0c7e0d7d3f1c6d3c6d3c6ced2d0c4") k = [int.from_bytes(key_bytes[i:i+4], "little") for i in range(0, 16, 4)] # 目标输出:Bingo! out = b"Bingo!" pre = bytes([c ^ 0x17 for c in out]) # XOR 还原 assert crc16_like_bin(pre) == 27106 # 0x69E2 plain8 = pre + b"\x02\x02" # padding(8字节) v = [int.from_bytes(plain8[i:i+4], "little") for i in range(0, 8, 4)] c = btea(v, 2, k) cipher = b"".join(x.to_bytes(4, "little") for x in c) inp = cipher.hex() # 小写 hex print("input =", inp) print("flag =", f"rctf{{{inp}}}") print("md5 =", hashlib.md5(f"rctf{{{inp}}}".encode()).hexdigest())

运行会得到:

  • input = 05e8a376e4e0446e
  • flag = rctf{05e8a376e4e0446e}
  • md5 正好等于题目给的 5f8243a662cf71bf31d2b2602638dc1d

7) 你问的“分析这种函数应该怎么做更好”:给你一套通用套路

sub_201180(ptr, v4, &unk_key, 16, &v9) 为例:

A. 先标注参数语义(从检查条件入手)

  • keylen==16key[16]
  • len%4==0 → 32-bit word 算法
  • malloc(len+1) + 写 0 → 输出是字符串/可打印数据
  • outlen 指针被写 → 返回 buffer + length

这种函数你就可以先把它重命名成:
decrypt_buf(in, in_len, key, keylen, out_len)

B. 再顺着 call graph 看“真正的核心计算点”

wrapper 里只做参数检查和内存管理,真正算法在它调用的那个大函数里。

而大函数里:

  • 0x9E3779B9 / 0x61C88647 → TEA family
  • 6 + 52/n → XXTEA
  • (sum>>2)&3、(p&3)^e → XXTEA/BTEA 铁证

C. 对 CRC 这种“看着复杂”的,盯常量就够

  • 0x1021 → CRC-16/CCITT 系
  • 0xEDB88320 / 0x04C11DB7 → CRC-32
  • 0xA001 / 0x8005 → 另一类 CRC16

再看它是左移还是右移、有没有 bit reverse,就能判断 refin/refout。

D. 逆向题最省时间的方式:从输出倒推输入

你这题就是完美例子:

  • 输出 Bingo! 在提示语里写得很直白
  • XOR 可逆
  • CRC 常量能验证你的猜测
  • TEA/XXTEA 可逆 → 用加密还原输入

这比你从输入空间硬推强太多。


最终答案

  • 程序可通过的 input(16 hex 字符,小写)是:
    05e8a376e4e0446e

  • 对应 flag(结合 hint 的格式):
    rctf{05e8a376e4e0446e}

如果你愿意,我也可以把 sub_201180sub_2013D0 对应的伪代码按你 IDA 的风格写成几乎一模一样的 C(便于你对照反编译输出、练“看出算法”的感觉)。

Pergunta

sub_55A507E01180之所以卡住我是因为这个函数在我输入16个1时会返回0,然后跳转到flag错误,我就觉得这是判断函数而不是加密或者解密函数,这是为什么

Pensamento

Examining input handling

I’m analyzing how sub_201180 handles input validation, padding, and decryption. Invalid padding leads to a return of 0, aligning with typical crypto wrapper practices.

Examining padding checks

I'm thinking through why sub_201180 returns 0: it catches invalid padding during decryption, aligning with normal crypto practices. This ensures the function isn't a judge.

Identifying decryption cues

I’m highlighting signs of a decryption function: using malloc, memcpy, key parameters, and delta constants. There’s a focus on the validity of padding in the decryption process.

Taking a closer look

Decrypting 16 '1's leads to a random last byte, resulting in NULL. For analysis, consider bypassing or testing with random input to reveal the output.

Thought for 6m 29s

Resposta

你这个“卡住”的点,其实是非常典型的误判sub_55A507E01180 不是“判断函数”,它更像一个 decrypt/解码 wrapper,返回 指针(成功时指向解密后的缓冲区),失败时返回 0 = NULL。你输入 16 个 1 时返回 0,原因是它里面做了一个很“苛刻”的padding/格式合法性检查,随机密文几乎都会过不了,所以你一跑就一直是 NULL。

我把你这次遇到的现象,用更贴近反编译/汇编的角度解释清楚。


1) 你输入 16 个 1,实际传进去的不是 “16字节 0x31”

main 里先走了 sub_200C00(flag, 16, &ptr),它是 hex 解码

  • 输入 "1111111111111111"(16 个字符)
  • 每两位 hex 组成一个 byte:"11" -> 0x11
  • 所以输出 ptr 实际是 8 字节:0x11 0x11 0x11 0x11 0x11 0x11 0x11 0x11
  • 返回的 v4 就是 8(长度 8)

然后 sub_...1180(ptr, 8, key, 16, &v9) 才开始工作。


2) sub_55A507E01180 返回 0 的真正原因:它在解密后做了 padding 检查

我看了你上传的 babyre.text 段(这个函数就在 RVA 0x1180),它的逻辑非常清晰:

2.1 它做了这些“失败就直接 return NULL”的检查

  • if (!in) return 0;
  • if (len <= 0) return 0;
  • if (!key) return 0;
  • if (keylen != 16) return 0;
  • if (outlen_ptr) *outlen = 0;
  • if (len % 4 != 0) return 0;(按 32-bit word 对齐)

这些都过了才会继续。

2.2 它会 malloc(len+1),拷贝密文,然后调用真正的解密核心

关键片段你应该能在 IDA 里看到:

  • buf = malloc(len + 1)
  • memcpy(buf, in, len)
  • n = -(len/4)
  • btea_xxtea(buf, n, key)真正的 TEA-family 解密在这里

然后才是你遇到的“为什么经常返回 0”的核心:

2.3 解密完后,它读取最后 1 个字节当 padding 长度,并强约束

它做了类似:

c
pad = buf[len - 1]; buf[len] = 0; // 先塞一个0当临时字符串结尾 *outlen = len; if (pad >= len || pad > 4) { free(buf); *outlen = 0; return NULL; } *outlen = len - pad; buf[*outlen] = 0; return buf;

也就是说:

  • 它要求 最后一个字节 pad 必须 <= 4
  • 还要求 pad < len(不能比总长还大)
  • 不满足就直接 free() 然后返回 NULL

3) 为什么你随便输入(比如 16 个 1)几乎必定失败?

因为对一个固定 key 的 “XXTEA/BlockTEA 解密” 来说:

  • 你的输入密文几乎就是“随机块”
  • 解密出来的最后一个字节 pad 在 0~255 上近似均匀
  • 但它只接受 pad ∈ {0,1,2,3,4}(从汇编看它允许 0)
    所以通过概率大约是:
52561.95%\frac{5}{256} \approx 1.95\%

也就是说:你随便试一个输入,98% 的概率它都会直接返回 NULL
你看到“输入 16 个 1 返回 0”完全正常。

这也解释了你当时的直觉误区:
你看到函数返回 0,就以为“这是在判断对不对”。
但实际上它返回的是 NULL 指针,代表“解密结果不符合它自定义的 padding/格式规则”。


4) 怎么避免这种误判:看“返回值被怎么用”,而不是看“返回0”

你 main 里是这样用的:

c
v5 = sub_201180(...); if (v5 && v9 > 0 && crc(v5, v9) == 27106) { ... puts(v5 ^ 0x17); }

注意:

  • 返回值后面被当作 char* 来 XOR、来 puts
  • 这类函数就算 IDA 默认给你推成 __int64,你也应该手动改函数原型,否则你会一直把 NULL 当 “false”。

你可以在 IDA 里把它改成类似:

c
char* __fastcall sub_201180(void* in, int len, void* key, int keylen, int* outlen);

改完之后,你再看 return 0 的语义就会立刻变成:返回 NULL,不是“校验失败返回 false”。


5) 你想继续分析时的技巧:直接把这个 padding check patch 掉/绕过

如果你想观察它到底解密成啥(方便你识别 XXTEA 特征),最实用的办法是:

方法A:动态调试(gdb/gef)

  • btea 调用返回后下断点(或者在读取 buf[len-1] 那行下断点)
  • 观察 pad 是多少
  • 手动把 pad 改成 0/2/4 之一,让它通过
  • 这样函数就会返回 buf,你能直接看到解密输出字节分布

方法B:静态 patch(最省事)

cmp eax, 4; jle ok 这一段改成“无条件跳转 ok”(或者直接 NOP 掉失败分支),让它永远不因为 pad 失败而 return NULL。
这样你用任意输入都能拿到解密后的 buffer,方便继续做算法识别。


6) 顺便点一下:这也是出题人“隐藏算法”的小花招

它把 “解密 + padding 检查” 包在一起,导致:

  • 你用错误输入时,根本看不到后续逻辑(比如 CRC/XOR/puts)
  • 你会误以为它是“判断函数”
  • 实际上它只是在做 格式筛选

很多 CTF babyre 都会用这种招数来“卡新手”。


如果你愿意,你把 sub_55A507E01180 里调用的那个核心函数(在你二进制里是 0xCE0 那个)反编译贴出来,我可以教你怎么从那一坨运算里不靠 FindCrypt,稳定抓出 XXTEA/BTEA 的三大指纹0x9E3779B96+52/nk[(p&3)^e]),以后遇到变种也不慌。

Compartilhar esta Q&A