android so库函数,[原创]三. Android ELF系列:手写一个so文件(包含两个导出函数)

Android ELF系列:ELF文件格式简析和linker的链接so文件原理分析

Android ELF系列:实现一个so文件加载器

3. Android ELF系列:手写一个so文件(包含两个导出函数)

Android ELF系列:实现一些小功能

Android ELF系列:实现一个so的加密壳

Android ELF系列:待续............

手写so文件

准备工作

我们对so文件的定位是拥有两个导出函数,可以正常被dlopen加载,可以通过dlsym获取我们的函数.

而且根据前面的分析so文件的dynmic段必须具有 DT_HASH, DT_STRTAB , DT_SYMTAB.

这个so文件我们不需要section但必须有program 头.

所以我打算是只定义两个必须的程序头 PT_LOAD 和一个 PT_DYNMIC.

大致安排结构如下.

cb7f173482a0cd43c5fdfe105cbd456b.png

我们先一步一步来:

elf头typedef struct {

unsigned char e_ident[EI_NIDENT];

Elf32_Half e_type;

Elf32_Half e_machine;

Elf32_Word e_version;

Elf32_Addr e_entry;

Elf32_Off e_phoff;

Elf32_Off e_shoff;

Elf32_Word e_flags;

Elf32_Half e_ehsize;

Elf32_Half e_phentsize;

Elf32_Half e_phnum;

Elf32_Half e_shentsize;

Elf32_Half e_shnum;

Elf32_Half e_shstrndx;

} Elf32_Ehdr;

e_ident[EI_NIDENT]:7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

- 0:"\177ELF" ELFMAG

- 4:1 ELFCLASS32

- 5:1 ELFDATA2LSB

- 6:1 E_CURRENT

- 7-15:0

e_type:03 00 ET_DYN

e_machine:28 00 EM_ARM

e_version:01 00 00 00 EV_CURRENT

e_entry:00 00 00 00

e_phoff:34 00 00 00 因为Elf header的大小事34h

e_shoff:00 00 00 00 不需要

e_flags:00 02 00 05 额这个随便写

e_ehsize:34 00 ELF 头大小

e_phentsize:20 00 每个程序头表的大小

e_phnum:02 00 2各程序头表

e_shentsize:00 00

e_shnum:00 00

e_shstrndx:00 00

所以ELF头的十六进制为:

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00

00 00 00 00 00 02 00 05 34 00 20 00 02 00 00 00

00 00 00 00

程序头

ELF头之后紧接着就是程序头

PT_LOAD

typedef struct {

Elf32_Word p_type;

Elf32_Off p_offset;

Elf32_Addr p_vaddr;

Elf32_Addr p_paddr;

Elf32_Word p_filesz;

Elf32_Word p_memsz;

Elf32_Word p_flags;

Elf32_Word p_align;

} Elf32_Phdr;

p_type :01 00 00 00 PT_LOAD

p_offset:00 00 00 00

p_vaddr :00 00 00 00

p_paddr :00 00 00 00

p_filesz:77 77 77 01 文件大小后期需要修改,前面都是77 77 77 后面是需要改的序号这个是第一处

p_memsz :00 10 00 00 0x1000大小的内存应该足够了

p_flags :07 00 00 00 R_W_X 可读_可写_可执行

p_align :00 10 00 00 一页对齐PT_DYNAMIC

p_type :02 00 00 00 PT_DYNMIC

p_offset:74 00 00 00 34H+2*20H =74H

p_vaddr :74 00 00 00

p_paddr :00 00 00 00

p_filesz:20 00 00 00 一个DT_HASH,DT_STRTAB,DT_SYMTAB,DT_NULL 一共4*8 = 32 = 20h

p_memsz :20 00 00 00

p_flags :06 00 00 00 R_W 可读_可写

p_align :04 00 00 00 4字节对齐

现在so文件的内容为

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00

00 00 00 00 00 02 00 05 34 00 20 00 02 00 00 00

00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 30 01 00 00 30 01 00 00 07 00 00 00

00 10 00 00 02 00 00 00 74 00 00 00 74 00 00 00

00 00 00 00 20 00 00 00 20 00 00 00 06 00 00 00

04 00 00 00

DYNMIC

现在我们先不填充DYNMIC段,先全部填充 77.DT_NULL就填充00.为了补齐我们后面也全部填满一行00

