1. 移除链表元素
1.1 反汇编分析

1.2 映射C语言代码

1.3 输出结果

1.4 📝 极简战术卡:单链表定点清除 (Remove Elements)
-
核心阵型(双指针)
-
cur = head;(探路兵) -
prev = NULL;(垫后部队,必须置空,严防野指针!)
-
-
核心微操(分情况爆破)
-
命中头节点(重点):
-
head = cur->next;(必须同步后撤大本营!) -
free(cur); -
cur = head;
-
-
命中中后部:
-
prev->next = cur->next;(跨过废墟) -
free(cur); -
cur = prev->next;
-
-
未命中(正常行军):
prev = cur;cur = cur->next;
-
-
终极闭环(情报回传)
-
C语言默认传值调用,函数内部的
head只是拷贝。 -
必须在末尾
return head;,让外部主函数重新接收最新的头节点坐标。
-
2. 链表的中间节点
2.1 反汇编分析

2.2 映射C语言代码

2.3 输出结果

2.4 📝 极简战术卡:寻找链表中间节点 (Middle Node)
2.4.1. 核心战术(快慢指针联合侦察)
-
slow = head;(探路兵:每次步长 1 节点) -
fast = head;(侦察机:每次步长 2 节点) -
物理定论: 侦察机撞线(到达末尾)时,探路兵必在绝对中点。
2.4.2 绝命雷区与绝对防御(while 循环条件)
-
💥 死亡阵型(引发段错误):
while(fast->next != NULL && fast != NULL)- 死因: 偶数节点下,fast 变 NULL 后,强行读取 fast->next 会瞬间踩爆空指针!
-
🛡️ 金身防御(标准答案):
while(fast != NULL && fast->next != NULL)- 原理: 利用 C 语言
&&的 短路求值(Short-Circuit) 特性。把fast != NULL这个盾牌顶在最前面,一旦为假,直接短路跳出,后半句绝不执行!
- 原理: 利用 C 语言
2.4.3 边界防弹衣(特殊地形拦截)
-
极度缺员(空链表):
if (head == NULL) return NULL; -
孤军奋战(单节点):
if (head->next == NULL) return head;(防患于未然,直接返回头节点)。
2.4.4 阵型结算(奇偶差异)
-
奇数节点(如 7 个):
fast停在最后一个节点(fast->next == NULL 跳出)。slow停在正中间(第 4 个)。 -
偶数节点(如 6 个):
fast飞出边界停在废墟(fast == NULL 跳出)。slow停在下半区的第一个(第 4 个)。
3. 找到链表倒数第N个节点
3.1 反汇编分析

3.2 映射C语言代码

3.3 输出结果

3.4 📝 极简战术卡:寻找倒数第 N 个节点 (The_last_N_nodes)
3.4.1 核心阵型("游标卡尺"战术)
-
创建两名同起点的士兵:
fast = head;slow = head; -
战术本质: 制造一个长度绝对为 N 的真空地带,然后整体平移。
3.4.2 绝对防御装甲(防崩溃指南)
-
防"神经病指令":
if (num <= 0) return NULL;(拦截无效参数)。 -
防"敌后空降(N > 链表总长)": 在 for 循环突进时,必须紧盯先锋状态:
if (fast == NULL) break;。突进结束后,必须进行战损评估:
if (i != num) return NULL;(步数没走够说明越界,直接撤退)。
3.4.3 两段式推进微操
-
第一阶段(造尺子): for 循环 让
fast独自突进N步。 -
第二阶段(平移卡尺):
while (fast != NULL),fast和slow保持 1 步的同频推进。当fast踩空坠崖时,slow所在的坐标就是完美靶心!
3.4.4 逆向战场特别收录:破除工具幻象
-
敌情: 调试器引擎"自作聪明",导致反汇编错位(如将纯正的
8B 55 FC解析为残缺的db 8B、push ebp、cld)。 -
决断: 果断下达 【从模块中移除分析】 指令!
-
底层原理: 撕毁错误的启发式标签,迫使工具退化为 "重新线性扫描" ,放弃虚假的语法高亮,直取物理层面的绝对真实(
mov edx, [ebp-0x4])!
4. 合并链表

4.1 反汇编分析

4.2 映射C语言代码

4.3 输出结果

