管线劫持
对大多数非计算机相关专业的同学而言,这是一个很难理解的名词。简要的说,“管线”指的是GPU中的渲染管线;“劫持”的意思是在渲染管线中插入我们希望渲染的内容,或改变渲染的方式。
从使用体验的角度上讲,通过“管线劫持”可以让计算机的GPU向屏幕输出任何自定义的内容,比如考试答案。
听起来好耳熟!这不就是“字幕”/“水印”吗???
当然……不是!❌
Well,如果从“表现形式”这个单一的方面看,的确,和普通的字幕并没什么区别:都是将答案放在了屏幕上。
但是它却具备一些非常之奇妙的、“字幕”/“水印”并不具备的特质:
- 第二机位监考的手机无法拍到它,所以考生不需要“卡机位”
- 不会出现文字与题目互相遮挡,导致考生看不清的现象
- 免疫一切屏幕捕获,哪怕是驱动级的屏幕捕获
听起来像是天方夜谭,对吧?一段可以被显示在屏幕上的文字,却几乎无法被摄像头拍到?完全没有任何道理!但请先别急,在我们麻瓜的世界里,没有魔法,只有科学。
如果你是相关专业,并具备相当的理工背景,可以直接参考这四篇论文:
- Invisible Perturbations: Physical Adversarial Examples Exploiting the Rolling Shutter Effect (CVPR 2021 — Sayles et al.)
- Over-the-Air Adversarial Flickering Attacks Against Video Recognition Networks (CVPR 2021 — Pony & Naeh)
- Adversarial Laser Beam: Effective Physical-World Attack to DNNs in a Blink (CVPR 2021 — Duan et al.)
- Structured Polarization for Invisible Depth and Reflectance Sensing (CVPR 2024 — Ichikawa et al.)
如此最为科学严谨。
如果论文对你来说并不友好,ELPIS尽量用“低技术、多比喻”的方式说明白。但即便如此,以下内容也稍有些硬核。
首先,想要弄明白为什么摄像头“拍不到”,先要弄明白摄像头是“怎么拍到的”。对于任何现代手机摄像头,其工作原理大致如下:

物体所发出(或反射出)的光线,先通过镜头组进入相机内部,然后经由(电子)快门控制曝光时长,最后CMOS组件负责感光成像。这样,一张照片就拍摄完成了。
人们常说照片是一瞬间的定格,但从物理学的角度这句话并不严谨:如果真的是定格了“瞬间”,照片就不会出现残影模糊的现象了。因为对于每一个“瞬间”,宏观物体的状态都是确定的、是精准的。


所以实际上,照片记录的是“一段极短时间内,被摄物运动量的总和”。比如某手机前置摄像头的快门时间是1/30秒,也就意味着从按下快门开始算的1/30秒内,如果被摄物基本保持不动,则被叠加在一起的运动量几乎没有,也就没有残影。但是如果在这1/30秒内,被摄物运动幅度较大,则被叠加在一起的运动量就会较多,也就出现了残影。
现在让我们来看一个例外情况,物体的确在高速运动,并且相机快门时间相对较长,但被摄物却没有出现残影:
这不是PS出来的录像,而是一种真实的物理现象。直升机的确在天上飞,但是螺旋桨好像看起来没有动。这是因为螺旋桨的转速和摄像头的帧率碰巧一致了。
我们假设摄像头帧率为30,也就是说每一帧的快门时间是1/30秒。如果螺旋桨转动整整一圈的时间也刚好是1/30秒,那么在这1/30秒的时间内,螺旋桨转了一圈又刚好回到了原位!对于每一帧,皆是如此!所以螺旋桨好像看起来“没动”。
不是螺旋桨真的没动,而是相机被“欺骗”了。
可这些,和“看不见”又有什么关系呢?
既然我们知道相机可以被“欺骗”,我们就可以人为的构造“欺骗”相机的条件!
我们假设有一个奇妙的小色块,它可以瞬间在黑白两种颜色之间切换。我们令纯白为0,纯黑为100,并且令色块每切换颜色的时间间隔刚好为1/120秒。
已知:色块初始色为纯白;
如果:有一台快门速度为1/30秒的相机对色块拍照一次;
请问:拍出来的颜色会是什么?
- 纯白?
- 纯黑?
- 还是…灰色?
感觉有点抽象?我们来画个图。

