浙江省网站建设_网站建设公司_前端开发_seo优化
2026/1/7 15:39:47 网站建设 项目流程

Rust in your disassembler

今年在Atr2con会议上,我将展示如何破解一个用Rust编写的CrackMe二进制文件。由于会议是在线形式,我选择主要以大型演示的形式录制,仅使用极少的幻灯片。然而,你们中的一些人可能希望了解一些细节或理论。Rust编译器所做的事情非常巧妙且有趣。

字符串是胖指针

这与C语言不同,在C语言中,你的字符串实际上只是一个指向字符的简单指针。在Rust中,你的内联字符串将指向一个包含以下内容的结构:

  • 长度:你字符串的长度。不需要像C语言那样在字符串末尾用\0结尾。
  • 一个指向字符串字符的指针

Radare2将这些字符串显示为"reloc.fixup.xxxx"。例如,下面的字符串显然位于地址0x5e790。如果我们查看该地址的字节,可以清楚地看到指针(6bc0 0400 -> 0x04c06b),然后是长度(0x25)。我们确认字符位于0x04c06b。

;0x5e790;"k\xc0\x04" 0x00008b6a 488d051f5c.. lea rax,reloc.fixup.Space_Station_Airlock_Control_S [0x00008b40]> px10 @ 0x5e790 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x0005e790 6bc0 0400 0000 0000 2500 k.......%. [0x00008b40]> px0x25 @ 0x04c06b - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x0004c06b 5370 6163 6520 5374 6174 696f 6e20 4169 Space Station Ai 0x0004c07b 726c 6f63 6b20 436f 6e74 726f 6c20 5379 rlock Control Sy 0x0004c08b 7374 656d 0a stem.

单态化

你可能听说过多态。这是指一个给定的函数能够操作不同的类型。

单态化则“相反”:我们确保每种类型严格对应一个函数。

Rust编译器会自动使用单态化——这是出于优化的原因,比如不需要虚函数表。当它遇到泛型函数时,在底层,它实际上会为每种类型生成一个函数的汇编代码。开发者看不到这一点,这是编译器的工作。

// 泛型函数fnsquare<T:std::ops::Mul<Output=T>+Copy>(x:T)->T{x*x}fnmain(){leta=square(3i32);// 生成 square::<i32>letb=square(2.5f64);// 生成 square::<f64>}

编译器也经常为闭包这样做。

闭包

闭包是一个捕获其环境的函数。在下面的例子中,add_x是一个闭包。它捕获了环境,其中x = 5。调用add_x(3)返回 8。

fnmain(){letx=5;letadd_x=|y|x+y;println!("{}",add_x(3));}

Rust编译器的具体行为很大程度上取决于优化级别。假设你用-C opt-level=0编译这段代码。

[0x00007960]> pdi @ sym.main::main::h8f1c9e3495794b54 0x00007b90 sym.main::main::h8f1c9e3495794b54: 0x00007b90 4883ec68 sub rsp, 0x68 0x00007b94 c7442404050. mov dword [rsp + 4], 5 0x00007b9c 488d442404 lea rax, [rsp + 4] 0x00007ba1 4889442408 mov qword [rsp + 8], rax 0x00007ba6 488d7c2408 lea rdi, [rsp + 8] 0x00007bab be03000000 mov esi, 3 0x00007bb0 e85b000000 call sym.main::main::__u7b__u7b_closure_u7d__u7d_::h0ea7a13e6c14ebb2 0x00007bb5 89442464 mov dword [rsp + 0x64], eax

编译器为我们的主函数生成了一个特定的闭包。它被命名为:sym.main::main::__u7b__u7b_closure_u7d__u7d_::h0ea7a13e6c14ebb2。该闭包的参数是:

  • 第一个参数(在rdi中):rsp + 8。这是闭包环境。它包含一个指向rsp + 4的指针,而该地址存放着值 5。
  • 第二个参数(在esi中):3。这是传递给闭包的参数

在闭包的汇编代码中,我们有一些针对整数操作的指令。

0x00007c11 488b07 mov rax, qword [rdi] 0x00007c14 0330 add esi, dword [rax] ... 0x00007c1f 8b442404 mov eax, dword [rsp + 4]

如果闭包被用于浮点数,则会生成另一个闭包,并带有不同的指令。这就是单态化。我们可以通过创建一个同时适用于浮点数整数的闭包来更好地观察这一点:

usestd::ops::Add;fnget_adder<T>(x:T)->implFn(T)->TwhereT:Add<Output=T>+Copy,{move|y|x+y}fnmain(){letadd_int=get_adder(5);letadd_float=get_adder(5.0);println!("{}",add_int(3));println!("{}",add_float(4.5));}

现在,如果我们检查汇编代码,会注意到编译器生成了2个get_adder函数。

