首页
社区
课程
招聘
[原创] Frida 自机简易持久化方案 (需要root)
发表于: 2021-4-1 14:42 17901

[原创] Frida 自机简易持久化方案 (需要root)

2021-4-1 14:42
17901

app的启动在很早期的阶段会经由zygote进程fork出子进程, 该过程在android源码中搜索nativeForkAndSpecialize可以找到.

通过lief之类的工具感染android系统库令zygote加载我们的so, 并在so中用pthread_atfork捕获fork行为, 之后可以进行app包名的判断, 然后干点啥 (比如加载frida-gadget).

在这个过程中有小踩了几个坑, 分别是:

较高版本的android的链接器命名空间机制导致不能将so随便放(比如/data/local/tmp/),就算指定绝对路进行加载, 在开启了selinux的情况下仍然会报错找不到库从而加载失败. 解决方式可以看看 https://github.com/hack0z/byOpen 或者关闭selinux, 或者老老实实把库放在/system/lib(64)/目录下面.

在fork之后不能马上进行用户或app的判断(此时还是root用户)和线程的创建(加载frida-gadget会创建线程), 否则会发生selinux_context的切换失败导致app无法启动. 解决方式是通过setitimer和signal延迟一段时间等它切换完了再执行. 在自机上测的是使用ITIMER_PROF的话等待60毫秒就可以了, 可以再长些因为之后还要等它将cmdline切换至app的进程名.

frida-gadget配置使用v8环境时在调用dlopen进行初始化时需要一个足够大的栈空间, 否则v8会初始化失败, 错误原因是栈空间不足, 可以通过手动指定一个足够大的栈空间解决.

源码见 https://github.com/tacesrever/easy-frida/tree/master/gadget

完整食用方式

编译 gadget-loader.cpp 可以使用自己常用的IDE新建一个android C++库项目导入gadget-loader.h, gadget-loader.cppnlohmann/json.hpp

/proc/zygote's pid/maps中找一个看着顺眼的库, 以libpng.so为例

使用adb pull将该库的32位与64位版本都pull下来, 先做个备份

使用lief分别为它们添加一个依赖库, 库名称可以随便起, 看自己喜好

