濮阳市网站建设_网站建设公司_Logo设计_seo优化
2025/12/22 20:16:32 网站建设 项目流程

Rust逆向工程实战:从闭包到宏的底层原理分析

在今年的Atr2con会议上,我将演示如何破解一个用Rust编写的CrackMe二进制文件。由于会议是在线举行的,我选择将其主要录制为一个大演示,只有非常少的幻灯片。然而,你们中的一些人可能想阅读一些细节/理论。Rust编译器所做的事情非常巧妙且有趣。

字符串是“胖指针”

它不像在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-   909192939495969798999A9B9C9D9E9F0123456789ABCDEF
0x0005e790  6bc0 0400 0000 0000 2500                 k.......%.
[0x00008b40]> px 0x25 @0x04c06b
-offset-   6B6C6D6E6F707172737475767778797ABCDEF0123456789A
0x0004c06b  5370 6163 6520 5374 6174 696f 6e20 4169  SpaceStationAi
0x0004c07b  726c 6f63 6b20 436f 6e74 726f 6c20 5379  rlockControlSy
0x0004c08b  7374 656d 0a                             stem.

单态化

你可能听说过多态性。这是指一个给定的函数能够操作不同的类型。
单态化则“相反”:我们确保每种类型严格对应一个函数。

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

// generic
fn square<T: std::ops::Mul<Output = T> + Copy>(x: T) -> T {x * x
}fn main() {let a = square(3i32); // generates square::<i32>let b = square(2.5f64); // generates square::<f64>
}

编译器也经常为闭包做同样的事情。

闭包

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

fn main() {let x = 5;let add_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  c744240405000000   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]

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

use std::ops::Add;fn get_adder<T>(x: T) -> impl Fn(T) -> T
whereT: Add<Output = T> + Copy,
{move |y| x + y
}fn main() {let add_int = get_adder(5);let add_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  488d3d19ee0400         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  488d15feed0400         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)

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

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

; set value = 10
0x00007bc4  c744240c0a000000   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! 的调用(这个“函数”并不存在),而是:

  1. 设置格式化字符串和参数。
  2. 构建 Arguments 结构。
  3. 调用 dbg._print,它实际上调用 dbg.write_fmtdbg.write...

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

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

0x00007b20 sym.print::main::h13b5f0e40e4e7865:
0x00007b20  4883ec38           sub rsp, 0x38
; allocate place for Arguments object on the stack
0x00007b24  488d7c2408         lea rdi, [rsp + 8]
; fat pointer to the string Hello World
0x00007b29  488d35d0a30400     lea rsi, [rip + 0x4a3d0]
; instantiate the Arguments object
0x00007b30  e83bffffff         call sym.core::fmt::rt::__impl_core::fmt::Arguments_::new_const::ha519b55ee7e59acf
0x00007b35  488d7c2408         lea rdi, [rsp + 8]
; call to the dbg._print routine via GOT
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-   909192939495969798999A9B9C9D9E9F0123456789ABCDEF
0x00054a90  c0430200 0000 0000 0000                 .C........
[0x00007b20]> pd 10 @0x0243c0
;-- std::io::stdio::_print::h915f3273edec6464:
;DATA XREF from reloc.fixup.UHSHxHH_ @
┌ 206: dbg._print (int64_t arg1);
│           ; arg int64_t arg1 @ rdi
│           ; var int64_t var_18h @ sp+0x18
│           ; var int64_t var_80h @ sp+0x80
│           0x000243c0      55             push rbp                    ; sync.rs:0:13 ; void _print();

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

公众号二维码

公众号二维码

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

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

立即咨询