4.4 📝 极简战术卡:合并有序链表 (Merge_List)
4.4.1 正向 C 语言战略(核心框架)
-
神级微操"哨兵节点(Dummy Node)":
malloc一个不存数据的临时头节点(前线指挥所)。战术价值: 彻底免除 "判断谁是第一个节点" 的繁琐逻辑,所有数据直接往它身后排队。 -
拉链式遍历:
while (var_1 && var_2),谁小就挂谁,挂完自己往后挪一步,tail也紧跟其后。 -
残部无缝收编: 循环结束后,必有一条链表先空。直接用
tail->next对准另一条没空的链表头部,瞬间完成整编(不需要再用while遍历剩下的节点)。 -
🔥 避坑排雷(内存泄漏预警): 链表合并后,"原头节点"可能已经沦为小弟。打扫战场时(
free或Destroy),必须对准合并后返回的"新头节点"开火,否则会导致堆内存大面积泄漏!
4.4.2 逆向汇编情报(底层真容)
-
结构体尺寸暴露:
push 0x8配合call malloc,直接在汇编层暴露了哨兵节点的物理大小(8字节)。 -
逻辑与(&&)的连发狙击: C 语言里的
A && B,在底层是极其干脆的连续两枪cmp + je。一旦发现 0(NULL),立刻顺着相同的跳板脱离战场。 -
编译器的"反向思维": C 语言写的是
<=,汇编里执行的是cmp + jg(大于则跳转)。CPU 采用"反向过滤",一旦var_1 > var_2,立刻踢给else分支;否则顺理成章进入if分支。 -
缝合指针的物理本质: 所谓的
tail->next = node,在底层就是一条极其暴力的内存覆写指令:mov dword ptr ds:[ecx+0x4], edx。
5. 反转链表
5.1 反汇编分析

5.2 映射C语言代码

5.3 输出结果

5.4 📝 极简战术卡:原地反转单链表 (Reverse_List)
5.4.1 正向 C 语言战略(三车道滑动窗口)
-
绝对防御装甲:
if (phead == NULL || phead->next == NULL)。必须前置拦截空链表和单节点链表,防止后续指针解引用时引发段错误(Segfault)导致全军覆没。 -
三大特种兵列阵: 殿后卫
prev(初始为NULL)、中军将curr(初始为head)、先锋兵next(负责探路)。 -
四步物理微操(循环体):
-
探路: 先锋前出,咬住下一个节点坐标(防走失)。
-
转身: 中军过河拆桥,指针反向指向殿后卫。
-
跟进: 殿后卫向前接替中军的位置。
-
突进: 中军向前接替先锋的位置。
-
5.4.2 逆向汇编情报(底层真容)
-
短路求值(Short-Circuit)的物理真相: 逻辑或 || 在底层是极其冷酷的连续
cmp和跳跃。只要第一道NULL检查命中,CPU 直接执行je脱离战场,看都不看第二个条件,极其高效。 -
指针缝合的暴力美学: 所谓"转身",在汇编里没有任何高级概念,仅仅是一句极其干脆的寄存器覆写指令:
mov dword ptr ds:[ecx+0x4], edx。 -
零战损(O(1) 空间复杂度): 整个循环体没有一次
call malloc或call free,纯靠通用寄存器(eax、ecx、edx)在原地把内存坐标颠倒乾坤。
6. 判断回文结构

6.1 反汇编分析

6.2 映射C语言代码

6.3 输出结果

6.4 📝 判断链表回文结构反汇编分析要点总结
6.4.1 内存布局的"特征码"
在分析 SListNode 这种简单结构体时,注意偏移量(Offset):
-
`reg`:通常访问的是结构体的第一个成员(本例中是
data)。 -
`reg + 0x4`:在 32 位程序中,这通常是下一个成员。看到它被赋值给指针变量,基本可以确定是
next指针。 -
规律:如果你看到
mov eax, [eax+4]这种"自己指向自己下一跳 "的指令,这就是标准的链表遍历。
6.4.2 函数调用的"意图推断"
当遇到无法直接看到源码的 call 时,通过输入和输出来盲推:
-
call 00401014: 传入头指针,返回一个中间位置的指针 →\rightarrow→ 推断为
GetMiddleNode。 -
call 00401041: 传入一个节点,返回一个新链表头 →\rightarrow→ 推断为
ReverseList。
6.4.3 💡 逆向思维模型
-
边界检查:
if (head == NULL)。 -
寻找中点: 利用快慢指针或计数。
-
反转局部: 将后半段链表反转。
-
双向对冲比较: 从两头(或从中点和头)向中间遍历对比数据。从两头(或从中点和头)向中间遍历对比数据。
7. 找到相交节点
7.1 反汇编分析