鉴于使用现在的0.11.5版本的LIEF库注入android库会出现问题(参考issue#396),提一下当时使用的环境是LIEF版本为0.10.1,系统为android 9
如issue#396仍未解决,且加载修改后的android库出现bad relocation header问题,可以使用如下代码对inject后的so进行修补:

将编译完后的gadget-loader库名改为你起的名字, 和注入后的libpng.so, 下载下来改名后的frida-gadget.so一起用adb push放进/system/lib(64)/下面, 注意32位和64位的分别放一起,别放错了

参考gadget文档, 在/system/lib(64)/下分别新建与frida-gadget.so同名的扩展名为.config的文件, 内容为

运行adb shell pkill -f zygote, 等待手机从黑屏重新恢复, 这一步不会断开adb连接, 如果万一这一步手机黑屏醒不过来就要用到备份的libpng.so恢复, 错误原因可以使用logcat自查.

等手机重新亮起来后运行adb shell ps -ef | grep zygote得到zygote的pid, 再运行adb shell cat /proc/zygote的pid/maps | grep gadget-loader.so(你起的库名) 看到有输出就成功了.

 
 
 
 
 
 
 
import lief
bin = lief.parse("libpng.so"
bin.add_library("gadget-loader.so") # 你起的库名称 
bin.write("libpng.so") # 之前要做好备份
import lief
bin = lief.parse("libpng.so"
bin.add_library("gadget-loader.so") # 你起的库名称 
bin.write("libpng.so") # 之前要做好备份
import lief
def read_sleb128(content, offset):
    value = 0
    len = 0
    shift = 0
    while 1:
        a_byte = content[offset]
        offset += 1
        value += (a_byte & 0x7f) << shift
        shift += 7
        len += 1
        if a_byte < 128:
            break
    return (value, len)
def encode_leb128(value):
    if value == 0:
        return [0]
    ret = []
    while value != 0:
        a_byte = value & 0x7F
        value >>= 7
        if value > 0:
            a_byte |= 0x80
        ret.append(a_byte)
    return ret
def add_library_for_androidso(target_path, libname):
    output_path = target_path + ".injected.so"
    sofile = lief.parse(target_path)
    if sofile.type == sofile.type.CLASS32:
        addr_size = 4
    else:
        addr_size = 8
    sofile.add_library(libname)
    sofile.write(output_path)
    seg_relr = sofile.get_section(".relr.dyn")
    seg_relro = sofile.get_section(".data.rel.ro")
    sofile.patch_address(seg_relr.virtual_address, seg_relro.virtual_address, addr_size)
    sofile.patch_address(seg_relro.virtual_address, seg_relro.virtual_address, addr_size)
    seg_got = sofile.get_section(".got")
    seg_rel = None
    if sofile.type == sofile.type.CLASS32:
        seg_rel = sofile.get_section(".rel.dyn")
    else:
        seg_rel = sofile.get_section(".rela.dyn")
    content = seg_rel.content
    current_pos = 4
    for i in range(4):
        tmp_value, len = read_sleb128(content, current_pos)
        current_pos += len
        # skip num_relocs r_offset group_size group_flags
    encoded_gotaddr = encode_leb128(seg_got.virtual_address)
    sofile.patch_address(seg_rel.virtual_address + current_pos, encoded_gotaddr)
    for e in sofile.dynamic_entries:
        if e.tag.value == 0x6000000f: # ANDROID_REL
            e.value = seg_rel.virtual_address
        elif e.tag.value == 0x24: # RELR
            e.value = seg_relr.virtual_address
        elif e.tag.value == 0x60000011: # ANDROID_RELA
            e.value = seg_rel.virtual_address
    sofile.write(output_path)
import lief
def read_sleb128(content, offset):
    value = 0
    len = 0
    shift = 0
    while 1:
        a_byte = content[offset]
        offset += 1
        value += (a_byte & 0x7f) << shift
        shift += 7
        len += 1
        if a_byte < 128:
            break
    return (value, len)
def encode_leb128(value):
    if value == 0:
        return [0]
    ret = []
    while value != 0:
        a_byte = value & 0x7F
        value >>= 7
        if value > 0:
            a_byte |= 0x80
        ret.append(a_byte)
    return ret
def add_library_for_androidso(target_path, libname):
    output_path = target_path + ".injected.so"
    sofile = lief.parse(target_path)

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-4-21 10:56 被tacesrever编辑 ,原因: 更新lief相关问题
收藏
免费 4
支持
分享
最新回复 (6)
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
JniInvocation: Failed to dlopen libart.so: dlopen failed: bad android relocation header.
用lief来添加依赖后系统启动报这个错误,可能是哪里有问题呢?需要自行修复relocation段么
2021-6-24 10:00
2
雪    币: 26
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
强啊
2021-6-24 10:26
0
雪    币: 4898
活跃值: (3118)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
大佬,用你的 lief 库重新编译后,最终打出来的 libart.so 运行时还是报 bad android relocation header,求指点
2021-7-22 09:27
0
雪    币: 3089
活跃值: (2994)
能力值: ( LV12,RANK:367 )
在线值:
发帖
回帖
粉丝
5
anenn 大佬,用你的 lief 库重新编译后,最终打出来的 libart.so 运行时还是报 bad android relocation header,求指点
用别的库试试,比如libpng.so
2021-7-22 10:30
0
雪    币: 3089
活跃值: (2994)
能力值: ( LV12,RANK:367 )
在线值:
发帖
回帖
粉丝
6
我的libart.so在apex包里不方便测试...如果你的libart.so还在system/lib下面,那可能咱系统版本不一样
2021-7-22 10:33
0
雪    币: 122
活跃值: (536)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7

我直接使用的官方给的gadget的so文件,然后也是链接的libpng.so,但是发生了一些错误:

请问这是为什么?这种空指针是什么导致的?

2021-10-21 11:24
0
游客
登录 | 注册 方可回帖
返回
//