那么相机拍出来会是什么颜色呢?
算法是:SUM(颜色数字*持续时间)/ 总体持续时间
也就是:
(0 * 1/120 + 100 * 1/120 + 0 * 120 + 100 * 1/120) / (1 / 30) = 50
50是一个介于0(纯白)和100(纯黑)之间的数字,所以,答案是介于黑白正中间的灰色!
你看!我们完全可以在色彩上“欺骗”相机!在计算机的色彩世界中,颜色被用RGB表示:

所以只要可以精确控制在极短时间内文字颜色的变换,我们甚至可以控制文字在摄像头中显示出各种不同的色彩!
诚然,截至目前,距离实现“拍不到”还是有一定差距。不过基本的方法论就是这样啦!余下的事情只是在基本方法论之上的演绎。
我们知道,这部分的讲述有点…戛然而止的感觉:略微爽,但不够爽。ELPIS在这里致歉各位看官老爷!毕竟,网站是公开的平台,恕我们无法把所有的技术路线、实现细节都一一道出(尽管我们内心特别希望可以与大家分享,因为真的真的很有趣)。所以,我们现在不得不只把基础知识说出来,然后给爱思考的、甚至想自己动手实现的各位留一个很难的课后作业。
接下来,为了弥补各位看官老爷,也为了证明ELPIS的方案与“字幕”/“水印”完全不同,我们直接贴出“字幕”/“水印”方案的核心代码。我们从代码的角度看看,这种方案为什么不安全。
//Register window class
const wchar_t CLASS_NAME[] = L"Zimu Window";
WNDCLASSEX wc = { sizeof(WNDCLASSEX) };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszClassName = CLASS_NAME;
wc.style = CS_HREDRAW | CS_VREDRAW;
//Set window style
DWORD exStyle = WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_TOOLWINDOW;
DWORD style = WS_POPUP;
//Create window
HWND hWnd = CreateWindowEx(
exStyle,
CLASS_NAME,
L"Zimu",
style,
x, y,
initW, initH,
NULL, NULL, hInstance, NULL
);
以上代码所做的事情非常简单那,无非是创建一个窗口,并且规定这个窗口的一些特质。比如,窗口属性是Always on Top的工具类(Pop Up)窗口。但是问题在于CreateWindowEx这个函数。调用这个函数,会让系统把你的窗口在屏幕上“画出来”,但同时也会使得你的窗口被系统登记在册。一旦被你的窗口被系统登记,就一定可以被找到。比如可以使用EnumWindows()这个系统函数枚举出所有窗口句柄,再通过窗口句柄找到对应的进程。用以下代码即可实现:
// Callbck function
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
DWORD processId = 0;
GetWindowThreadProcessId(hwnd, &processId);
if (processId == 0) return TRUE;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (hProcess) {
TCHAR exeName[MAX_PATH] = {0};
if (GetModuleFileNameEx(hProcess, nullptr, exeName, MAX_PATH)) {
std::wcout << L"HWND: " << hwnd
<< L" | PID: " << processId
<< L" | Executable: " << exeName << std::endl;
}
CloseHandle(hProcess);
}
return TRUE; // Enumeration continues
}
int main() {
// Calling EnumWindows with callback installed
if (!EnumWindows(EnumWindowsProc, 0)) {
std::cerr << "EnumWindows failed with error: " << GetLastError() << std::endl;
return 1;
}
return 0;
}
以上代码正是现在主流考试软件的做法!字幕方案的两难性在于:
- 如果不使用CreateWindowEx()注册字幕视窗,系统就无法显示出字幕;
- 如果使用CreateWindowEx()注册字幕视窗,字幕视窗就变得可查询了;
有些好奇的同学会问:那是否存在一种既让字幕视窗显示出来,又让它无法查询的方式呢?
答案是:没有。就像是有没有“既让马儿跑,又让马儿不吃草”的方法呢?并没有。计算机不会和你讨价还价。
可是为什么目前依然有字幕出分的案例?
那是因为字幕的窗口的名字可以自定义,只要把字幕的窗口名字改成某一个系统进程的窗口名字,就可以骗过考试系统一段时间。但是总有一天考试系统会发现端倪,然后成绩被回溯取消。
说了这么多,其实我们还没真正解释清楚,为什么需要“劫持”GPU的渲染管线!前面提到过,如果想做到“肉眼可见但相机难以捕捉”,我们必须让文字内容以极高的频率快速变化,甚至每次变化的间隔要短至 1/200 秒。
如果完全依靠 CPU 来完成这一过程,几乎不可能。因为常规流程是:先把需要显示的文字生成到系统内存(RAM)中,再由 CPU 进行处理,然后把结果拷贝到 GPU 的显存中,接着 GPU 才会渲染并输出到屏幕。这个过程涉及多次内存传输和 CPU 计算,延迟太高,根本跟不上所需的刷新速度。
那能不能直接把文字送进 GPU 的显存里,让 GPU 自己完成渲染并直接显示呢?理论上当然可以,而且效率极高。但在实践中,这并不简单——因为这意味着要绕过常规的渲染API调用流程,直接介入GPU的渲染管线。
我们需要先看看 GPU 的渲染管线是怎样工作的。

