返回列表 发新帖

【初阶逆向】初探so层~更适合新手宝宝的教程

    [复制链接]

16

主题

1778

回帖

1万

积分

博士生

阿巴阿巴

Rank: 7Rank: 7Rank: 7

金币
2219
好评
244
信誉
190
发表于 2025-5-1 12:14:17 | 显示全部楼层 | 阅读模式  来自 贵州
本帖最后由 零星0523 于 2025-5-1 12:38 编辑

适用人群:适用于想要入门so层逆向的人


本次学习知识点:
1.什么是so文件?它的格式是什么
2.了解导入表和导出表
3.学会基础NDK开发,为ida逆向打下基础

温馨提示:观看本贴时开电脑模式会有更好的观感哦

话不多说,开始教程吧
什么是so文件?它的结构是什么?我们该怎么解析


so其实就是ELF可执行文件,所以,我们需要了解ELF文件是什么
ELF文件的基本结构,其主要由四部分组成:

[td]
ELF HeaderELF文件头文件开头存储ELF文件类型(可执行、共享库等)、目标架构、入口地址、Program Header 表和 Section Header 表的位置等关键信息
Program Header Table程序头表ELF Header指定位置描述可执行时所需的段(Segment),如代码段、数据段等,告诉操作系统如何创建进程的虚拟内存映射
Sections节(代码/数据)文件中间部分存储实际的代码、数据、符号表、字符串表等内容。每个Section负责一种类型的内容,不一定参与加载,但常用于链接或调试
Section Header Table节头表ELF Header指定位置描述每个Section的名字、类型、大小、位置等信息,供链接器、调试器等工具使用,不参与程序执行







ELF 文件结构
我们可以使用010editor来解析so文件结构并观察(如何使用呢?)
在菜单栏中点击 Templates -> Template Repository
搜索ELF并选择ELF.bt。下载它,随后Templates -> Run Template,选择 ELF.bt
我们就可以看到一个ELF的文件结构啦
可以看到,下框有刚刚我们说的五个部分,我们先看ELF Header部分
[td]
字段名称偏移量(十六进制)大小(字节)有什么用
e_ident0x0016魔数、类(32/64位)、字节序等基础标识信息
e_type0x102文件类型(如可执行文件、重定位文件、共享对象等)
e_machine0x122指定目标机器架构(如x86、ARM等)
e_version0x144版本号(通常为1)
e_entry0x184/8程序入口地址(执行开始的位置)
e_phoff0x1C / 0x204/8程序头表(Program Header Table)在文件中的偏移量
e_shoff0x20 / 0x284/8节头表(Section Header Table)在文件中的偏移量
e_flags0x24 / 0x304与处理器相关的标志
e_ehsize0x28 / 0x342ELF Header 本身的大小(一般为52或64字节)
e_phentsize0x2A / 0x362每个程序头表项的大小
e_phnum0x2C / 0x382程序头表中表项的数量
e_shentsize0x2E / 0x3A2每个节头表项的大小
e_shnum0x30 / 0x3C2节头表中表项的数量
e_shstrndx0x32 / 0x3E2节头字符串表的索引


之后我们可以回到开头可以看到,一个so文件是以ELF文件开头,而这就是ELF魔数了
什么是魔数?


ELF魔数
每种可执行文件的格式的开头几个字节都是很特殊的,特别是开头4个字节,通常被称为魔数(Magic Number)。通过对魔数的判断可以确定文件的格式和类型。如:ELF的可执行文件格式的头4个字节为0x7F、e、l、f;Java的可执行文件格式的头4个字节为c、a、f、e;如果被执行的是Shell脚本或perl、python等解释型语言的脚本,那么它的第一行往往是#!/bin/sh或#!/usr/bin/perl或#!/usr/bin/python,此时前两个字节#和!就构成了魔数,系统一旦判断到这两个字节,就对后面的字符串进行解析,以确定具体的解释程序路径。


