文章总结: 本文档为学习笔记,涵盖汇编进阶(x86-64系统调用syscall的调用约定、数据定义.ascii与字符串长度计算、jmp跳转控制流)和Web安全入门实战(Bugku平台三道题:通过X-Forwarded-For伪造IP绕过本地管理员登录、分析并篡改Base64编码的sign参数修改游戏分数、通过URL解码和代码审计获取密码)。核心结论是掌握底层系统调用与Web常见漏洞利用技巧,建议通过抓包分析和编码识别进行实战练习。 综合评分: 80 文章分类: WEB安全,渗透测试,实战经验,漏洞分析,二进制安全
本日学习笔记:汇编进阶 + Web 入门实战
原创
00后反骨崽 00后反骨崽
00后反骨崽
2026年7月12日 20:07 北京
在小说阅读器读本章
去阅读
今天干了两件事:一是汇编继续往上走,学了系统调用和跳转控制流;二是开了 Bugku 平台的 Web 题,搞了三道入门题。一文打包,干货不少,建议收藏!👇
上篇:汇编进阶 —— 系统调用与跳转控制流
📡 SYSCALL:让程序和操作系统对话
在 x86-64 汇编中,syscall 是调用 Linux 内核服务的入口。相当于你对操作系统说”帮我干个活”。
调用约定:
| 寄存器 | 作用 |
| — | — |
| rax | 系统调用号(告诉内核你要调哪个功能) |
| rdi | 第 1 个参数 |
| rsi | 第 2 个参数 |
| rdx | 第 3 个参数 |
| rcx | 第 4 个参数 |
| r8 | 第 5 个参数 |
| r9 | 第 6 个参数 |
💡 比如调用号 1 = write(输出),调用号 60 = exit(退出程序)。完整调用号表参考:https://filippo.io/linux-syscall-table/
📝 数据定义:.ascii 与字符串长度计算
.ascii —— 存字符串
.ascii “Hello,World!\n” ; 把字符串逐字节存入内存
和 .byte 的区别:.byte 是一个字节一个字节手动存;.ascii 直接存一整段字符串。
计算字符串长度
汇编里没有 strlen() 这种现成函数,但可以用汇编器的地址运算:
q1: .ascii “Hello,World!\n”
q1len = . – q1 ; 当前地址 – 起始地址 = 长度
. 表示汇编器当前所在地址,减去字符串起始地址 q1,就得到了字符串的字节数。非常巧妙!
🧩 Exercise 3:输出两段字符串 + 用 code 99 退出
题目要求:
把两个字符串输出到终端
用系统调用号 60(exit)退出程序,退出码为 99
完整解法
; === 第 1 次输出 ===
mov rax, 1 ; syscall 1 = write
mov rdi, 1 ; fd = 1 (stdout)
lea rsi, [q1] ; 缓冲区地址 = q1 的地址
lea rdx, [q1len] ; 长度 = q1len
syscall ; 执行!输出第一段字符串
; === 第 2 次输出 ===
mov rax, 1
mov rdi, 1
lea rsi, [q2]
lea rdx, [q2len]
syscall ; 输出第二段字符串
; === 退出程序 ===
mov rax, 60 ; syscall 60 = exit
mov rdi, 99 ; 退出码 = 99
syscall ; 程序结束
.data
q1: .ascii “Hello,World!\n”
q1len = . – q1
q2: .ascii “you are so beautiful”
q2len = . – q2
💡 关键点:每次 syscall 之前都要把
rax重新设为调用号。因为上一次 syscall 执行后,rax的值可能已经被内核修改了。
补充:xor 清零技巧
如果不希望程序报错,最后要正确退出。清零寄存器有个经典写法:
xor rdi, rdi ; rdi 异或自身 = 0(比 mov rdi, 0 更短)
🔀 JMP:跳转——让代码不再只能从上往下跑
之前写的代码都是从上往下顺序执行,但真实程序需要分支、循环、函数调用,这就需要跳转指令。
jmp —— 无条件跳转
jmp print1 ; 直接跳到 print1 标签处执行
程序结构示例
用跳转可以写出类似 if-else 的结构:
_start:
jmp print1 ; 先跳到 print1
exit:
mov rax, 60
mov rdi, 0
syscall ; 正常退出
print1:
mov rax, 1
mov rdi, 1
lea rsi, q1
lea rdx, q1len
syscall
jmp print2 ; print1 完成后跳到 print2
print2:
mov rax, 1
mov rdi, 1
lea rsi, q2
lea rdx, q2len
syscall
jmp exit ; print2 完成后跳到 exit
.data
q1: .ascii “Hello”
q1len = . – q1
q2: .ascii “World”
q2len = . – q2
执行流程:
_start → print1(输出Hello)→ print2(输出World)→ exit(退出)
💡 理解要点:虽然代码在内存中是按
_start → exit → print1 → print2排列的,但通过jmp指令,实际执行顺序是_start → print1 → print2 → exit。代码的存储顺序 ≠ 执行顺序,这是跳转的核心概念。
📦 预告:CALL、RET 与栈
跳转之外,汇编还有几个重要概念将在后续学习中涉及:
| 指令 | 作用 |
| — | — |
| CALL | 调用子程序(跳到目标地址,同时把返回地址压栈) |
| RET | 从子程序返回(从栈中弹出返回地址,跳回去) |
| STACK | 内存中的一系列字节,用于存储函数调用的上下文 |
这三个配合起来,就能实现函数调用——这是写出复杂程序的基础。下一篇继续!
下篇:Web 入门 —— Bugku 平台三题全解
题目一:本地管理员 —— IP 伪造 + Base64 解码
🎯 考点
HTTP 请求头伪造、Base64 编码
🔍 解题过程
打开网站,看到一个管理员登录界面。先用admin/test123试试——果然不对,提示:
“IP禁止访问,请联系本地管理员登录,IP已被记录”
页面底部还藏了一段密文:dGVzdDEyMw,Base64 解码后得到 → test123,确认了密码。但重新登录还是 IP 禁止提示。
关键线索:题目叫”本地管理员”,说的是 IP 限制。需要让服务器以为你是从本机(127.0.0.1)访问的。
💡 突破口:X-Forwarded-For
用抓包工具拦截登录请求,添加请求头:
X-Forwarded-For: 127.0.0.1
重新发包 → 直接拿到 flag!
flag{c55e8a06ff14a6dbc242d229422001ff}
📖 知识点:
X-Forwarded-For是代理服务器用来记录客户端原始 IP 的头,如果后端只信任这个头而不校验,就可以被伪造。
题目二:game1 —— 游戏分数篡改 + 加密分析
🎯 考点
抓包分析、参数篡改、Base64 编码识别
🔍 解题过程
题目是一个盖楼小游戏。正常玩几把,观察游戏失败时发送的请求包:
score=50&ip=111.194.76.212&sign=zMNTA===
score=75&ip=111.194.76.212&sign=zMNzU===
score=25&ip=111.194.76.212&sign=zMMjU===
第一步:直接改 score 为超大值 → 还是失败,说明分数不是唯一判定条件。
第二步:分析 sign 参数。发现 zM 前缀不变,后面的部分尝试 Base64 解码:
NTA= → 50 ✓
NzU= → 75 ✓
MjU= → 25 ✓
确认了!sign 中 zM 后面的部分就是分数的 Base64 编码。
第三步:把 999999999999999 做 Base64 编码,替换 sign 中对应部分,同时 score 也改成匹配值:
score=999999999999999&ip=111.194.76.212&sign=zMOTk5OTk5OTk5OTk5OTk5==
发包 → 成功拿到 flag!
📖 知识点:
sign参数常用于防篡改,但如果编码方式太简单(如直接 Base64),等于没加密。分析思路:观察变化规律 → 猜测编码方式 → 验证猜测 → 构造请求。
题目三:源代码 —— URL 编码解码 + 代码审计
🎯 考点
HTML 源码查看、URL 编码(%xx)解码、JavaScript 代码审计
🔍 解题过程
题目描述就一句话:”看看源代码”。页面上有一个输入框和 submit 按钮。
按 F12 查看页面源码,发现两段 URL 编码的变量:
var p1 = ‘%66%75%6e%63%74%69%6f%6e…’;
var p2 = ‘%61%61%36%34%38%63%66%36…’;
eval(unescape(p1) + unescape(‘%35%34%61%61%32’ + p2));
手动 URL 解码后拼接,得到完整的 JavaScript:
function checkSubmit(){
var a = document.getElementById(“password”);
if(“undefined” != typeof a){
if(“67d709b2b54aa2aa648cf6e87a7114f1” == a.value)
return !0;
alert(“Error”);
a.focus();
return !1;
}
}
document.getElementById(“levelQuest”).onsubmit = checkSubmit;
🔥 本周学习笔记:汇编进阶 + Web 入门实战
这周干了两件事:一是汇编继续往上走,学了系统调用和跳转控制流;二是开了 Bugku 平台的 Web 题,搞了三道入门题。一文打包,干货不少,建议收藏!👇
上篇:汇编进阶 —— 系统调用与跳转控制流
📡 SYSCALL:让程序和操作系统对话
在 x86-64 汇编中,syscall 是调用 Linux 内核服务的入口。相当于你对操作系统说”帮我干个活”。
调用约定:
表格
| 寄存器 | 作用 |
| — | — |
| rax | 系统调用号(告诉内核你要调哪个功能) |
| rdi | 第 1 个参数 |
| rsi | 第 2 个参数 |
| rdx | 第 3 个参数 |
| rcx | 第 4 个参数 |
| r8 | 第 5 个参数 |
| r9 | 第 6 个参数 |
💡 比如调用号 1 = write(输出),调用号 60 = exit(退出程序)。完整调用号表参考:https://filippo.io/linux-syscall-table/
📝 数据定义:.ascii 与字符串长度计算
.ascii —— 存字符串
.ascii “Hello,World!\n” ; 把字符串逐字节存入内存
和 .byte 的区别:.byte 是一个字节一个字节手动存;.ascii 直接存一整段字符串。
计算字符串长度
汇编里没有 strlen() 这种现成函数,但可以用汇编器的地址运算:
q1: .ascii “Hello,World!\n”
q1len = . – q1 ; 当前地址 – 起始地址 = 长度
. 表示汇编器当前所在地址,减去字符串起始地址 q1,就得到了字符串的字节数。非常巧妙!
🧩 Exercise 3:输出两段字符串 + 用 code 99 退出
题目要求:
把两个字符串输出到终端
用系统调用号 60(exit)退出程序,退出码为 99
完整解法
; === 第 1 次输出 ===
mov rax, 1 ; syscall 1 = write
mov rdi, 1 ; fd = 1 (stdout)
lea rsi, [q1] ; 缓冲区地址 = q1 的地址
lea rdx, [q1len] ; 长度 = q1len
syscall ; 执行!输出第一段字符串
; === 第 2 次输出 ===
mov rax, 1
mov rdi, 1
lea rsi, [q2]
lea rdx, [q2len]
syscall ; 输出第二段字符串
; === 退出程序 ===
mov rax, 60 ; syscall 60 = exit
mov rdi, 99 ; 退出码 = 99
syscall ; 程序结束
.data
q1: .ascii “Hello,World!\n”
q1len = . – q1
q2: .ascii “you are so beautiful”
q2len = . – q2
💡 关键点:每次 syscall 之前都要把
rax重新设为调用号。因为上一次 syscall 执行后,rax的值可能已经被内核修改了。
补充:xor 清零技巧
如果不希望程序报错,最后要正确退出。清零寄存器有个经典写法:
xor rdi, rdi ; rdi 异或自身 = 0(比 mov rdi, 0 更短)
🔀 JMP:跳转——让代码不再只能从上往下跑
之前写的代码都是从上往下顺序执行,但真实程序需要分支、循环、函数调用,这就需要跳转指令。
jmp —— 无条件跳转
jmp print1 ; 直接跳到 print1 标签处执行
程序结构示例
用跳转可以写出类似 if-else 的结构:
_start:
jmp print1 ; 先跳到 print1
exit:
mov rax, 60
mov rdi, 0
syscall ; 正常退出
print1:
mov rax, 1
mov rdi, 1
lea rsi, q1
lea rdx, q1len
syscall
jmp print2 ; print1 完成后跳到 print2
print2:
mov rax, 1
mov rdi, 1
lea rsi, q2
lea rdx, q2len
syscall
jmp exit ; print2 完成后跳到 exit
.data
q1: .ascii “Hello”
q1len = . – q1
q2: .ascii “World”
q2len = . – q2
执行流程:
_start → print1(输出Hello)→ print2(输出World)→ exit(退出)
💡 理解要点:虽然代码在内存中是按
_start → exit → print1 → print2排列的,但通过jmp指令,实际执行顺序是_start → print1 → print2 → exit。代码的存储顺序 ≠ 执行顺序,这是跳转的核心概念。
📦 预告:CALL、RET 与栈
跳转之外,汇编还有几个重要概念将在后续学习中涉及:
表格
| 指令 | 作用 |
| — | — |
| CALL | 调用子程序(跳到目标地址,同时把返回地址压栈) |
| RET | 从子程序返回(从栈中弹出返回地址,跳回去) |
| STACK | 内存中的一系列字节,用于存储函数调用的上下文 |
这三个配合起来,就能实现函数调用——这是写出复杂程序的基础。下一篇继续!
下篇:Web 入门 —— Bugku 平台三题全解
题目一:本地管理员 —— IP 伪造 + Base64 解码
🎯 考点
HTTP 请求头伪造、Base64 编码
🔍 解题过程
打开网站,看到一个管理员登录界面。先用admin/test123试试——果然不对,提示:
“IP禁止访问,请联系本地管理员登录,IP已被记录”
页面底部还藏了一段密文:dGVzdDEyMw,Base64 解码后得到 → test123,确认了密码。但重新登录还是 IP 禁止提示。
关键线索:题目叫”本地管理员”,说的是 IP 限制。需要让服务器以为你是从本机(127.0.0.1)访问的。
💡 突破口:X-Forwarded-For
用抓包工具拦截登录请求,添加请求头:
X-Forwarded-For: 127.0.0.1
重新发包 → 直接拿到 flag!
flag{c55e8a06ff14a6dbc242d229422001ff}
📖 知识点:
X-Forwarded-For是代理服务器用来记录客户端原始 IP 的头,如果后端只信任这个头而不校验,就可以被伪造。
题目二:game1 —— 游戏分数篡改 + 加密分析
🎯 考点
抓包分析、参数篡改、Base64 编码识别
🔍 解题过程
题目是一个盖楼小游戏。正常玩几把,观察游戏失败时发送的请求包:
score=50&ip=111.194.76.212&sign=zMNTA===
score=75&ip=111.194.76.212&sign=zMNzU===
score=25&ip=111.194.76.212&sign=zMMjU===
第一步:直接改 score 为超大值 → 还是失败,说明分数不是唯一判定条件。
第二步:分析 sign 参数。发现 zM 前缀不变,后面的部分尝试 Base64 解码:
NTA= → 50 ✓
NzU= → 75 ✓
MjU= → 25 ✓
确认了!sign 中 zM 后面的部分就是分数的 Base64 编码。
第三步:把 999999999999999 做 Base64 编码,替换 sign 中对应部分,同时 score 也改成匹配值:
plaintext
1
2
score=999999999999999&ip=111.194.76.212&sign=zMOTk5OTk5OTk5OTk5OTk5==
发包 → 成功拿到 flag!
📖 知识点:
sign参数常用于防篡改,但如果编码方式太简单(如直接 Base64),等于没加密。分析思路:观察变化规律 → 猜测编码方式 → 验证猜测 → 构造请求。
题目三:源代码 —— URL 编码解码 + 代码审计
🎯 考点
HTML 源码查看、URL 编码(%xx)解码、JavaScript 代码审计
🔍 解题过程
题目描述就一句话:”看看源代码”。页面上有一个输入框和 submit 按钮。
按 F12 查看页面源码,发现两段 URL 编码的变量:
varp1=’%66%75%6e%63%74%69%6f%6e…’;
varp2=’%61%61%36%34%38%63%66%36…’;
eval(unescape(p1)+unescape(‘%35%34%61%61%32’+p2));
手动 URL 解码后拼接,得到完整的 JavaScript:
functioncheckSubmit(){
vara=document.getElementById(“password”);
if(“undefined”!=typeofa){
if(“67d709b2b54aa2aa648cf6e87a7114f1″==a.value)
return!0;
alert(“Error”);
a.focus();
return!1;
}
}
document.getElementById(“levelQuest”).onsubmit=checkSubmit;
真相大白!代码直接写了:输入框的值等于 67d709b2b54aa2aa648cf6e87a7114f1 就通过验证。
把这个值输入密码框,提交 → 拿到 flag!
flag{1a97c178d7e12c68ab5220497effbbfd}
📖 知识点:看源码是 Web 题的第一步——flag 可能藏在注释里、JS 里、甚至 CSS 里。
eval()+unescape()是前端常见的动态执行手段,CTF 中经常遇到。
📊 本日收获总结
| 方向 | 内容 | 关键知识点 | | — | — | — | | 汇编 | 系统调用 | syscall 调用约定、.ascii、地址差算长度 | | 汇编 | 跳转控制流 | jmp 指令、代码存储顺序≠执行顺序 | | 汇编 | xor 清零 | xor reg, reg 快速归零 | | Web | IP 伪造 | X-Forwarded-For 请求头 | | Web | 参数篡改 | sign 签名机制 + Base64 编码识别 | | Web | 源码审计 | URL 编码解码 + eval/unescape |
🔗汇编练习平台:https://app.x64.halb.it/
🔗系统调用号查询:https://filippo.io/linux-syscall-table/
🔗Web 练习平台:https://ctf.bugku.com/
💬 对 CTF 感兴趣的欢迎关注我们的公众号,一起学习交流!
QQ 群:1051436928
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:00后反骨崽 00后反骨崽 00后反骨崽《本日学习笔记:汇编进阶 + Web 入门实战》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论