3、Git基础与补丁提取:Git常用命令、生成补丁文件、提取特定版本的补丁
做漏洞分析,说白了就是跟代码的「前世今生」打交道。你拿到一个CVE公告,光看diff可能一头雾水。但如果你能亲手把补丁从Git仓库里「拎」出来,对比一下修复前后的变化,很多隐藏的漏洞就藏不住了。这一章,我就带你过一遍Git里最核心的几个操作,专门为补丁分析服务。
3.1 我常用的几个Git命令
Git命令很多,但做补丁分析,你不需要全记住。我个人习惯,日常就靠下面这几个命令吃饭。
| 命令 | 作用 | 我为什么常用它 |
|---|---|---|
git log |
查看提交历史 | 找漏洞修复的commit,看作者、时间、描述 |
git show |
显示某个commit的详细改动 | 直接看diff,比去网页上翻方便多了 |
git diff |
比较两个版本之间的差异 | 对比修复前后的代码,找逻辑漏洞 |
git format-patch |
生成补丁文件 | 把commit导出成.patch文件,方便分享或存档 |
git apply |
应用补丁文件 | 在本地复现漏洞环境,或者测试补丁效果 |
嗯,这里要注意一点:git log 默认只显示当前分支的历史。如果你发现某个漏洞修复的commit不在当前分支上,记得加个 --all 参数。我曾经因为没加这个参数,找了半天没找到那个关键的commit,后来才发现它躺在另一个分支里。
3.2 生成补丁文件
生成补丁文件,是漏洞分析里很关键的一步。你想想看,你从开源项目里找到了一个修复commit,怎么把它提取出来?总不能截图吧?
Git提供了两种方式:
3.2.1 用 git format-patch 生成标准补丁
这个命令会生成一个 .patch 文件,里面包含了commit的作者、时间、描述和具体的代码改动。格式是标准的,可以直接发给别人,或者用 git am 导入到另一个仓库。
# 生成最近1个commit的补丁
git format-patch -1
# 生成从 commitA 到 commitB 之间的所有补丁
git format-patch commitA..commitB
# 生成某个commit的补丁(比如漏洞修复的那个)
git format-patch -1 <commit-hash>
举个例子,假设你找到了一个修复了缓冲区溢出的commit,哈希是 abc123。你只需要跑:
git format-patch -1 abc123
然后当前目录下就会多出一个 0001-xxx.patch 文件。打开看看,里面就是完整的补丁内容。
--stdout 参数,把内容直接输出到终端,方便快速浏览。比如 git format-patch -1 abc123 --stdout。这样就不用生成文件,看完就完事了。
3.2.2 用 git diff 生成简易补丁
有时候你不需要完整的commit信息,只想看代码改了什么。这时候用 git diff 更直接。
# 比较两个commit之间的差异
git diff commitA commitB
# 比较工作区和暂存区的差异
git diff
# 比较暂存区和最新commit的差异
git diff --cached
举个例子,你想看漏洞修复前(commit old)和修复后(commit new)到底改了哪些行:
git diff old new
输出结果里,- 开头的行是删除的,+ 开头的行是新增的。漏洞分析时,我特别关注那些被删除的代码——往往就是漏洞所在。
git diff 生成的补丁不包含commit的元信息(作者、时间、描述)。如果你需要保留这些信息,请用 git format-patch。我曾经在提交漏洞报告时,直接用 git diff 生成的补丁发给厂商,结果对方问我「这是谁改的?什么时候改的?」——尴尬。
3.3 提取特定版本的补丁
在实际分析中,你经常需要提取某个特定版本的补丁。比如,Linux内核的某个CVE修复,可能是在5.10.100版本里。你怎么把这个版本的补丁提取出来?
我的做法分三步:
- 找到修复commit:用
git log --oneline搜索,或者直接看CVE公告里给出的commit哈希。 - 确认父commit:修复commit的父commit,就是漏洞存在时的代码。用
git show <commit-hash>^可以查看父commit的内容。 - 生成补丁:用
git format-patch或git diff提取差异。
举个例子,假设CVE-2024-12345的修复commit是 def456,它的父commit是 abc123。你想提取这个补丁:
# 方法一:用 format-patch
git format-patch -1 def456
# 方法二:用 diff
git diff abc123 def456
两种方法都行。我个人更推荐 format-patch,因为它保留了commit信息,方便后续追溯。
git diff 出来的补丁可能包含大量无关的改动。怎么办?我的经验是:先找到漏洞版本对应的tag或分支,然后在这个基础上 cherry-pick 修复commit,再生成补丁。这样得到的补丁才是「纯净」的。
3.4 实战:从Linux内核提取一个补丁
光说不练假把式。我们模拟一个场景:假设你发现Linux内核的某个驱动存在一个整数溢出漏洞,修复commit是 a1b2c3d4。你想提取这个补丁,并分析漏洞细节。
步骤:
- 克隆仓库:
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git - 切换到修复commit:
git checkout a1b2c3d4 - 生成补丁:
git format-patch -1 a1b2c3d4 - 查看补丁:用文本编辑器打开生成的
.patch文件,或者直接用cat查看。
补丁文件里,你会看到类似这样的内容:
From a1b2c3d4... Mon Sep 17 00:00:00 2001
From: Author Name <author@example.com>
Date: Mon, 1 Jan 2024 12:00:00 +0000
Subject: [PATCH] Fix integer overflow in xxx driver
---
drivers/xxx/xxx.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/xxx/xxx.c b/drivers/xxx/xxx.c
index abc123..def456 100644
--- a/drivers/xxx/xxx.c
+++ b/drivers/xxx/xxx.c
@@ -100,7 +100,7 @@ static int xxx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret;
unsigned long size;
- size = arg + sizeof(struct xxx_data);
+ if (arg > ULONG_MAX - sizeof(struct xxx_data))
+ return -EINVAL;
+ size = arg + sizeof(struct xxx_data);
...
}
看到了吗?修复前,arg + sizeof(struct xxx_data) 可能溢出,导致 size 变成一个很小的值,进而引发缓冲区溢出。修复后,加了一个溢出检查。这就是漏洞的根因。
xxx_ioctl 函数里可能还有其他路径没有做检查,那可能就是另一个漏洞。
好了,这一章的内容就到这里。Git基础操作是补丁分析的「基本功」,练熟了,后面分析复杂漏洞时才能游刃有余。下一章,我们会深入补丁的diff分析,看看怎么从一行代码的改动里挖出隐藏的漏洞。