ELF文件类型
ELF文件主要有三种类型,可以通过ELF Header中的e_type成员进行区分。
可重定位文件(Relocatable File):ETL_REL。一般为.o文件。可以被链接成可执行文件或共享目标文件。静态链接库属于可重定位文件。
可执行文件(Executable File):ET_EXEC。可以直接执行的程序。
共享目标文件(Shared Object File):ET_DYN。一般为.so文件。有两种情况可以使用。链接器将其与其他可重定位文件、共享目标文件链接成新的目标文件;
动态链接器将其与其他共享目标文件、结合一个可执行文件,创建进程映像。 (感兴趣的可以自己查看一下加深印象)


ELF Section Header Table
ELF 文件中的 Section Header Table(节头表) 是描述每个 Section(节)信息的一个结构数组,它存在于所有的 ELF 文件中(包括 .so、.o、可执行文件)。
每个节(Section)包含一类特定的数据,例如代码、符号表、字符串表、调试信息等,Section Header Table 就是描述这些节的元信息的集合。
32 位 ELF 文件中,Elf32_Shdr 的定义如下(64 位结构类似):
  1. typedef struct {
  2.     uint32_t sh_name;      // 节名称的字符串表索引
  3.     uint32_t sh_type;      // 节的类型(SHT_PROGBITS 等)
  4.     uint32_t sh_flags;     // 属性标志(可读、可写、可执行等)
  5.     uint32_t sh_addr;      // 在内存中的虚拟地址
  6.     uint32_t sh_offset;    // 文件中的偏移地址
  7.     uint32_t sh_size;      // 大小(字节)
  8.     uint32_t sh_link;      // 与其他节的链接(如关联的符号表)
  9.     uint32_t sh_info;      // 补充信息
  10.     uint32_t sh_addralign; // 对齐要求
  11.     uint32_t sh_entsize;   // 如果节中包含固定大小条目,此为每条目大小
  12. } Elf32_Shdr;
复制代码

