Rust逆向学习 (2)

文章目录

  • Guess a number
    • 0x01. Guess a number .part 1
      • line 1
      • loop
      • line 3~7
      • match
    • 0x02. Reverse for enum
    • 0x03. Reverse for Tuple
    • 0x04. Guess a number .part 2
    • 0x05. 总结

在上一篇文章中,我们比较完美地完成了第一次Rust ELF的逆向工作,但第一次编写的Rust程序毕竟只使用了非常有限的几种Rust特性,Rust还有很多的东西没有涉及,像是流程控制、泛型、Trait等。这些内容我们将在本文以及以后的文章中一一进行学习与探索。

Guess a number

0x01. Guess a number .part 1

本文从一个跳跃不是很大的程序开始,也就是一个真正的猜数字小程序:

use std::cmp::Ordering;
use std::io;    // prelude
use rand::Rng;  // traitfn main() {let secret = rand::thread_rng().gen_range(1, 101);    // ThreadRng: random number generatorloop {println!("Please guess a number between 1 and 100:");let mut guess = String::new();io::stdin().read_line(&mut guess).expect("Cannot read a line!");println!("Your guess is: {}", guess);let guess: u32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,};match guess.cmp(&secret){Ordering::Less => println!("Too small."),Ordering::Greater => println!("Too large."),Ordering::Equal => { println!("You win."); break;},}}
}

这里要注意,使用上一篇文章中的编译工具网站时需要添加库并在代码中通过extern crate rand手动加载rand库,否则会编译失败。

考虑到效率问题,本文对于上述代码的反汇编以IDA的反汇编结果为主,汇编代码分析为辅。

line 1

第一行中thread_rng方法返回ThreadRng实例,也就是使用于单个线程的随机数产生器实例,随后将其作为参数1(即self),参数2和参数3分别为范围的下界和上界。通过汇编代码可以发现,Range这个对象需要两个寄存器传递。通过查看Rust官方库源码也可以发现,Range实际上也就只有开始和结尾这两个属性值:

pub struct Range<Idx> {/// The lower bound of the range (inclusive).#[stable(feature = "rust1", since = "1.0.0")]pub start: Idx,/// The upper bound of the range (exclusive).#[stable(feature = "rust1", since = "1.0.0")]pub end: Idx,
}

gen_range方法以常规的方式使用rax返回了生成的随机数值。

随后,一个drop_in_place直接删除了ThreadRng实例,可见Rust对于生命周期的管理非常严格,后续代码已经没有使用ThreadRng实例的代码,因此Rust直接就将其删除了,尽最大可能减少对象重用与悬垂指针引用的可能。

loop

在Rust的反汇编界面中,continue很少见到,因为对于一个循环而言,其内部很有可能存在生命周期在循环之内的对象,因此即使Rust代码中写continue,Rust也需要首先将循环中创建的对象删除之后再开始新一轮循环。这也就导致IDA的反汇编界面中可能会出现很多goto

line 3~7

println!的特征很好识别,Arguments::new_v1_print一出,就知道肯定又是一次输出,不过输出的具体字符串内容直接查看反汇编界面无法确定,不过在汇编代码中也很好找。随后的String::new等也非常正常。

match

上述代码一共有两个match语句,第一个是将字符串parse的结果进行判断,替换了上一篇文章中的expect。这里parse函数的返回值是一个枚举对象Result<F, F::Err>。我们知道Rust的枚举对象是一个很强大的结构,比C/C++中的枚举对象好用很多,这是因为Rust的枚举对象可以理解成一个Key有限且确定的Map,选择一个Key之后还能够根据Key指定的数据类型自由设置Value。在这里我们不妨研究一下,Rust中的枚举对象是如何组织的。

0x02. Reverse for enum

下面通过一个简单的程序对枚举类型进行逆向分析。