现在so文件的内容为

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00

00 00 00 00 00 02 00 05 34 00 20 00 02 00 00 00

00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 77 77 77 01 30 01 00 00 07 00 00 00

00 10 00 00 02 00 00 00 74 00 00 00 74 00 00 00

00 00 00 00 20 00 00 00 20 00 00 00 06 00 00 00

04 00 00 00 77 77 77 77 77 77 77 77 77 77 77 77

77 77 77 77 77 77 77 77 77 77 77 02 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

插入函数代码

我们打算写两个函数AddNumber,SubNumber.

extern "C" JNIEXPORT jint JNICALL AddNumber(jint a,jint b)//A0 记录函数位置

{

return a+b;

}

extern "C" JNIEXPORT jint JNICALL SubNumber(jint a,jint b)//B8

{

return a-b;

}

然后在Android Studio中获取函数的二进制数据即可.

方法1:

调试,在AddNumber,SubNumber,下断,触发这两个函数,当触发时切换到lldb界面

7cab485d43f33de7514bb2f5ccb6ae7b.png

方法2:自己把编译的so文件拖出来定位到指定函数抽取二进制代码吧.

现在so文件的内容为

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00

00 00 00 00 00 02 00 05 34 00 20 00 02 00 00 00

00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 77 77 77 01 30 01 00 00 07 00 00 00

00 10 00 00 02 00 00 00 74 00 00 00 74 00 00 00

00 00 00 00 20 00 00 00 20 00 00 00 06 00 00 00

04 00 00 00 77 77 77 77 77 77 77 77 77 77 77 77

77 77 77 77 77 77 77 77 77 77 77 02 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

84 B0 0A 46 03 46 03 90 02 91 03 98 02 99 08 44

01 92 00 93 04 B0 70 47 84 B0 0A 46 03 46 03 90

02 91 03 98 02 99 40 1A 01 92 00 93 04 B0 70 47

str表

记录位置:0xD0

我们导入了两个函数所以str表只需要两个字符串

\0AddNumber\0SubNumber\0\0

现在so文件的内容为(后面补齐一行0):

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00

00 00 00 00 00 02 00 05 34 00 20 00 02 00 00 00

00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 77 77 77 01 30 01 00 00 07 00 00 00

00 10 00 00 02 00 00 00 74 00 00 00 74 00 00 00

00 00 00 00 20 00 00 00 20 00 00 00 06 00 00 00

04 00 00 00 77 77 77 77 77 77 77 77 77 77 77 77

77 77 77 77 77 77 77 77 77 77 77 02 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

84 B0 0A 46 03 46 03 90 02 91 03 98 02 99 08 44

01 92 00 93 04 B0 70 47 84 B0 0A 46 03 46 03 90

02 91 03 98 02 99 40 1A 01 92 00 93 04 B0 70 47

00 41 64 64 4E 75 6D 62 65 72 00 53 75 62 4E 75

6D 62 65 72 00 00 00 00 00 00 00 00 00 00 00 00

sym表

记录位置: 0xf0

我们有两个导出函数所以需要2个sym表.但第一个sym表必须是SH_UNDEF.所以有3个.

AddNumber

typedef struct {

Elf32_Word st_name;

Elf32_Addr st_value;

Elf32_Word st_size;

unsigned char st_info;

unsigned char st_other;

Elf32_Section st_shndx;

} Elf32_Sym;

st_name :01 00 00 00 AddNumber在字符串表的偏移是1

st_value:A0 00 00 00 AddNumber的地址

st_size :16 00 00 00 函数又24个字节

st_info :12 STB_GLOBAL|STT_FUNC

st_other:0

st_shndx:01 00SubNumberst_name :0B 00 00 00 SubNumber 在字符串表的偏移是1

st_value:B8 00 00 00 SubNumber 的地址

st_size :16 00 00 00 函数又24个字节

st_info :12 STB_GLOBAL|STT_FUNC

st_other:0

st_shndx:01 00

现在so文件的内容为(后面补齐一行0):

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00

00 00 00 00 00 02 00 05 34 00 20 00 02 00 00 00

00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 77 77 77 01 30 01 00 00 07 00 00 00

00 10 00 00 02 00 00 00 74 00 00 00 74 00 00 00

00 00 00 00 20 00 00 00 20 00 00 00 06 00 00 00