typedef struct {    uint32_t sh_name;      // 节名称的字符串表索引    uint32_t sh_type;      // 节的类型(SHT_PROGBITS 等)    uint32_t sh_flags;     // 属性标志(可读、可写、可执行等)    uint32_t sh_addr;      // 在内存中的虚拟地址    uint32_t sh_offset;    // 文件中的偏移地址    uint32_t sh_size;      // 大小(字节)    uint32_t sh_link;      // 与其他节的链接(如关联的符号表)    uint32_t sh_info;      // 补充信息    uint32_t sh_addralign; // 对齐要求    uint32_t sh_entsize;   // 如果节中包含固定大小条目,此为每条目大小} Elf32_Shdr;
节类型(sh_type)节名是一个字符串,只是在链接和编译过程中有意义,但它并不能真正地表示节的类型。对于编译器和链接器来说,主要决定节的属性是节的类型(sh_type)和节的标志位(sh_flags)。节的类型相关常量以SHT_开头
常见的节类型如下表所示
[td]
十进制值宏定义名描述说明
0SHT_NULL无效节头项(一般是第一个,表示未使用)
1SHT_PROGBITS程序代码或数据段,如 .text、.data
2SHT_SYMTAB静态符号表(含函数名、变量名等,调试使用)
3SHT_STRTAB字符串表,提供节名、符号名等字符串
4SHT_RELA重定位表,带显式 addend(偏移修正值)
5SHT_HASH符号哈希表,用于动态链接
6SHT_DYNAMIC动态链接信息,供动态加载器使用
7SHT_NOTE附加注释/元数据(如 Android ABI、Build ID)
8SHT_NOBITS不占用实际文件空间的节,如 .bss
9SHT_REL重定位表,不带 addend
10SHT_SHLIB保留,暂未使用
11SHT_DYNSYM动态符号表(给动态链接器用,包含全局符号)
14SHT_INIT_ARRAY构造函数数组(初始化代码指针)
15SHT_FINI_ARRAY析构函数数组(清理代码指针)
16SHT_PREINIT_ARRAY程序入口前调用的函数指针
17SHT_GROUPSection Group 信息(如用于 comdat 组)
18SHT_SYMTAB_SHNDX用于扩展 .symtab 中的节索引字段
0x6ffffff6SHT_GNU_HASHGNU 扩展的哈希表,用于加快符号查找
0x70000000~SHT_LOPROC处理器相关(私有用途)


接下来说几个比较重要的段


[td]
节名称作用说明所在段节类型(sh_type)
.text程序代码指令.textSHT_PROGBITS
.rodata只读常量(如字符串).textSHT_PROGBITS
.plt动态库函数调用跳板(过程链接表).textSHT_PROGBITS
.data初始化的全局变量.dataSHT_PROGBITS
.bss未初始化的全局变量(加载时清零).dataSHT_NOBITS
.got.plt全局偏移表(供链接器运行时修改).dataSHT_PROGBITS
.dynsym动态符号表(共享库函数的符号信息).textSHT_DYNSYM
.dynstr动态字符串表(动态符号名称).textSHT_STRTAB
.rel.*重定位表(无 addend 的重定位信息)SHT_REL
.rela.*重定位表(有 addend 的重定位信息)SHT_RELA
.hashGNU 哈希表,用于加快符号解析.textSHT_HASH / SHT_GNU_HASH
.symtab静态符号表(编译器调试使用).text 或无段SHT_SYMTAB
.strtab静态字符串表(供 .symtab 使用).text 或无段SHT_STRTAB
.ctors构造函数表(程序执行前自动运行).dataSHT_INIT_ARRAY
.dtors析构函数表(程序执行后自动运行).dataSHT_FINI_ARRAY




上面我们介绍了.dynsym节和.symtab节,两者都是符号表。那么它们到底有什么区别呢?存在什么关系呢?
符号表
[td]
名称作用
.dynsym动态链接器(如ld.so) 提供必要的符号信息,用于程序运行时解析共享库中的函数和变量
.symtab调试器、反汇编器 和链接器使用,包含完整的符号信息,用于静态链接、调试等目的

[td]
比较项.dynsym.symtab
包含符号仅包含动态链接需要的外部符号包含全部符号(局部 + 全局 + 调试用)
符号数量少(仅用于动态链接)多(包含局部变量、函数、调试符号等)
是否包含局部符号
总结:.symtab不是在运行时必需的,因此不会被装载到内存中。.dynsym保存的符号只能在运行时被解析,因此是运行时动态链接器所需的唯一符号。.dynsym对于动态链接可执行文件的执行是必需的,而.symtab只是用来进行调试和链接的。
什么是导入表(Import Table)
导入表记录了一个 .so 文件依赖的外部符号(函数或变量),这些符号来自其他共享库,运行时由动态链接器进行解析。
导入表体现在哪些节中?[td]
节名称说明
.dynsym动态符号表,包含所有导入/导出符号
.dynstr动态字符串表,保存符号名称
.rel.plt / .rela.plt重定位信息(用于懒加载)
.got.pltGOT表,用于存放外部符号地址
.dynamic含有 DT_NEEDED(依赖库)、DT_SYMTAB 等指针,供加载器解析


什么是导出表(Export Table)
导出表列出了一个 .so 文件对外暴露的符号,即其他程序或库可以调用的函数和变量。导出符号同样保存在 .dynsym 中。

基础NDK开发(确保已配备开发环境)

新建一个NDK项目,之后我们看到native-lib.cpp,native层的代码就在此处编写了。
初步了解静态注册和动态注册

静态注册(Static Registration)

优点:使用固定命名格式,系统自动匹配 Java 方法和 native 实现。      简单直接,不需要手动注册。

缺点:每个函数名非常长,容易出错,不灵活。且逆向成本较低

动态注册(Dynamic Registration)

优点:使用任意函数名,通过 JNI_OnLoad 中显式注册       结构清晰,支持混淆类名/方法名

缺点: 如果注册失败不会编译报错,只会在运行时崩溃或方法调用失败


现在,让我们来注册两个native函数(一个实现静态注册,另一个实现动态注册)

新建一个类math,接下来在这个类里这么写

  1. package com.lingxing.myapplication;

  2. public class math {
  3.     public static native int add(int a , int b);
  4.     public static native int sub(int a , int b);
  5. }
复制代码

之后,我们开始在native注册。

  1. #include <jni.h>           // JNI 头文件
  2. #include <string>

  3. extern "C" JNIEXPORT jstring JNICALL
  4. Java_com_lingxing_myapplication_MainActivity_stringFromJNI(
  5.         JNIEnv* env,           // JNI 接口指针,用于调用 JNI 提供的各种函数
  6.         jobject /* this */) {  // Java 中调用该方法的对象引用,这里未使用,可省略参数名
  7.     std::string hello = "Hello from C++";
  8.     return env->NewStringUTF(hello.c_str());
  9. }

  10. extern "C" JNIEXPORT jint JNICALL
  11. Java_com_lingxing_myapplication_math_add(
  12.         JNIEnv *env,         // JNI 接口指针
  13.         jclass clazz,        // 调用该静态 native 方法的 Java 类引用
  14.         jint a,              // 从 Java 传入的第一个 int 参数
  15.         jint b) {            // 从 Java 传入的第二个 int 参数
  16.     return a + b;
  17. }

  18. jint sub(JNIEnv* env, jclass clazz, jint a, jint b) {
  19.     return a - b;      
  20. }
  21. static JNINativeMethod methods[] = {
  22.     // {Java 层方法名, 方法签名, 对应的 C 函数指针}
  23.     // 方法签名 (II)I 表示:接收两个 int (I, I),返回一个 int (I)
  24.     {"sub", "(II)I", (void*)sub}
  25. };

  26. // JNI 默认加载函数,当 Java 层使用 System.loadLibrary() 加载本地库时会调用此函数
  27. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
  28.     JNIEnv* env = nullptr;
  29.     // 获取 JNI 环境,指定最低支持的 JNI 版本为 1.6
  30.     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
  31.         return JNI_ERR;     // 获取失败则返回错误,库加载失败
  32.     }

  33.     // 查找要注册 native 方法的 Java 类,使用全路径,包名与类名之间用 '/' 分隔
  34.     jclass clazz = env->FindClass("com/lingxing/myapplication/math");
  35.     if (clazz == nullptr) {
  36.         return JNI_ERR;     // 找不到类,返回错误
  37.     }

  38.     // 动态注册 methods 数组中定义的 native 方法
  39.     // 第三个参数是 methods 数组长度
  40.     if (env->RegisterNatives(
  41.             clazz,
  42.             methods,
  43.             sizeof(methods) / sizeof(methods[0])
  44.         ) < 0) {
  45.         return JNI_ERR;     // 注册失败,返回错误
  46.     }

  47.     // 返回支持的 JNI 版本号
  48.     return JNI_VERSION_1_6;
  49. }