如图所示,通过HLSL(D3D11/12)即可精细化的操作GPU的Pixel Shader(片元着色器),从而完成极高频的染色。
由于我们没有利用任何现有的API,而是自己用代码“介入”了GPU的工作流程,也就相当于“劫持”了管线。
所以,“管线劫持”的目的是使GPU可以以极高频对一个区域进行渲染,从而达到对第二机位隐藏文字的目的。
更美妙的是,这种方法不会在软件层面留下任何痕迹。着色器会在考前载入完毕,载入后退出,不影响计算机的正常使用,更不影响考试。因为严格意义上说,着色器只是决定了屏幕画面的显示方式,着色完成后并不存在任何进程。而考试软件自然不关心考生显示器色温、色准、刷新率等等与考试完全不相关的、并且每台计算机都可能不一致的东西。
另外,由于我们“劫持”了渲染管线,在面对屏幕录制时,我们可以轻易的“摘除”不希望被录制进去的内容。当然,有一个前提:我们必须完全控制一台计算机。唯有如此才可以100%使得驱动级别的录制无效。这并不是简单的安装某个软件、执行某个命令可以做到的。而是需要利用 0day(零日漏洞)才能实现对计算机的完全控制。详情请见【】。在业界中,如何彻底制止驱动级别的屏幕捕获是一个“老大难”问题。虽然截至目前(和可预见的将来),任何考试软件都不可能接入驱动级别的屏幕录制(因为要么需要GPU厂商专门为了某款考试软件定制驱动,要么需要额外的特殊硬件),但我们依旧做了充分的future proof的方案,确保哪怕在最极端的条件下,屏幕上显示的文字都没有可能被录制。
最后,关于让考生“看得清”这一点…由于方案的本质,我们很难在网页上展示出来到底对人眼有多清晰!因为我们:
无法截屏:驱动级别的过滤使得我们的文字无法出现在任何截屏里;
无法拍照:方案设计的初衷就是避免文字被拍下来!哪怕我们特地距离很近的拍下,文字也几乎不可见,无法说明“对人眼清晰可见”,反而会让人看完后觉得“人眼也看不清”。
那就请各位看在我们码了这么多字的份上,暂且相信我们:你一定可以看得清!完全不会影响阅读!
“管线劫持”方案的好处讲完了,我们也来聊点坏的,说说这方案的局限性。
最重要的先说,虽然极高频的变换对人眼几乎是不可见的,对于大部分人来说,人眼的识别上限大约是60帧,超过60帧对人眼来说就是连续显示的图像,但是对于极少部分对光极度敏感的人来说,这个方案有一定概率触发癫痫!