04 00 00 00 77 77 77 77 77 77 77 77 77 77 77 77

77 77 77 77 77 77 77 77 77 77 77 02 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

84 B0 0A 46 03 46 03 90 02 91 03 98 02 99 08 44

01 92 00 93 04 B0 70 47 84 B0 0A 46 03 46 03 90

02 91 03 98 02 99 40 1A 01 92 00 93 04 B0 70 47

00 41 64 64 4E 75 6D 62 65 72 00 53 75 62 4E 75

6D 62 65 72 00 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

01 00 00 00 A0 00 00 00 16 00 00 00 12 00 01 00

0B 00 00 00 B8 00 00 00 16 00 00 00 12 00 01 00

hash表

记录位置:0x120

hash表结构

struct

{

unsigned nbucket;

unsigned nchain;

unsigned bucket[nbucket];

unsigned chain[nchain];

};

在说hash表之前,我们得先说两个函数.

unsigned elfhash(const char* _name) {

const unsigned char* name = (const unsigned char*) _name;

unsigned h = 0, g;

while(*name) {

h = (h << 4) + *name++;

g = h & 0xf0000000;

h ^= g;

h ^= g >> 24;

}

return h;

}

static Elf32_Sym* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name) {

Elf32_Sym* symtab = si->symtab;

const char* strtab = si->strtab;

TRACE_TYPE(LOOKUP, "SEARCH %s in %s@0x%08x %08x %d",

name, si->name, si->base, hash, hash % si->nbucket);

for (unsigned n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]) {

Elf32_Sym* s = symtab + n;

if (strcmp(strtab + s->st_name, name)) continue;

/* only concern ourselves with global and weak symbol definitions */

switch(ELF32_ST_BIND(s->st_info)){

case STB_GLOBAL:

case STB_WEAK:

if (s->st_shndx == SHN_UNDEF) {

continue;

}

TRACE_TYPE(LOOKUP, "FOUND %s in %s (%08x) %d",

name, si->name, s->st_value, s->st_size);

return s;

}

}

return NULL;

}

假如我们调用dlsym(so,"AddNumber");//查找函数地址的时候那么最终会有这样的调用

soinfo_elf_lookup(so,elfhash("AddNumber"),"AddNumber");

我们先算一下hash值:

AddNumber:157056866

SubNumber:123495026

因为我们只有2个符号,所以定义

nbucket = 2

那么

157056866 % 2 = 0

157056866 % 2 = 0

那么我们定义如下:

nbucket :02 00 00 00

nchain :02 00 00 00

bucket :01 00 00 00 00 00 00 00

chain :00 00 00 00 02 00 00 00

最终定义如下:

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00

00 00 00 00 00 02 00 05 34 00 20 00 02 00 00 00

00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 77 77 77 01 30 01 00 00 07 00 00 00

00 10 00 00 02 00 00 00 74 00 00 00 74 00 00 00

00 00 00 00 20 00 00 00 20 00 00 00 06 00 00 00

04 00 00 00 77 77 77 77 77 77 77 77 77 77 77 77

77 77 77 77 77 77 77 77 77 77 77 02 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

84 B0 0A 46 03 46 03 90 02 91 03 98 02 99 08 44

01 92 00 93 04 B0 70 47 84 B0 0A 46 03 46 03 90

02 91 03 98 02 99 40 1A 01 92 00 93 04 B0 70 47

00 41 64 64 4E 75 6D 62 65 72 00 53 75 62 4E 75

6D 62 65 72 00 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

01 00 00 00 A0 00 00 00 16 00 00 00 12 00 01 00

0B 00 00 00 B8 00 00 00 16 00 00 00 12 00 01 00

02 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00

00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00

fc04d32be235223a590bb4b8f26be85e.png

修正文件

我们有两处需要修改的地方

一个是文件大小,一个是dynmic段.

现在我们先把信息汇总一下:

文件大小:0x140

str表:0xD0

sym表:0xF0

hash表:0x120

修改文件大小,也就是77 77 77 01处为 40 01 00 00.

填充dynmic段04 00 00 00 DT_HASH

20 01 00 00

05 00 00 00 DT_STRTAB

d0 00 00 00

06 00 00 00 DT_SYMTAB

f0 00 00 00

也就是把77 . . . 77 02改为以上内容:

最终so文件的内容为