复制代码
接下来我们就完成了静态注册和动态注册,就可以用IDA看导出表了

直接搜java就能看到静态注册的方法了(动态注册下次再讲,三种对抗方法),点进去f5之后按y把一个参数改为

  1. JNIEnv *env
复制代码
这样来说对于jni函数伪c效果更好,但是这里逻辑太简单,所以无所谓改不改。
动态注册的话,咱们也可以静态看一看,静态其实不太好看。
导出表搜索JNI_OnLoad。
可以看到
  1. if ( (*(int (__fastcall **)(_QWORD, __int64, _UNKNOWN **, __int64))(*(_QWORD *)v5[0] + 1720LL))(
  2.          v5[0],
  3.          v4,
  4.          &off_40108,
  5.          1) < 0 )
复制代码
那么这其中的off_40108就是它的方法了,我们点进去看可以看到
  1. Z3subP7_JNIEnvP7_jclassii ; sub(_JNIEnv *,_jclass *,int,int)
复制代码
双击点进去就是减法的逻辑了
结帖结帖


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
已有14人评分好评 金币 理由
linbt + 1 + 1
Amin + 1 + 1 很给力!
tips + 1 + 1
36mt + 1 + 1
Rain150 + 1 + 1 赞一个!
熠小夕 + 1
絮尘惟倾恬 + 1 + 1 很给力!
讯锋 + 1 + 1 很给力!
橘子v + 1 + 1 赞一个!
T8numen + 1 + 1
yxm068 + 1 + 1 谢谢分享,学习
青春向上 + 1 + 1 膜拜大佬。
氵衮 + 1 + 1
苇草 + 1 + 1

查看全部评分 总评分:好评 +14  金币 +13 