#[derive(Debug)]
pub enum Student {Freshman(String),Sophomore(String),Junior(String),Senior(String),
}pub fn get_student(grade: i32, name: String) -> Option<Student> {match grade {1 => Some(Student::Freshman(name)),2 => Some(Student::Sophomore(name)),3 => Some(Student::Junior(name)),4 => Some(Student::Senior(name)),_ => None}
}pub fn main() {let x = get_student(4, "CoLin".to_string()).unwrap();println!("{:?}", x);
}

上述代码定义了一个枚举类型。首先来看get_student方法:

可以看到,在反汇编界面中,IDA将match语句识别为switch语句,通过汇编代码的分析也能够很容易地发现跳表的存在。

通过查看main函数的方法调用,可以获得get_student方法的参数分别为:Student对象指针、grade参数、name参数。在switch语句中,我们发现每一个分支都有大量的值传送指令,含义未知,但我们可以通过函数调用前后获取到枚举类型的大小与内容。


经过分析,获取到了枚举对象的内容如上图所示。从函数内容等处可以推断出,枚举对象的第一个值3表示的是枚举对象grade的关键字索引,这里由于返回的是Student::Senior,索引为3,也即枚举对象中的4个索引值对应了0、1、2、3这4个索引值。后面还有3个值,其中有字符串指针和字符串长度,经过测试发现,String对象占0x18大小内存,偏移0x8为字符串指针,偏移0和0x10均为字符串长度。

之后,笔者修改了Student枚举类型的定义,在每一项后面加上了一个i32,经过调试发现枚举类型的属性偏移如下:

0x0         枚举索引
0x4         i32
0x8~0x20    String

位于后面的i32类型反而在内存中更加靠前了。笔者推测这可能与Rust对tuple的内存排布有关,考虑到枚举索引很少有超过1个字节(不然就意味着有超过255个分支),使用后面4个字节能节省一定的内存空间。不过无论tuple是如何排布的,Rust的枚举类型在内存中的布局现在已经很清楚了,就是索引值+内容

不过既然都已经看到了tuple的不寻常,接下来不妨也对其进行一番研究。

0x03. Reverse for Tuple

下面将尝试通过数个Tuple的反编译结果分析Tuple的内存布局。众所周知,Tuple就是若干个数据的集合,这些数据之间没有什么明确的关联,只有一个Tuple将它们约束在一个集合中。

pub fn main() {let x = (2, 3, 5, 7, 11, String::new());
}

对于上述代码逆向的结果如下:

example::main:sub     rsp, 72lea     rdi, [rsp + 48]call    alloc::string::String::newmov     dword ptr [rsp], 2mov     dword ptr [rsp + 4], 3mov     dword ptr [rsp + 8], 5mov     dword ptr [rsp + 12], 7mov     dword ptr [rsp + 16], 11mov     rax, qword ptr [rsp + 48]mov     qword ptr [rsp + 24], raxmov     rax, qword ptr [rsp + 56]mov     qword ptr [rsp + 32], raxmov     rax, qword ptr [rsp + 64]mov     qword ptr [rsp + 40], raxmov     rdi, rspcall    qword ptr [rip + core::ptr::drop_in_place<(i32,i32,i32,i32,i32,alloc::string::String)>@GOTPCREL]add     rsp, 72ret

从相对于rsp的偏移量可以看出Tuple的排布情况,上述Tuple的内存排布顺序与数据的定义顺序相同。

但对于下面一个Tuple而言就不同了:

pub fn main() {let x = (2, 3, 5, 7, 11, String::new(), "CoLin");
}

逆向的结果为:

example::main:sub     rsp, 88lea     rdi, [rsp + 64]call    alloc::string::String::newmov     dword ptr [rsp + 24], 2mov     dword ptr [rsp + 28], 3mov     dword ptr [rsp + 32], 5mov     dword ptr [rsp + 36], 7mov     dword ptr [rsp + 40], 11mov     rax, qword ptr [rsp + 64]mov     qword ptr [rsp], raxmov     rax, qword ptr [rsp + 72]mov     qword ptr [rsp + 8], raxmov     rax, qword ptr [rsp + 80]mov     qword ptr [rsp + 16], raxlea     rax, [rip + .L__unnamed_1]mov     qword ptr [rsp + 48], raxmov     qword ptr [rsp + 56], 5mov     rdi, rspcall    qword ptr [rip + core::ptr::drop_in_place<(i32,i32,i32,i32,i32,alloc::string::String,&str)>@GOTPCREL]add     rsp, 88ret

