ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

radare2 ragg2 与 r_egg 语言:从示例程序到汇编输出的完整实战指南

radare2 ragg2 与 r_egg 语言:从示例程序到汇编输出的完整实战指南 逆向工程网络安全【免费下载链接】radare2UNIX-like reverse engineering framework and command-line toolset项目地址https://gitcode.com/gh_mirrors/ra/radare2点击查看免费下载导读r_egg 是 radare2 框架内置的“可重定位代码编译器”relocatable code compiler它定义了一门面向底层系统编程的迷你语言而 ragg2 则是它的命令行前端。本文以仓库libr/egg/t/目录下的官方示例程序为骨架系统讲解.r源文件的完整语法函数定义、syscall 声明、变量访问、别名、条件与循环、内联汇编、ragg2的常用编译参数以及trace/esil/x86/arm等后端输出的差异。读完本文你将能读懂并亲手编写 ragg2 程序用一条命令把它编译为原生汇编、ESIL 中间语言或可执行的独立二进制。仓库位置示例程序位于 libr/egg/t/9 个.r文件语言总文档位于 libr/egg/README解析器实现在 libr/egg/egg_lang.c。一、r_egg 是什么一门会“说话”的迷你汇编语言根据 libr/egg/README 的说明r_egg 是 rarc2 的重新实现定位为 radare 的可重定位代码编译器。它的工作流程是解析.r语言源文件单遍编译 one-pass函数必须按调用顺序定义由REggEmit系列后端生成指定架构的汇编文本x86-{32,64}、ARM 等汇编文本交给 r_asm 汇编成机器码由 r_bin 打包注入成一个小型二进制-f指定格式-F使用宿主原生格式。r_egg 的典型用途包括创建独立的可执行文件、生成位置无关的原始 shellcoderaw egg注入运行中的进程、或修补磁盘上的二进制文件。libr/egg/README特别强调生成的代码目前未做优化但保证在任何位置执行都是安全的这对注入场景至关重要。编译器的核心实现在 libr/egg/egg_lang.c不同目标后端的发射器分别位于 libr/egg/emit_x86.c、libr/egg/emit_x64.c、libr/egg/emit_arm.c、libr/egg/emit_arm64.c、libr/egg/emit_esil.c、libr/egg/emit_trace.c 等。二、快速上手一条命令编译示例程序libr/egg/t/README.md给出了每个示例文件的通用编译命令ragg2 -a arch -b bits -k os -s FILE.r参数含义与 libr/main/ragg2.c 中的usage()完全对应参数含义-a arch选择架构后端x86、mips、arm、ppc以及实验性后端 trace、esil-b bits寄存器宽度32、64 等-k os目标操作系统内核linux、bsd、osx、w32-s显示汇编输出show assembler即把生成的汇编文本打印到 stdout-o file输出到指定文件-f format输出格式raw、c、pe、elf、mach0、python、javascript-F输出宿主原生格式osxmach0、linuxelf…-x直接执行-e expr直接从命令行字符串取 egg 程序而不是读文件-i shellcode使用内置 shellcode 插件-L列出-L列出全部插件shellcodes 与 encoders-p padding编译后追加填充如n10s32nop/trap/a/sequence 序列-r输出原始字节而不是 hexpairs-z以 C 字符串语法输出-v显示版本2.1 推荐实验后端libr/egg/t/README.md特别推荐了四个用于学习和实验的后端组合-a trace伪汇编直接镜像解析器发射的 IR是理解编译器内部行为的最佳途径-a esil基于栈的中间语言可读且能被 r2 的 ESIL 虚拟机直接消费r2 里用ae命令执行-a x86 -b 32/64原生 x86 / x86-64 汇编-a arm -b 32/64原生 ARM / AArch64 汇编。2.2 官方示例的编译与检查命令libr/egg/t/README.md给出的三个最小示例在仓库根目录下执行因为t/是相对路径ragg2 -a esil -s t/hi.r ragg2 -a trace -s t/arith.r ragg2 -a x86 -b 64 -s t/regs.r第一条把hi.r编译成 ESIL 中间语言并打印第二条用 trace 后端观察算术表达式如何变成一串 IR 调用第三条把直接寄存器操作编译成 x86-64 汇编。这三个命令几乎覆盖了 r_egg 输出的三种形态是理解整个工具链最快的入口。三、官方示例逐一点评9 个.r文件libr/egg/t/目录包含 9 个自包含示例libr/egg/t/README.md对它们各自的定位做了说明下面逐个结合源码展开。3.1 hi.r —— 最小“write then exit”程序hi.r 是能完成“输出后退出”的最小程序/* smallest hello world style program: call write() then exit() */ writesyscall(4); exitsyscall(1); mainglobal(128) { .var0 hi!\n; write (1, .var0, 4); exit (0); }要点writesyscall(4);和exitsyscall(1);把write、exit声明为 Linux i386 系统调用号4 和 1mainglobal(128)定义一个全局可见函数栈帧大小为 128 字节global后的数字即stackframesize.var0是位于帧偏移 0 的局部变量这里存字符串字面量hi!\nwrite (1, .var0, 4)以函数调用语法触发 syscall 调用stdout1长度 4。3.2 hello.r —— while 循环版 Hello Worldhello.r 演示循环与复合赋值writesyscall(4); exitsyscall(1); mainglobal(128) { .var0 4; .var4 Hello World\n; while (.var0 0) { write (1, .var4, 12); .var0 - 2; } exit (0); }与hi.r的区别在于.var0从 4 开始每次循环执行write后减 2循环条件为.var0 0因此共输出两次 Hello World\n12 字节。这里同时用到了.var0与.var4两个帧槽、字符串字面量、while循环和复合赋值-。3.3 exit.r —— 纯内联汇编exit.r 完全不使用高层语法只用:前缀的内联汇编/* any line starting with : is copied verbatim into the * assembler stream, no high-level translation happens. */ mainglobal(128) { : nop : mov eax, 1 : push eax : int 0x80 }它以 Linux i386 原始 syscall 方式调用exit(1)mov eax, 1装系统调用号push eax传参int 0x80陷入内核。这个文件印证了libr/egg/README中“Inline assembly”一节的规则凡是以:开头的行原样透传给汇编器不做任何高层翻译。同节还给出了: jmp 0x8048400、: .byte 33,44等更多透传示例以及用:label_name:定义标签配合goto(label_name)的用法。3.4 write.r —— syscall 声明与字符串字面量write.r 只声明一个 syscallwritesyscall(4); mainglobal(128) { .var1 sup; write (1, .var1, 3); }注意这里用的是.var1而非.var0字符串 sup 的 3 个字节写入帧偏移 1 处然后作为write的第二个参数传给系统调用。它演示了字符串可以存放在任意帧偏移。3.5 arith.r —— 算术表达式与运算符优先级arith.r 覆盖了算术与位运算以及优先级mainglobal(32, 0) { .var0 5 3; /* 8 */ .var4 10 - 4; /* 6 */ .var8 2 3 * 4; /* precedence: 2 12 */ .var12 1 | 2 | 4; /* bitwise or chain */ .var16 0xff 0x0f; /* bitwise and */ .var20 7 ^ 3; /* xor */ /* compound operators */ .var24 10; .var24 5; .var24 * 2; }注释里的precedence: 2 12明确说明*的优先级高于。该文件注释还提示用ragg2 -a esil -s t/arith.r可以观察每个表达式如何变成一串 ESIL token——这是观察编译器优先级处理的最直观方式。另外注意mainglobal(32, 0)带两个数字第一个是栈帧大小stackframesize32 字节第二个是静态区大小staticframesize0 字节用于存放常量/字符串。这一点在libr/egg/README的函数签名一节有完整定义nametype(stackframesize,staticframesize) { body }libr/egg/egg_lang.c的第 486-498 行实现了该解析逻辑遇到逗号则拆分两个数字egg-lang.stackframe num; egg-lang.stackfixed num2;随后调用后端的e-frame()生成帧分配代码。3.6 regs.r —— 直接访问原生寄存器regs.r 演示.%name语法mainglobal(16, 0) { .%rax 1; /* write constant into rax */ .%rcx .%rax; /* copy rax into rcx */ .%rdx .%rax 41; /* rax 41 - rdx */ .%rax .%rdx * 2; /* rax rdx * 2 */ }.%name会把名字原样复制进指令流是对具体寄存器无歧义的访问方式。文件注释强调了它和另外三种.变量的区别.varN—— 帧槽局部变量.regN—— 按索引访问的通用寄存器用户通过alias定义的别名.%name—— 原样透传的原生寄存器名如.%rax、.%ecx、.%x0。该机制在 libr/egg/egg_lang.c 的r_egg_mkvar_name()中有明确实现if (*name %)时跳过别名表把名字逐字交给后端r_strbuf_set (out, name 1)与.varN/.regN/alias 的分支完全分开。同一函数还实现了.ret返回寄存器由e-retvar决定如 x86 的 eax、ARM 的 r0、.fix、.argN、.rargN等变量类型的解析。3.7 alias.r —— 用 alias 定义寄存器别名alias.r 演示跨架构可移植的别名机制counteralias(rax) limitalias(rcx) mainglobal(16, 0) { .counter 0; .limit 10; .counter .limit - 1; .counter 1; }namealias(value)声明之后出现的任何.name都解析为value。这有两个实际用途给目标寄存器起有语义的名字如counter/limit无需改动后端在寄存器集不同的架构间保持程序可移植。文件注释强调未定义的.name是编译错误除非它通过alias定义过或带%前缀见 regs.r。因此alias.r只能用-a esil或-a x86 -b 64编译ragg2 -a esil -s t/alias.r、ragg2 -a x86 -b 64 -s t/alias.r因为 esil 与 x86-64 都识别rax/rcx寄存器。libr/egg/README还给出别名在汇编层的本质alias 会被翻译成汇编的equ语句即汇编器层面的关键字重定义例如AF_INETalias(2);、printfalias(0x8053940);。别名语法对注入场景尤其重要因为 r_egg 目前不支持链接系统库但可以把注入目标里每个函数的地址用alias直接定死见libr/egg/README的 Libraries 一节。3.8 cond.r —— if / if-else / while 与 ESIL 跳转cond.r 覆盖全部条件语句形态mainglobal(16, 0) { .var0 5; /* execute body only if .var0 0 */ if (.var0 0) { .var0 99; } /* execute body only if .var0 ! 0 */ if (.var0 ! 0) { .var0 42; } /* truthy form - body runs when .var0 is non-zero */ if (.var0) { .var0 1; } /* countdown loop: keep looping while .var0 is non-zero */ .var0 3; while (.var0) { .var0 - 1; } }支持的条件形式包括if (cond)、if (cond x)/if (cond ! x)、非零即真的 “truthy” 形式以及while循环条件在每次迭代前重新求值。这个文件注释还详细解释了 ESIL 后端如何处理跳转——每个函数被编译成一个由逗号分隔 token 组成的单条表达式标签在 finalize 阶段被解析为数字的 word 索引从而输出纯 ESIL可以直接喂给 r2 的ae命令执行X,a1,OP,?{,N,GOTO,} 有序比较 X,a1,,$z,[!,]?{,N,GOTO,} 相等性测试 N,GOTO 无条件跳转其中 N 是目标在表达式内的 word 位置。while 循环还会在循环体底部刷新 a1从循环变量重新加载然后跳回循环开头让同一个比较基于更新后的值重新执行。这正是 r_egg 生成的 ESIL 能被 r2 ESIL 虚拟机直接消费的关键设计。3.9 customsyscall.r —— 覆盖 syscall 默认函数体customsyscall.r 演示syscall()覆盖机制exitsyscall(1); syscall() { : int3 } mainglobal() { exit (2); }syscall() { ... }定义会替换后端默认生成的 syscall 序言。本例中所有 syscall 都陷入int3而不是真正发起系统调用——非常适合在调试器/模拟器里拦截验证调用点。libr/egg/README的 Syscalls 一节给出了更完整的形态——既能覆盖整个调用体也能用.arg读取参数exitsyscall(1); syscall() { : mov eax, .arg : int 0x80 } mainglobal() { exit (0); }从 libr/egg/egg_lang.c 的实现看当调用一个已声明的 syscall 名时解析器会先注释set syscall args、调用e-syscall_args()布置参数若存在syscall覆盖体egg-lang.syscallbody则把覆盖体逐字符重新喂回解析器否则使用后端默认的e-syscall()生成的代码。这就是“覆盖”在实现层面的真实路径。四、r_egg 语言速查表全解libr/egg/t/README.md提供了一张精炼的语法速查表下面逐条展开并补充libr/egg/README中的完整变量表与函数类型表。4.1 注释与内联汇编/* comment */ // comment # comment三种注释风格等价libr/egg/README的 Comments 一节确认。: raw asm line以:开头的行原样透传给汇编器不做高层翻译。可透传的内容包括指令、.byte 33,44等汇编伪指令以及:label_name:形式的标签定义。4.2 函数与声明 指令族语法说明nameglobal(S,F) { body }定义函数S 字节栈帧F 字节常量区namesyscall(N);把 name 声明为系统调用号 Nnamealias(value)文本别名.name→ value汇编层equnamefastcall(addr)通过 fastcall 调用约定调用 addr 处函数syscall() { body }覆盖生成的 syscall 函数体libr/egg/README的 Function types 一节列出了全部六种函数类型alias别名、data函数体放入.data段、inline调用时内联展开函数体、global使符号全局可见、fastcallfastcall 调用约定、syscallsyscall 调用约定签名。4.3 变量访问语法含义.var0, .var4, .varN帧偏移 N 处的局部变量.arg0, .arg4函数参数.ret返回寄存器retvarx86 为 eaxarm 为 r0.regN按索引访问的通用寄存器.%rax, .%ecx, .%x0, ...原样透传的原生寄存器.foo先定义fooalias(...)别名解析后的寄存器/符号其他任何.name编译错误libr/egg/README的 Variables 一节还补充了.fix静态区/常量槽位、.bp、.pc、.sp等特殊变量。返回值的约定是返回值存放在 a0 寄存器调用函数时或直接书写变量名而不赋值时如add(3,4);后跟.var0;会设置该寄存器。4.4 表达式与控制流语法含义var expr赋值var expr, -, *, /复合赋值var *addr解引用读内存fn(arg1, arg2, ...)函数 / syscall 调用if (cond) { }条件分支含/!/truthy 形式while (cond) { }条件循环goto(addr)/goto(label)跳转break()/break触发当前架构的 trap 指令x86 上为int3libr/egg/README的 Traps 一节确认语言对break()的调用会触发所选架构的emit_trap回调x86 上编译为int3。五、深入原理从源码看 r_egg 的设计约束5.1 单遍编译one-pass与栈帧libr/egg/README明确指出 r_egg 是单遍编译器代码按“流”的方式顺序编译因此函数定义必须在使用之前按调用顺序定义函数开头必须写清楚栈帧大小否则后端无法分配局部变量区。从 libr/egg/egg_lang.c 可以看到解析global(S,F)时立即计算stackframe stackfixed并调用e-frame()生成帧建立代码函数结束时第 900 行附近调用e-frame_end()并复位stackframe 0。如果栈帧为 0参数访问会走不同的取值路径第 625 行if (egg-lang.stackframe 0) e-get_var (egg, 1, out, 4)这就是为什么mainglobal()无参数形式也能工作。5.2 trace 后端编译器的“日志模式”-a trace对应 libr/egg/emit_trace.c。它的输出形如frame (16) set (var0, 5, 0) ... call ([printf]) syscall (4) frame_end (16, 0)该后端把每一次发射动作frame、set、call、syscall、frame_end、equ等打印成一行可读的伪汇编寄存器模型是虚拟的a0~a4、sp、bp、t0与具体硬件解耦。这使它成为观察“解析器到底发射了什么”的最佳教学工具——ragg2 -a trace -s yourprogram.r因而被libr/egg/README的 Tracing 一节推荐为调试 r_egg 程序的手段。5.3 esil 后端与 r2 的联动ESILEvaluable Strings Intermediate Language是 radare2 的基于栈的中间语言r2 可以用ae命令执行。ESIL 后端的输出是一条逗号分隔 token 组成的表达式函数内所有跳转标签都被解析为数值 word 索引因此输出是纯 ESIL、可直接消费。这让用户可以在不接触机器码的情况下先在 r2 里单步验证 r_egg 程序的逻辑条件、循环、算术再切换到真实架构。5.4 从汇编到二进制ragg2 的完整输出链路libr/main/ragg2.c 的create()函数r_egg 编译得到汇编文本 → r_asm 汇编为机器码 →r_bin_create()按-f指定的格式elf、pe、mach0、raw…打包。-F则让 ragg2 自动选择宿主操作系统对应的原生格式linuxelf、osxmach0一步生成可执行文件。六、动手实验建议的练习路线跑通最小样例仓库根目录执行ragg2 -a trace -s libr/egg/t/hi.r观察 write/exit 两个 syscall 的 IR 输出再执行ragg2 -a esil -s libr/egg/t/cond.r把生成的 ESIL 表达式用 r2 的ae逐 token 执行验证 if/while 的跳转逻辑。观察优先级ragg2 -a esil -s libr/egg/t/arith.r对比2 3 * 4与(2 3) * 4生成的 token 顺序差异。对比后端对同一个libr/egg/t/regs.r分别执行ragg2 -a esil -s ...与ragg2 -a x86 -b 64 -s ...理解虚拟寄存器a0~a4与真实寄存器rax/rcx/rdx的映射关系。生成真实二进制ragg2 -a x86 -b 32 -F -o hello t/hello.r生成 Linux ELF 并执行验证循环计数逻辑应输出两行 Hello World。改写练习把libr/egg/t/customsyscall.r的int3覆盖体换成真实的: mov eax, 1: int 0x80即可把“假装 syscall”变成真正的exit调用——这是理解syscall覆盖机制最好的实验。七、小结libr/egg/t/的 9 个示例构成了 r_egg 语言的完整“最小教学集”从最小程序hi.r、循环hello.r、纯内联汇编exit.r、字符串与 syscall 声明write.r、算术与优先级arith.r、原生寄存器regs.r、别名alias.r、条件与循环及 ESIL 跳转cond.r到 syscall 体覆盖customsyscall.r。配合ragg2 -a trace观察 IR、-a esil进入 r2 虚拟机验证逻辑、-a x86/arm落地真实汇编读者可以在几分钟内走通“编写 → 编译 → 验证 → 生成二进制”的完整闭环并把 r_egg 的能力迁移到 shellcode 编写、进程注入与二进制补丁等真实场景中。参考文件索引示例程序目录libr/egg/t/hi.r、hello.r、exit.r、write.r、arith.r、regs.r、alias.r、cond.r、customsyscall.r语言总文档libr/egg/READMEragg2 CLI 实现与用法libr/main/ragg2.c解析器实现libr/egg/egg_lang.c后端发射器libr/egg/emit_trace.c、libr/egg/emit_esil.c、libr/egg/emit_x86.c、libr/egg/emit_x64.c、libr/egg/emit_arm.c赞分享逆向工程网络安全【免费下载链接】radare2UNIX-like reverse engineering framework and command-line toolset项目地址https://gitcode.com/gh_mirrors/ra/radare2点击查看免费下载相关推荐deepin-community/kernel热补丁技术运行时内核更新deepin community/kernel热补丁技术运行时内核更新 引言零停机时间的内核修复革命 你是否经历过这样的困境生产服务器发现了一个严重的内核教程从汇编到高级语言程序编译与执行的完整链路终极指南从汇编到高级语言程序编译与执行的完整链路终极指南 你是否曾经好奇当你点击一个程序图标时计算机内部究竟发生了什么从你编写的高级语言代码到最终在CPU上运文档教程Vosk API多语言字符编码终极实战从乱码到完美输出的完整指南明明语音识别准确率很高为什么输出结果全是乱码这是许多开发者在使用Vosk API处理中文、日文等多语言语音识别时的共同困惑。作为一名经历过无数次编码折磨人工智能语音上一篇3分钟上手Google Cloud Storage用PHP轻松管理云存储对象下一篇jcalaBlog 事务管理指南Transactional 在博客发布中的正确用法创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表