[0x00007960]> afl~main 0x00007c00 11 sym.main::get_adder::hba4b3b420a6e866b 0x00007c10 13 sym.main::get_adder::hbe8e2ff9c2a8357d 0x00007c20 122 sym.main::get_adder::__u7b__u7b_closure_u7d__u7d_::h6ff0b01bf90189e9 0x00007c40 117 sym.main::get_adder::__u7b__u7b_closure_u7d__u7d_::h72c4b1cd2fd53136 0x00007c60 1251 sym.main::main::h8f1c9e3495794b54

如果我们检查第一个get_adder的汇编代码,会发现它适用于浮点数:

0x00007c20 sym.main::get_adder::__u7b__u7b_closure_u7d__u7d_::h6ff0b01bf90189e9: 0x00007c20 50 push rax 0x00007c21 0f28c8 movaps xmm1, xmm0 0x00007c24 f20f1007 movsd xmm0, qword [rdi] 0x00007c28 488d3d19ee0. lea rdi, [rip + 0x4ee19] 0x00007c2f e87cfeffff call sym.__f64_as_core::ops::arith::Add_::add::hf4bd57df382c73d6 0x00007c34 58 pop rax 0x00007c35 c3 ret

而第二个get_adder适用于整数:

0x00007c40 sym.main::get_adder::__u7b__u7b_closure_u7d__u7d_::h72c4b1cd2fd53136: 0x00007c40 50 push rax 0x00007c41 8b3f mov edi, dword [rdi] 0x00007c43 488d15feed0. lea rdx, [rip + 0x4edfe] 0x00007c4a e871feffff call sym.__i32_as_core::ops::arith::Add_::add::h471fa892cd8f10a4 0x00007c4f 59 pop rcx 0x00007c50 c3 ret

脱糖

在Rust中,开发者通常调用像obj.blah()这样的方法。Rust编译器在内部将其转换为更明确(但不那么“甜”)的形式:class::blah(&obj)

letobj=MyObject{value:10};obj.blah();// 语法糖

查看下面生成的汇编代码:

; set value = 10 0x00007bc4 c744240c0a00. mov dword [rsp + 0xc], 0xa ; put obj in RDI (1st argument of blah()) 0x00007bcc 488d7c240c lea rdi, [rsp + 0xc] ; call blah() with address of obj as argument 0x00007bd1 e8baffffff call sym.sugar::MyObject::blah::h059bdb1e25fa70c4

在Rust中,用于显示内容的函数println!是一个宏,而不是一个“真正的”函数。因此,在汇编代码中,你不会看到对println!的调用(这个“函数”并不存在),而是看到:

  • 设置格式字符串和参数。
  • 构建 Arguments 结构体。
  • 调用dbg._print,它实际上会调用dbg.write_fmt,dbg.write

我建议你阅读这篇文章以获取更多细节。

下面的汇编代码是由一个 Hello World 程序生成的。

0x00007b20 sym.print::main::h13b5f0e40e4e7865: 0x00007b20 4883ec38 sub rsp, 0x38 ; 在栈上为 Arguments 对象分配空间 0x00007b24 488d7c2408 lea rdi, [rsp + 8] ; 指向字符串 "Hello World" 的胖指针 0x00007b29 488d35d0a30. lea rsi, [rip + 0x4a3d0] 0x00007b30 e83bffffff call sym.core::fmt::rt::__impl_core::fmt::Arguments_::new_const::ha519b55ee7e59acf 0x00007b35 488d7c2408 lea rdi, [rsp + 8] ; 通过 GOT 调用 dbg._print 例程 0x00007b3a ff1550cf0400 call qword [rip + 0x4cf50] 0x00007b40 4883c438 add rsp, 0x38 0x00007b44 c3 ret

注意,对dbg._print的调用是间接的:dbg._print的 GOT(全局偏移表)条目位于rip + 0x4cf50。Radare2 将其显示为一个reloc.fixup

[0x00007b20]> px10 @ reloc.fixup.UHSHxHH_ - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00054a90 c0430200 00000000 0000 .C........ [0x00007b20]> pd10 @ 0x0243c0 ;-- std::io::stdio::_print::h915f3273edec6464: ;DATA XREF from reloc.fixup.UHSHxHH_ @ ┌206: dbg._print (int64_t arg1); │`- args (rdi) vars (13: sp[0x18..0x80]) │ 0x000243c0 55 push rbp ; sync.rs:0:13 ; void _print();

— Cryptax
CSD0tFqvECLokhw9aBeRqtFmyXmRsBISmPE7edMOI2OK8XHtAZcBhDmHp75CbukkcV4OE5l5gwOQpJ1KPqBQB2mY1bHR5IDRIauIsx0/nRk=
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询