可以看到,这里是将String::new()产生的String实例放在了开头,随后才是5个i32,最后是&str。至于为什么要这样排列,询问了一个Rust大手子之后,给到的答案是:Rust数据结构和内存排布没有必然关联,Rust编译器可能根据不同的架构进行相应的内存结构调整,说人话就是——不能预判,不是必然顺序排列。不过考虑到对于Tuple的遍历、索引等操作在代码中都是固定的,编译器在编译的时候完全可以将地址偏移与索引值一一对应,不影响正常的索引,但对于反编译则是一个巨大的噩梦,因为你不确定某个索引值的数据到底有多少偏移。另外,如何通过汇编代码对栈空间的布局判断是否存在一个tuple也是一个问题。在定义变量时,一个tuple完全可以拆分为多个变量进行定义,反正在汇编代码中也不会保存临时变量的变量名。这在内存中会表现出来不同吗?

我们还是通过实际验证来解答我们的问题。

pub fn main() {let x = (2, 3, 5, 7, 11, 13);println!("{}", x.0);
}
pub fn main() {let x = 2;let y = 3;let z = 5;let a = 7;let b = 11;let c = 13;println!("{}{}{}{}{}{}", x, y, z, a, b, c);
}

给出上面的两个Rust函数,通过查看6个整数值在内存中的排布可以发现,两者对于6个整数值都是按相同顺序进行排列,从低地址到高地址依次为2、3、5、7、11、13。不过在编译过程中发现,只有当变量被使用时,Rust编译器才会将这个变量编译到ELF中,否则这个变量将不会出现在ELF中。也就是说,我们不能仅仅通过栈内存排布判断源代码中是否定义了Tuple。不过转念一想,这样其实是合理的。Tuple实际上就相当于是一个匿名的结构体实例,想一想C语言中的结构体,实际上也就是将一堆各种类型的数据集合在一起,使用相邻的内存空间保存各个属性而已。定义一个具有两个int类型的C语言结构体,将其在栈内存中分配一个实例空间,与在栈内存中分配两个int类型的变量,在本质上是完全相同的。

因此,我们在对Rust ELF进行逆向分析时,不必纠结源码的编写者是否定义了元组,全部将其看做独立的变量就可以了。

0x04. Guess a number .part 2

好不容易说完了对Rust枚举类型和元组的逆向,接下来让我们回到最开始的那个程序,说到两个match语句。

对于第一个match语句,match的对象是一个枚举类型,在match语句体之内实际上是按照枚举类型进行分支。在汇编语句中,Rust是这样完成分支的:

注意0xCEAC处的指令:mov al, byte ptr [rsp+1D8h+var_C0],第二个操作数是parse方法的返回值,也就是Result<F, F::Err>。考虑到这里的Fu32类型,整个枚举类型占用的空间大小为8字节,因此rax返回的直接就是对象本身的内容(0x??_0000_0000)。第1个字节为枚举索引值,后4个字节为转换后的值。在0xCEAC地址的这条指令将第1个字节赋值给al后进行了比较(cmp rax, 0),这也就是分支的具体实现方法——提取出枚举类型的索引值,根据索引值进行分支。

对于后面cmp方法返回值的match与之类似,本质上使用的也是if-else结构,主要是因为分支数量较少,没有必要使用跳转表,分支逻辑如上图所示。不过不同的是,第一个分支是判断枚举对象索引值是否等于0xFF,即-1。经过调试发现,Ordering::Less对应的枚举索引为-1,Ordering::Greater对应1,Ordering::Equal对应0。而对于每个分支,都只是一个简单的输出语句,这里就不再分析了。

0x05. 总结