回复

使用道具 举报

68

主题

921

回帖

4373

积分

禁止发言

金币
3637
好评
12
信誉
132

考神

发表于 2025-5-1 12:15:15 来自手机  | 显示全部楼层  来自 江苏
看看看
回复

使用道具 举报

0

主题

626

回帖

4498

积分

大学生

Rank: 5Rank: 5

金币
2049
好评
0
信誉
96
发表于 2025-5-1 12:18:06 来自手机  | 显示全部楼层  来自 海南
看看隐藏
回复

使用道具 举报

2

主题

999

回帖

4445

积分

大学生

Rank: 5Rank: 5

金币
2441
好评
0
信誉
100
发表于 2025-5-1 12:18:17 来自手机  | 显示全部楼层  来自 广东
看看教程
回复

使用道具 举报

24

主题

826

回帖

3267

积分

大学生

Rank: 5Rank: 5

金币
1482
好评
4
信誉
100
发表于 2025-5-1 12:18:37 来自手机  | 显示全部楼层  来自 浙江
看看隐藏
回复

使用道具 举报

66

主题

6472

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
1748
好评
2
信誉
109

考神

发表于 2025-5-1 12:19:06 来自手机  | 显示全部楼层  来自 江西
路过看看
回复

使用道具 举报

0

主题

5325

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
3870
好评
1
信誉
108

MT论坛新人MT论坛最佳新人考神MT论坛帅哥

发表于 2025-5-1 12:19:48 来自手机  | 显示全部楼层  来自 福建
学习一下
回复

使用道具 举报

10

主题

1646

回帖

7998

积分

硕士生

Rank: 6Rank: 6

金币
259
好评
10
信誉
120
发表于 2025-5-1 12:19:54 来自手机  | 显示全部楼层  来自 四川
学习学习
回复

使用道具 举报

16

主题

3792

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
4890
好评
1
信誉
100
发表于 2025-5-1 12:21:38 来自手机  | 显示全部楼层  来自 广东
学习学习
回复

使用道具 举报

9

主题

1640

回帖

8540

积分

硕士生

Rank: 6Rank: 6

金币
4535
好评
0
信誉
90

MT论坛新人考神

发表于 2025-5-1 12:22:48 来自手机  | 显示全部楼层  来自 陕西
感谢分享
回复

使用道具 举报

46

主题

2704

回帖

1万

积分

博士生

听冬宝宝

Rank: 7Rank: 7Rank: 7

金币
4089
好评
91
信誉
151

MT论坛帅哥考神MT论坛新人MT论坛活跃会员MT论坛最佳新人

QQ
发表于 2025-5-1 12:23:24 来自手机  | 显示全部楼层  来自 贵州
Look Look
回复

使用道具 举报

39

主题

2624

回帖

1万

积分

博士生

一辆自行车

Rank: 7Rank: 7Rank: 7

金币
3456
好评
12
信誉
100

MT论坛最佳新人MT论坛新人考神

发表于 2025-5-1 12:25:14 来自手机  | 显示全部楼层  来自 新疆
回复看看
回复

使用道具 举报

4

主题

4190

回帖

1万

积分

博士生

小白

Rank: 7Rank: 7Rank: 7

金币
5884
好评
14
信誉
102

MT论坛最佳新人考神

发表于 2025-5-1 12:25:20 来自手机  | 显示全部楼层  来自 福建
来学习学习,谢谢
回复

使用道具 举报

3

主题

1421

回帖

4041

积分

大学生

Rank: 5Rank: 5

金币
2907
好评
1
信誉
100

考神MT论坛新人MT论坛最佳新人MT论坛帅哥

发表于 2025-5-1 12:26:33 来自手机  | 显示全部楼层  来自 云南
感谢分享
回复

使用道具 举报

18

主题

4876

回帖

1万

积分

博士生

MW

Rank: 7Rank: 7Rank: 7

金币
2516
好评
2
信誉
100

考神MT论坛新人MT论坛帅哥MT论坛最佳新人

发表于 2025-5-1 12:26:47 来自手机  | 显示全部楼层  来自 新疆
感谢分享,看看隐藏
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表