如何修改android的so文件进行api HOOK钩子补丁

1. 准备工具和环境

    1.1 android NDK 

    1.2 android SDK 

    1.3 Synalyze It! Pro, 安装ELF语法

    1.4 elf文件格式文档.

2. 文件格式是 ELF, 组织如下

实例:

3.我们只需要做以下修改就可以,

    3.1  在SHT_DYNAMIC这里增加我们的so,一般来说这个段都有一些对齐的空位,我们不需要增加字节,这样就不重新计算各个偏移了,要不然很多段,这个计算还是比较麻烦的。如下图:

   这个一个信息是8个字节,分两个整型吧, 第一个是tag这个查手册有以下字义,第二数的意义根据tag的不同来定义.

  

你可以使用readelf 来打印这个段出来:

Dynamic section at offset 0xefc contains 24 entries:
  Tag        Type                         Name/Value
 0x00000003 (PLTGOT)                     0x1fdc
 0x00000002 (PLTRELSZ)                   48 (bytes)
 0x00000017 (JMPREL)                     0x28c
 0x00000014 (PLTREL)                     REL
 0x00000011 (REL)                        0x27c
 0x00000012 (RELSZ)                      16 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffa (RELCOUNT)                   2
 0x00000006 (SYMTAB)                     0x114
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000005 (STRTAB)                     0x1c4
 0x0000000a (STRSZ)                      120 (bytes)
 0x00000004 (HASH)                       0x23c
 0x00000001 (NEEDED)                     Shared library: [libc.so]
 0x00000001 (NEEDED)                     Shared library: [libdl.so]
 0x0000001a (FINI_ARRAY)                 0x1ef0
 0x0000001c (FINI_ARRAYSZ)               8 (bytes)
 0x00000019 (INIT_ARRAY)                 0x1ef8
 0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 0x00000010 (SYMBOLIC)                   0x0
 0x0000001e (FLAGS)                      SYMBOLIC BIND_NOW
 0x6ffffffb (FLAGS_1)                    Flags: NOW
 0x00000001 (NEEDED)                     Shared library: [ibdl.so] //这个就是我增加的
 0x00000000 (NULL)                       0x0

可以看到,我在图上增加的修改的数在readelf里,增加了:

      0x00000001 (NEEDED)                     Shared library: [ibdl.so] 

      01 00 00 00  //表示NEEDED 

      70 00 00 00 //表示这个动态库名字在strtab中的偏移, (有多个的,这里是, 看图,sh_link是2,所以是下标2那个),如图划了红线:


 

     

 

3.2  将对应的api的名字修改成我们的,如fopen 改成fopex, 这样的不增加文件内容,      在 解析符号引用时,动态链接程序使用宽度优先算法检查符号表。就是说首先检查可执行程序自身的符号表,然后检查 DT_NEEDED 条目(按顺序) 的符号表,接着在第二级 DT_NEEDED 条目上搜索。共享目标文件必须对进程而言可 读,不需要其他权限。

顺序:

     1. in the executable,

     2. in the shared libraries specified on the link command line, in order,

     3. in the dependencies of the shared libraries specified on the link

        command line,

     4. in the dlopen()ed shared libraries, in the order in which they were

        dlopen()ed.

 

有一些符号没有版本的,这种的话直接改了就可以了,但有些符号是有版本的,如:

readelf  --dyn-syms   libtestmod.so 

Symbol table '.dynsym' contains 13 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000000004a0     0 SECTION LOCAL  DEFAULT    9 
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fopex@GLIBC_2.2.5 (2)
     5: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fclose@GLIBC_2.2.5 (2)
     7: 00000000002008a8     0 NOTYPE  GLOBAL DEFAULT  ABS _end
     8: 0000000000200898     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
     9: 0000000000200898     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    10: 00000000000005cc    52 FUNC    GLOBAL DEFAULT   11 test_so_call
    11: 00000000000004a0     0 FUNC    GLOBAL DEFAULT    9 _init
    12: 0000000000000638     0 FUNC    GLOBAL DEFAULT   12 _fini