在本文中,我们学习了:

  1. Rust的枚举类型在汇编代码层的数据结构实现。
  2. Rust的元组Tuple类型在汇编代码层无法被有效识别,但可将其看做多个独立变量进行分析。
  3. 三个Ordering枚举对象的索引值为-1、0、1,与一般枚举对象索引值从0开始不同。
  4. Rust倾向于当变量不再使用时就删除变量对象,以尽可能地提高安全性。
  5. Rust的元组类型在汇编代码层栈空间的数据排列顺序与元组类型中数据的定义顺序不一定相同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/117798.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

公司电脑屏幕录制软件有什么功能

电脑屏幕录制软件有很多&#xff0c;今天简单说说说它的基础功能和附属功能&#xff1a; 基础功能&#xff1a; 1、屏幕录像 支持对所选电脑的屏幕进行录制&#xff0c;并且支持调整截屏频度、画面质量、单个视频时长等。 2、实时屏幕 可以对对方电脑进行实时屏幕查看&…

linux网络测试命令

文章目录 一.route命令解释二.traceroute命令三.nslookup命令四.本地主机映射文件五.修改网络配置文件六.设置网络接口参数 一.route命令解释 Destination&#xff08;目标&#xff09;&#xff1a;这一列显示要路由的目标网络或主机的IP地址。它标识了数据包要发送到的目的地。…

浙江环保用电计量adw300-hj治污产污生产设备监测

浙江环保用电计量表&#xff0c;浙江环保用电能表&#xff0c;浙江环保督查计量电表&#xff0c;环保设备能耗采集表 企业基本信息 企业名称&#xff1a;XXXXXXXXXXX 企业地址&#xff1a;XX省XX市 工 程 量&#xff1a;X台监测仪表 预计工期&#xff1a;X天 监测点位信息…

实战经验分享:打造千万级直播项目,如何选择适合的长连接技术,告别CRUD开发

前言 其实不管大厂、小厂&#xff0c;做业务开发的同学都知道&#xff0c;写一个功能&#xff0c;有中台&#xff0c;有架构&#xff0c;有API&#xff0c;有SDK&#xff0c;很多可复用的代码直接调一下RPC接口或者一个注解就搞定了复杂的操作&#xff0c;所以很多螺丝钉们都没…

OPC UA:工业领域的“HTML”

OPC UA是工业自动化领域的一项重要的通信协议。它的特点是包括了信息模型构建方法。能够建立工业领域各种事物的信息模型。在工业自动化行业&#xff0c;OPCUA 类似互联网行业的HTTP协议和“HTML”语言。能够准确&#xff0c;可靠地描述复杂系统中各个元素&#xff0c;并且实现…

机器学习中常见的特征工程处理

一、特征工程 特征工程&#xff08;Feature Engineering&#xff09;对特征进行进一步分析&#xff0c;并对数据进行处理。 常见的特征工程包括&#xff1a;异常值处理、缺失值处理、数据分桶、特征处理、特征构造、特征筛选及降维等。 1、异常值处理 具体实现 from scipy.s…

桶装水订水送水小程序开发搭建;

上门送水小程序桶装水配送是一款的同城上门配送平台&#xff0c;为用户提供便捷的桶装水配送服务。解决用户在获取干净健康的饮用水方面的需求&#xff0c;提供高效、便捷的在线预约和下单服务。 小程序平台开发&#xff0c;具备强大的技术支持和良好的用户体验。用户可以通过…

跨平台开发技术

目录 1.Qt1.简介2.优势3.劣势 2.NET CoreVue1.简介2.优点 3.Flutter1.简介2.优点3.缺点 4.Maui1.简介2.优点3.缺点 5.Avalonia1.简介2.优点3.缺点 6. Cordova1.简介2.优点3.缺点 7.Electron1.简介2.优点3.缺点 个人搜集资料并总结了一些跨平台开发技术&#xff0c;如有不足欢迎…

分享一下怎么做一个房间预定链接