7.2 映射C语言代码

7.3 输出结果

7.4 📝 链表_找到相交节点算法反汇编分析总结
7.4.1 算法逻辑:对齐长度法 (The Alignment Method)
-
这段汇编完美还原了解决相交链表问题的经典 O(n)O(n)O(n) 时间复杂度算法:
-
第一步: 双链表统计。两个独立的
while循环,利用count++和ptr = ptr->next统计长度。 -
第二步: 尾节点校验。通过
cmp ecx, [ebp-0x10]判断两个链表的最后一个节点地址是否一致。这是判断相交的必要条件(若末尾不同,必不相交)。 -
第三步: 长短链表对齐。计算差值 abs,让长链表的指针先行 N 步。
-
第四步: 同步碰撞。两个指针同时向后走,地址相等时即为交点。
7.4.2 关键汇编特征模式 (Pattern Recognition)
-
在分析中,以下指令组合具有极高的识别价值:
-
遍历特征:
mov edx, [ecx+0x4]紧跟mov [ebp-0xC], edx→\rightarrow→ 这是标准的p = p->next。 -
地址比较 vs 数据比较:
-
mov edx, [eax]/cmp edx, [ecx]→\rightarrow→ 比较的是节点存的值(data)。 -
cmp eax, ecx→\rightarrow→ 比较的是节点在内存中的地址(指针本身)。 -
本题的核心就在于地址比较。
-
-
循环计数器:
- 在
while内部看到add [ebp-X], 1,通常意味着正在统计链表长度或索引。
- 在
7.4.3 逆向中的变量管理技巧
-
这道题涉及了约 7 个局部变量 (
var_0x4到var_0x1C),在反汇编中跟踪它们需要遵循以下原则:-
寄存器缓存:在 Debug 模式 下,由于不开启优化,寄存器(
eax,ecx,edx)频繁作为内存变量的"中转站"。 -
栈帧定位:
-
[ebp+0x8]和[ebp+0xC]是外部传入的两个链表头(List1,List2)。 -
[ebp-0x4]和[ebp-0x8]是计数器(len1,len2)。 -
[ebp-0x14]和[ebp-0x18]是用于对齐和最终遍历的移动指针(pLong,pShort)。
-
-
7.4.4 环境线索:为什么代码这么多?
-
你可能注意到代码中有很多
mov和跳转,这揭示了编译器的环境: -
Debug 编译: 使用了
sub esp, 0x5C预留了大量栈空间,并用0xCCCCCCCC初始化。这意味着代码没有经过逻辑精简,保留了所有中间变量。 -
栈平衡检查: 末尾的
call _chkesp证明了这是为了防止函数调用导致栈溢出而存在的防御性代码。
8. 判断环形链表
8.1 反汇编分析

8.2 映射C语言代码

8.3 输出结果

9. 找到环点

9.1 思路
思路: 找到相遇点,两个指针一起走,即可走到环点。
以下是论证:

9.2 反汇编分析

9.3 映射C语言代码

9.4 📝 事项总结:Floyd 判圈算法分析要点
-
快慢步频差: 看到一个指针走一步,另一个指针在同一个循环内通过
[reg+4]访问两次next,基本可以锁定是在找环或找中点。 -
两次循环结构:
-
第一个循环: 判断是否有环(条件是
ptr1 == ptr2)。 -
第二个循环: 找入口(条件是
ptr_head == ptr_meeting)。
-
-
返回值语义:
-
返回
0或NULL:通常表示链表是线性的(无环)。 -
返回
eax(节点指针):表示找到了环的入口。
-
10. 分割链表

10.1 反汇编分析

10.2 映射C语言代码

10.3 输出结果