Version symbols section '.gnu.version' contains 13 entries:
 Addr: 00000000000003b8  Offset: 0x0003b8  Link: 3 (.dynsym)
  000:   0 (*local*)       0 (*local*)       0 (*local*)       0 (*local*)    
  004:   0 (*local*)       2 (GLIBC_2.2.5)   2 (GLIBC_2.2.5)   1 (*global*)   
  008:   1 (*global*)      1 (*global*)      1 (*global*)      1 (*global*)   
  00c:   1 (*global*)   


Version needs section '.gnu.version_r' contains 1 entries:
 Addr: 0x00000000000003d8  Offset: 0x0003d8  Link: 4 (.dynstr)
  000000: Version: 1  File: libc.so.6  Cnt: 1
  0x0010:   Name: GLIBC_2.2.5  Flags: none  Version: 2

Displaying notes found at file offset 0x00000190 with length 0x00000024:
  Owner                 Data size	Description
  GNU                  0x00000014	NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: b735f8c89e4063c8985931547b23b321de943a22


fopex@GLIBC_2.2.5 (2) , @GLIBC_2.2.5表示这个是version string,  (2) 是(这个也是一个标识值,对于在version_info中对比Elf_Internal_Vernaux的vna_other值, 见上面Version needs section,  [ none  Version: 2]这个信息), 具体可以看binutils的源代码的readelf.c, 这个是由SHT_GNU_versym段定义的,根据代码,只有外部的重态符号才有这个version strings,

因此SHT_GNU_versym的version的数量是和dynamic sym的数量是一致的,这个的version的数据结构是:

typedef struct {
  unsigned char		vs_vers[2];
} ATTRIBUTE_PACKED  Elf_External_Versym;

 

0和1两个值是特别意义的,如上面发出来的,0是表示是local, 1表示global, 其它值的话就是在Version needs section中定义的version的值了。

 

很明显一旦一个符号指定了一个version string的话,其实它就绑到一个so的(needed)了。

所以我们为了 LINKER去我们的hook的so去找这个fopex符号的话,我们必须将这个version的值改成1(global).

否则会这样报错:

/test: relocation error: /lib64/libtestmod.so: symbol fopex, version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference

我们做以下修改:

改完之后:

 

readelf  --dyn-syms   libtestmod.so 

Symbol table '.dynsym' contains 13 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000000004a0     0 SECTION LOCAL  DEFAULT    9 
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fopex
     5: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fclose@GLIBC_2.2.5 (2)
     7: 00000000002008a8     0 NOTYPE  GLOBAL DEFAULT  ABS _end
     8: 0000000000200898     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
     9: 0000000000200898     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    10: 00000000000005cc    52 FUNC    GLOBAL DEFAULT   11 test_so_call
    11: 00000000000004a0     0 FUNC    GLOBAL DEFAULT    9 _init
    12: 0000000000000638     0 FUNC    GLOBAL DEFAULT   12 _fini

 

这样fopex如果在libc.so里找不到的话,链接程序会加载ibdl.so在ibdl.so里去找这个fopex, 结果找着了,于是我们的hook就成功了。

 

测试,

环境:

要先设置一个交叉编译的环境,使用以下步骤:

#NDK_ROOT是你安装ndk的目录 。
1.  export SYSROOT=$NDK_ROOT/platforms/android-21/arch-x86

2. mkdir toolchain

3. $NDK_ROOT/ools/make-standalone-toolchain.sh --arch=x86  --platform=android-21  --install-dir=tc_androidx86

4. export PATH=$PATH:"$PWD/tc_androidx86/bin"

编写编译脚本:

GCC=$PWD/tc_androidx86/bin/i686-linux-android-gcc

$GCC testhook.c -pie -fPIE -shared -o ibdl.so
$GCC testmod.c  -pie -fPIE -shared -o libtestmod.so
$GCC test.c -pie -fPIE -L.  -ltestmod -o test

代码:

test.c

#include <stdio.h>
#include "test.h"

int main(int argc, char ** argv)
{
	
	test_so_call("test");

	return 0;
}

testmod.c

#include <stdio.h>
#include "test.h"