7F 45 4C 46 01 01 01 00 00 00 00 00 00 00 00 00

03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00

00 00 00 00 00 02 00 05 34 00 20 00 02 00 00 00

00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 40 01 00 00 30 01 00 00 07 00 00 00

00 10 00 00 02 00 00 00 74 00 00 00 74 00 00 00

00 00 00 00 20 00 00 00 20 00 00 00 06 00 00 00

04 00 00 00 04 00 00 00 20 01 00 00 05 00 00 00

D0 00 00 00 06 00 00 00 F0 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

84 B0 0A 46 03 46 03 90 02 91 03 98 02 99 08 44

01 92 00 93 04 B0 70 47 84 B0 0A 46 03 46 03 90

02 91 03 98 02 99 40 1A 01 92 00 93 04 B0 70 47

00 41 64 64 4E 75 6D 62 65 72 00 53 75 62 4E 75

6D 62 65 72 00 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

01 00 00 00 A0 00 00 00 16 00 00 00 12 00 01 00

0B 00 00 00 B8 00 00 00 16 00 00 00 12 00 01 00

02 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00

00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00

c250f9407c2e01e179a330bf43cc4a36.png

验证新建一个ndk项目

72ec206e5ded8573fcf8fbc811130988.png

写如下代码

native-lib.cpp

#include

#include

#include

#include

#define TAG "chpmesotest"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)

typedef jint (*SubNumber)(jint a,jint b);

typedef jint (*AddNumber)(jint a,jint b);

SubNumber Sub;

AddNumber Add;

extern "C" JNIEXPORT jstring JNICALL

Java_com_example_mi_testmesotes_MainActivity_stringFromJNI(

JNIEnv *env,

jobject /* this */) {

void *meso = dlopen("/data/data/com.example.mi.testmesotes/lib/meso.so",0);

if(meso)

{

Add = (AddNumber) dlsym(meso,"AddNumber");

Sub = (SubNumber) dlsym(meso,"SubNumber");

LOGD("meso:%x Add:%x Sub:%x",meso,Add,Sub);

jint a = Add(3,4);

jint b = Sub(8,1);

LOGD("a:%d b:%d",a,b);

}

std::string hello = "Hello from C++";

return env->NewStringUTF(hello.c_str());

}

把我们手写的meso.so文件放入/data/data/com.example.mi.testmesotes/lib

adb push meso.so /data/local/tmp

adb shell

su

cd /data/data/com.example.mi.testmesotes/lib

cp /data/local/tmp/meso.so ./

chmod 777 meso.so

结束再次运行发现缺奔溃了.

但我们发现打印了一行log.证明加载so文件是成功了,但是缺发生了错误

02-24 04:57:10.849 25107-25107/? D/chpmesotest: meso:71cdcdb4 Add:753930a0 Sub:753930b8

说明是在调用这两个函数的时候发生了错误.请看到前面我们抽取的函数代码.

我们抽取的函数代码是Thumb.要切换的Thumb一般都是通过把最后一位置为1:但函数Add:753930a0 Sub:753930b8.的地址最后一位都不是1.所以我们需要再次修改so文件.就是把两个sym符号的st_value的最后一位都置为1.

修改如下:

AddNumber

st_name :01 00 00 00 AddNumber在字符串表的偏移是1

st_value:A1 00 00 00 最后一位置1,变为Thumb执行代码

st_size :16 00 00 00 函数又24个字节

st_info :12 STB_GLOBAL|STT_FUNC

st_other:0

st_shndx:01 00SubNumberst_name :0B 00 00 00 SubNumber 在字符串表的偏移是1

st_value:B9 00 00 00 最后一位置1,变为Thumb执行代码

st_size :16 00 00 00 函数又24个字节

st_info :12 STB_GLOBAL|STT_FUNC

st_other:0

st_shndx:01 00

09de4eefaaec4d4d8e5d4a94c3fe827b.png

再次把修改好后的meso.so文件放进 /data/data/com.example.mi.testmesotes/lib.

然后运行程序:

输出log:

02-24 05:08:42.939 25898-25898/? D/chpmesotest: meso:71cdcdb4 Add:753930a1 Sub:753930b9

02-24 05:08:42.939 25898-25898/? D/chpmesotest: a:7 b:7

证明我们手写的so文件成功了.此处应该有掌声

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值