在旅游行业中&#xff0c;房间预定是非常重要的一环。随着互联网的普及和旅游业的发展&#xff0c;越来越多的人选择在网上预订房间。本文将介绍如何制作一个房间预定链接&#xff0c;以及推广该链接的方法和策略&#xff0c;帮助读者更好地了解房间预定的需求和实现方式。 一、…

隧道代理 vs 普通代理:哪种更适合您的爬虫应用?

前言 随着互联网的普及&#xff0c;爬虫技术在多个领域得到广泛应用。在进行爬虫开发时&#xff0c;代理服务器是不可或缺的工具之一。代理服务器可以隐藏客户端的真实 IP 地址和位置&#xff0c;从而保护客户端的隐私&#xff0c;同时通过代理可以绕过一些网络限制和安全机制…

什么是React中的高阶组件(Higher Order Component,HOC)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

迅为itop-3568开发板qt学习手册上新

基于RK3568的QT教程他来了~从C基础到QT编程实例再到项目实战&#xff0c;《iTOP-3568开发板QT学习手册》带你打通QT的任督二脉。 界面布局 3.5.1 水平布局 l Horizontal Layout&#xff1a;水平方向布局&#xff0c;组件自动在水平方向上分布 使用时先选中组件&#xff0…

设计模式中的黄金原则:引领你的代码风格,提升可维护性与扩展性

中国的先贤说过: 有道无术,术可求.有术无道,止于术. 术指的是技能、技术或方法&#xff0c;而道指的是原则、道德、智慧和理念。 西方古代的哲人也说过同样的话: 智慧之路从感性开始&#xff0c;却终极于理性.为什么要说设计原则呢, 因为设计模式通常需要遵循一些设计原则&…

Ant-Design-Pro-V5 :QueryFilter高级筛选组件、Table以及Pagination组件结合实现查询。

需求&#xff1a;根据 分类条件选择不同类型&#xff0c; table表格调取不同接口&#xff0c;展示不同数据。 代码&#xff1a; import React, { useRef, useState, Fragment, useEffect } from react; import { getNoticeInfo, getBannerList, delNotice } from ./service; …

windows系统ntp服务器一键开启

脚本 echo off REM 自动判断权限问题&#xff0c;主动获取管理员权限 echo off >nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" if %errorlevel% NEQ 0 ( goto UACPrompt ) else ( goto gotAdmin ) …

探索随机森林: 机器学习中的集成学习神器

机器学习 第七课 随机森林 概述机器学习机器学习的主要分类监督学习无监督学习强化学习 集成学习提高准确性增强稳定性提升泛化能力 集成学习的主要方法BaggingBoostingStacking 随机森林的理论基础决策树的基本原理随机森林的生成过程随机森林的优势与局限性 随机森林的实际应…

vue2.0项目中组件和iframe之间如何传值

vue2.0项目中组件和iframe之间如何传值 一、vue组件二、iframe组件 一、vue组件 mounted() {// 注册 message 事件监听器&#xff0c;只注册一次window.addEventListener(message, this.handleFromIframeMessage) }, beforeDestroy() {// 移除事件监听器window.removeEventList…

C#实现数据导出任一Word图表的通用呈现方法及一些体会

疲惫的修改 应人才测评产品的需求&#xff0c;导出测评报告是其中一个重要的环节&#xff0c;报告的文件类型也多种多样&#xff0c;其中WORD输出也扮演了一个重要的角色。 实现方法比较简单&#xff0c;结合分析结果数据&#xff0c;通过WORD模板文件进行替换输出。在实现的…

关于报错java.util.ConcurrentModificationException: null的源码分析和解决

一般有这种问题,方法中至少会有List或者Map下的至少两个子类,有可能参数类型相同,也有可能不同都有可能触发这个问题!其主要原因是使用了ArrayList进行删除操作或者使用iterator遍历集合的同时对集合进行修改都有可能会出现这个问题 ArrayList属于List下的子类 需要区分的是Li…

qt的一些自绘控件

https://download.csdn.net/download/venice0708/88469835