void test_so_call(const char *sz)
{
	FILE *f = fopen(sz,"rb");
	printf("%s %p\n", "adfsaf",f);
	if(!f)
		fclose(f);
}

testhook.c


#include <stdio.h>

#define EXPORT __attribute__((visibility("default")))

EXPORT FILE *fopex(const char * filename , const char *mode)
{
	printf("fopex called\n");
	return fopen(filename,mode);
}

 

执行结果:

adb push libtestmod.so /lib/
adb push ibdl.so /lib/
adb push test /data/local
adb shell chmod +x /lib/libtestmod.so 
adb shell chmod +x /lib/ibdl.so 
adb shell chmod +x /data/local/tes 

$ adb shell /data/local/test
fopex called
adfsaf 0x0

成功!

实际对于apk安装来说,ibdl.so这样的文件不会给正确安装,这样安装之后就会造成ibdl.so加载时缺失,由于libdl.so基本地每个so都是需要的,其实这个声明是多余的,这个so早就给加载了,就算没有给加载,hook的so里也会加载这个so,

其实我们可以,直接把so改成libxy.so, 效果也是一样的。

修改so的脚本:


function removeVersion()
{


#readelf    -S   "$1" | grep VERSYM 

versymOff=`readelf    -S   "$1" | grep VERSYM | awk '{print $5}' | tr "[:lower:]" "[:upper:]"`


#echo "obase=10;ibase=16;$versymOff" 

versymOff=`echo "obase=10;ibase=16;$versymOff" | bc `

#echo "$versymOff"

readelf   --dyn-syms $1 |  grep -E "fclosx|closx|reax|opex|lstax|stax|fopex|lseex|freax|fwritx|writx|accesx|flocx|lseek6x|fstax"  | awk '{print "index="$1"; symbol="$8";"}' | tr -d ":@LIBC" | while read line; do 

eval "$line"


#echo "$index * 2 + $versymOff "

offme=`echo "$index * 2 + $versymOff" | bc`

echo "$offme"

done  |  xargs  replver $1 $2


}

	


for cc in Development Release ; do 


	arrUnityPatchSearch=("fopen" "open" "fread" "read"  "fwrite" "write"  "lseek" "lseek64" "flock" "fclose"  "close" "access" "lstat" "stat" "fstat" "libdl.so" );


	mydir=`pwd` 
	
	for patchArch in  armeabi-v7a  arm64-v8a x86 ; do
	    cd il2cpp/${cc}/Libs/$patchArch
	 	
	  	hexstr=`xxd -p libu.so | tr -d '\n'`
	  	for fn in ${arrUnityPatchSearch[*]} ; do 

			pat=`echo -n "$fn" | xxd -p`
			if [ "libdl.so" == "$fn" ]; then
				rel=`echo -n "libhs.so" | xxd  -p`
			else
				rel=`echo -n "${fn%?}x" | xxd  -p`
			fi

			pat="00${pat}00"
			rel="00${rel}00"
			hexstr=`echo "$hexstr" | sed "s/${pat}/${rel}/g" `
		done

		echo  "$hexstr" | xxd -r -p > libu2.so

	    echo "il2cpp/${cc}/Libs/$patchArch"
		removeVersion libu2.so libu3.so
	  cd  "$mydir"
	done 

done

去掉LIBC的程序:

#include <stdio.h>

int main(int argc, char **argv)
{
	if(argc < 4)
		return -1;
	
	FILE *f = fopen(argv[1],"rb");

	if(f)
	{
		fseek(f,0,SEEK_END);
		int num = ftell(f);
		printf("file size is %d\n",num);
		char *szc = malloc(num);

		fseek(f,0,SEEK_SET);

		fread(szc,num,1,f);

		fclose(f);

		for(int i=3;i<argc;i++)
		{
			int offset = atoi(argv[i]);
			printf("offset is  %d\n",offset);
			szc[offset] = 0;
			szc[offset+1] = 0;
			
		}


		remove(argv[2]);
		f =  fopen(argv[2],"wb"); 
		if(f)
		{
			fwrite(szc,num,f);

			fclose(f);
		}
		else
		{
			printf("open target file failed %s\n",argv[2]);
		}

		free(szc);
	}

	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值