所以有癫痫史的同学们,这方案不要用!考试的确重要,但是健康更可贵!在ELPIS的内部测试中,曾有极少数情况下测试者汇报有不适感受。比如在持续阅读一小时的文字后,有轻微的头晕、恶心等现象。但由于ELPIS团队内暂无癫痫患者,所以更多副作用暂且未知。(就算有我们也不会让她/他去测试!)
其次,这个方案对考试笔记本的要求相当高。笔记本需要一个十分强劲独立显卡,和一块240Hz的高刷屏,屏幕响应时间理想情况下应为1ms。这导致大部分考生的笔记本根本无法达到要求。
没办法,我们不能“既要又要也要”。一个又好用、又稳定、又好读、又隐形的方案的代价就是一台超级顶配的笔记本。
结束语:
说实话,从25年4月开始,我们就一直试图构想出一种完美解决双机位的方案。我们尝试了很多很多种方法,比如“利用定制的短波通滤光片使得某种特定颜色的屏幕内容无法被摄像头拍到”。又比如“在计算机屏幕上粘附某种复合的、多层的薄膜,使屏幕在摄像头拍摄下出现光栅效应(阶梯状锯齿的摩尔纹),从而让摄像头失真,但又不至于像防窥膜一样使得屏幕变黑。
虽然我们尝试了不下7、8种各式各样奇思妙想的方案,但后来都因为效果问题、隐蔽性问题、成本问题、跨设备普适性等问题,被我们一一否决了。直到有一个已经在Nvidia工作的前同事,出于自身职业的角度,提出了如上的构想,我们终于恍然大明白!原来,还可以这样玩!原来不需要借助任何特殊道具,只需要一台高端游戏本,就可以完美解决两难困境!
这再次提醒我们,科学技术才是第一生产力。谁能想到解决ETS重金部署的双机位的功臣,本质上竟然一块小小的GPU芯片?这让人不禁联想到中国高端芯片的发展进程,还真是感到任重而道远。至少目前在高端芯片制造领域,还得有一段“师夷长技以制夷”的过程。
在这个过程中,ELPIS更要好好的将“在专业上有能力”但是“被没啥用的标准化考试卡住”的各位稳稳送出国。ELPIS团队绝大数成员具备强理工背景,我们深知就像GRE、托福这种东西,在2025年的今天,已经纯粹变成了为了卡住学生而特地设计的、与所学专业严重脱节的、给考试中心赚大钱的工具。托福如此,GRE更甚。所以我们偏要有理有据的反潮流,反标准化。况且,哪个理工人可以拒绝“破解”的诱惑?可以拒绝在智商的高地上演精彩刺激的攻防战呢?
最后,祝各位看官老爷事半功倍。无论是否选择我们ELPIS,都可以一帆风顺,前途似锦。如果你选择我们,若干年后可能我们会在世界的某处相遇。你不认识我,我也不认识你,但我们都有一个共同的,名为ELPIS的小秘密。
很有趣,甚至还有点浪漫~
—ELPIS全体成员,敬上
致谢(排名分先后):
- 一位不愿意透露姓名的Nvidia员工,ELPIS的前团队成员
- 一块在反复测试中不幸阵亡的3060显卡,你的牺牲为我们带来了宝贵的实验数据,和一张公费购买的5080显卡。Anyway, RIP.
【显卡照片】
- 一位总说如果没有10043禁令,他就一定会去MIT读书的ELPIS团队成员。感谢你完成了方案的数学计算部分,让后续的程序编写有法可依。
- 笔者自己。如果没有我这些理工男就要往这篇文章里疯狂堆砌公式了。
- ELPIS强大的工程团队,让一切构想成为可能。(说白了,只给你们排第五完全是个人恩怨,咬我啊)