10.4 🧩 分割链表(Partition List)核心特征总结
10.4.1 哨兵节点的开场布局 (Initialization)
这是最显著的特征。为了简化逻辑,代码通常会创建两个"虚头节点"。
-
汇编表现: 在函数头连续出现两次
malloc(0x8)(或对应结构体大小的内存分配)。 -
特征指令:
c
push 0x8
call malloc ; 创建"小于x"链表的哨兵
mov [ebp-0xC], eax
...
push 0x8
call malloc ; 创建"大于等于x"链表的哨兵
mov [ebp-0x14], eax
- 逻辑映射: 看到这一对
malloc就要意识到,程序准备了两条"流水线"来分拣数据。
10.4.2 双路分流的选择逻辑 (The Partition Loop)
在一个大的 while 循环内部,必然存在一个基于目标值(Pivot)的判断。
-
汇编表现: 取当前节点的
data值,与参数(通常是arg_0xC)对比,并根据结果产生两个方向的指针更新。 -
特征动作:
-
分支 A: 将当前节点挂载到第一个哨兵链表的尾部。
-
分支 B: 将当前节点挂载到第二个哨兵链表的尾部。
-
-
注意:每个分支最后通常都有一个
mov dword ptr [edx+4], 0,这是为了强行断开原有的物理连接,防止链表成环。
10.4.3 "断桥重连"的桥梁指令 (The Final Connection)
在循环结束后,汇编中会出现一段不属于循环体的"接龙"操作,这是算法的灵魂。
- 特征指令:
c
mov ecx, [ebp-0x10] ; 取"小链表"的尾巴
mov edx, [ebp-0x14] ; 取"大链表"的哨兵头
mov eax, [edx+0x4] ; 取"大链表"的第一个真实节点
mov [ecx+0x4], eax ; 拼接!
- 逻辑映射: 这段指令的作用是把"小链表"的尾部指向"大链表"的开头,完成逻辑重组。
10.4.4 现场清理与结果返回 (Cleanup)
由于哨兵节点是用 malloc 申请的临时空间,函数结束前必须销毁它们,否则会内存泄漏。
-
汇编表现: 连续出现两次
free调用,释放的对象正是最初那两个哨兵。 -
返回特征:
return的不再是原始参数,而是第一个哨兵的next指针(即var_0xC->next)。
10.5 💡 逆向心得建议
你在分析时那种"冥冥之中的感觉"其实来源于对数据流向的把握。对于分割链表:
-
盯住指针的"接力": 如果看到两组指针(一个负责头,一个负责跑)在同时运动,那就是在重组结构。
-
忽略琐碎的 mov: 在汇编中,为了操作 ebp 偏移,会有很多重复的装载。你只需要抓住核心的 mov edx+4, eax(修改 next 指针),就能看清逻辑。
-
识别哨兵的意义: 看到 free(var_0xC) 却 return var_0xC->next,这就是标准的哨兵用法------"过河拆桥"。
11. 深拷贝链表
11.1 反汇编分析

11.2 映射C语言代码

11.3 输出结果

11.4 📝 复杂链表深拷贝(Random Pointer)分析总结
11.4.1 构体内存布局的精准识别
在 32 位底层环境下,识别结构体是所有分析的基石:
-
偏移量特征:通过汇编指令中的
+0,+4,+8偏移,结合push 0xC(12字节) 的malloc调用,可以推断出节点布局:-
0x0: 数据域(val/data)
-
+0x4: 指针域(next)
-
+0x8: 特殊指针域(random)
-
-
多级解引用: 看到类似
mov eax, [ecx+8]紧跟mov edx, [eax+4],要立刻反应出这是在通过random指针寻找它后继的"克隆节点"。
11.4.2 三段式算法模式的识别
这道题在汇编控制流(CFG)中呈现出三个清晰的 while 循环块:
-
第一阶段:交错插入(Interweaving)
-
核心动作:
Old1 -> New1 -> Old2 -> New2。 -
汇编特征: 循环内频繁调用
malloc,且新节点的next指向原节点的next,原节点的next指向新节点。
-
-
第二阶段:随机指针校准(Random Alignment)
-
核心动作:
copy->random = cur->random->next。 -
汇编特征: 出现大量的
cmp [reg+8], 0(判空)以及利用"交错步长"进行的指针赋值。这是整个算法中最具辨识度的代码块。
-
-
第三阶段:链表拆分(Detaching)
-
核心动作: 将混合链表拆分为原链表和新链表。
-
汇编特征: 两个移动指针(
var_0x4和var_0x18)同步步进,且伴随着cur->next = copy->next这种"跳跃式"的指针修改,用于恢复原链表。
-
本章完~