静态补丁脚本 - 修改 libtolua.so

发布于:2025-07-17 ⋅ 阅读:(14) ⋅ 点赞:(0)

直接改arm64的so, 使用python脚本。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
静态补丁脚本 - 修改 libtolua.so
主要功能:
1. 修改 luaL_loadbuffer 函数,将跳转目标从 luaL_loadbufferx 改为 luaL_loadfilex
2. 在 luaL_loadfilex 函数中实现跳转到自定义的 my_custom_luaL_loadbufferx 函数
"""

import struct
import os
import shutil
from pathlib import Path

class LibtoluaPatcher:
    def __init__(self, target_so_path, custom_so_path):
        self.target_so_path = Path(target_so_path)
        self.custom_so_path = Path(custom_so_path)
        self.backup_path = self.target_so_path.with_suffix('.so.backup')
        
        # 关键地址定义 (基于提供的信息)
        self.luaL_loadbuffer_offset = 0x8A728  # luaL_loadbuffer 函数偏移
        self.luaL_loadfilex_offset = 0x8A530   # luaL_loadfilex 函数偏移 (未使用的函数)
        
    def backup_original(self):
        """备份原始文件"""
        if not self.backup_path.exists():
            shutil.copy2(self.target_so_path, self.backup_path)
            print(f"已备份原始文件到: {self.backup_path}")
        else:
            print(f"备份文件已存在: {self.backup_path}")

    
    def calculate_branch_instruction(self, from_addr, to_addr):
        """计算ARM64分支指令"""
        # 计算相对偏移
        offset = to_addr - from_addr
        
        # 检查是否在B指令范围内 (±128MB)
        if abs(offset) < (1 << 27):
            # 使用B指令 (无条件分支)
            # B指令格式: 0x14000000 | ((offset >> 2) & 0x3FFFFFF)
            offset_encoded = (offset >> 2) & 0x3FFFFFF
            instruction = 0x14000000 | offset_encoded
            return struct.pack('<I', instruction)
        else:
            # 超出B指令范围,需要使用间接跳转
            raise ValueError(f"分支距离太远: {offset}, 需要使用间接跳转")

    
    def patch_luaL_loadbuffer(self, data):
        """修改 luaL_loadbuffer 函数"""
        print("正在修改 luaL_loadbuffer 函数...")
        
        # 原始代码:
        # .text:000000000008A728  MOV X4, #0
        # .text:000000000008A72C  B   luaL_loadbufferx
        
        # 新代码:
        # .text:000000000008A728  MOV X4, #0  (保持不变)
        # .text:000000000008A72C  B   luaL_loadfilex  (修改跳转目标)
        
        # 计算从 luaL_loadbuffer+4 到 luaL_loadfilex 的分支指令
        from_addr = self.luaL_loadbuffer_offset + 4  # +4 因为要修改第二条指令
        to_addr = self.luaL_loadfilex_offset
        
        try:
            branch_instruction = self.calculate_branch_instruction(from_addr, to_addr)
            
            # 修改数据
            patch_offset = self.luaL_loadbuffer_offset + 4
            data[patch_offset:patch_offset+4] = branch_instruction
            
            print(f"已修改 luaL_loadbuffer+4 (0x{patch_offset:X}) 的跳转目标")
            
        except ValueError as e:
            print(f"修改 luaL_loadbuffer 失败: {e}")
            return False
        
        return True
    
   
    
    def patch_luaL_loadfilex(self, data):
        """在 luaL_loadfilex 函数中实现跳转到自定义函数"""
        print("正在修改 luaL_loadfilex 函数...")
        
        # 创建一个简化的hook代码,直接跳转到我们预设的地址
        # 在实际应用中,这个地址需要通过运行时解析获得
        
        # 创建hook代码桩
        stub_code = self.create_dynamic_dlopen_dlsym_and_call_stub()
        
        # 将代码写入 luaL_loadfilex 位置
        patch_offset = self.luaL_loadfilex_offset
        if len(stub_code) <= 0x1F8:  # 确保不超出函数空间 (到下一个函数的距离)
            data[patch_offset:patch_offset+len(stub_code)] = stub_code
            print(f"已在 luaL_loadfilex (0x{patch_offset:X}) 处植入hook代码")
            return True
        else:
            print(f"Hook代码太长 ({len(stub_code)} 字节),超出可用空间")
            return False
    
    
    # def create_dynamic_loader_stub(self):
    #     """创建SVC系统调用代码桩"""
    #     # 使用SVC指令调用自定义的lua_loadbuffer系统调用
    #     # 根据lua_file_writer.c中的定义:SVC_LUA_LOADBUFFER = 0x1001
        
    #     instructions = []
        
    #     # 函数参数已经在X0-X4寄存器中:
    #     # X0 = lua_State *L
    #     # X1 = const char *buff  
    #     # X2 = size_t sz
    #     # X3 = const char *name
    #     # X4 = const char *mode
        
    #     # 直接调用SVC指令,无需保存/恢复寄存器
    #     # SVC #0x1001 (SVC_LUA_LOADBUFFER)
    #     svc_num = 0x1001
    #     svc_instruction = 0xD4000001 | (svc_num << 5)
    #     instructions.append(struct.pack('<I', svc_instruction))
        
    #     # 返回到调用者
    #     # RET
    #     instructions.append(struct.pack('<I', 0xD65F03C0))
        
    #     return b''.join(instructions)
    def create_dynamic_dlopen_dlsym_and_call_stub(self):
        """创建动态加载和调用代码桩"""
        # 这个函数生成ARM64汇编代码,实现:
        # 1. dlopen("liblua_file_writer.so", RTLD_LAZY)
        # 2. dlsym(handle, "my_custom_luaL_loadbufferx")
        # 3. 跳转到获取的函数地址
        
        instructions = []
        


#000000000008A6FC             luaL_loadbufferx                        ; CODE XREF: sub_C1B80+110↓p
#000000000008A530 F4 FF FF 17                 B luaL_loadbufferx (从0x00000000008A530跳转到0x8A6FC)
        # 正确计算从0x8A530跳转到0x8A6FC的B指令
        # 偏移量:0x8A6FC - 0x8A530 = 0x1CC = 460字节
        # 指令偏移:460 / 4 = 115
        # B指令编码:0x14000000 | 115 = 0x14000073
        #instructions.append(struct.pack('<I', 0x14000073))  # B luaL_loadbufferx

#     => 0x0000007f3d352530 <+0>:	sub	sp, sp, #0x80
#    0x0000007f3d352534 <+4>:	stp	x24, x23, [sp]
#    0x0000007f3d352538 <+8>:	stp	x22, x21, [sp,#16]
#    0x0000007f3d35253c <+12>:	stp	x20, x19, [sp,#32]
#    0x0000007f3d352540 <+16>:	stp	x29, x30, [sp,#48]
#    0x0000007f3d352544 <+20>:	str	x5, [sp,#56]

        # 保存参数寄存器 X0-X4 (逐个保存到不同栈位置)
        instructions.append(struct.pack('<I',0xD102C3FF))     # FF C3 02 D1                         SUB             SP, SP, #0xB0 
        instructions.append(struct.pack('<I',0xA9005FF8))     #F8 5F 00 A9                             STP             X24, X23, [SP]
        instructions.append(struct.pack('<I',0xA90157F6))     #F6 57 01 A9                             STP             X22, X21, [SP,#0x10]
        instructions.append(struct.pack('<I',0xA9024FF4))     #F4 4F 02 A9                             STP             X20, X19, [SP,#0x30]
        instructions.append(struct.pack('<I',0xA9037BFD))     #FD 7B 03 A9                             STP             X29, X30, [SP,#0x40]
        instructions.append(struct.pack('<I',0xF90057E5))     #E5 57 00 F9                             STR             X5, [SP,#0xA8]

        instructions.append(struct.pack('<I',0xAA0203F5))     #F5 03 02 AA                             MOV             X21, X2
        instructions.append(struct.pack('<I',0xAA0103F6))     #F6 03 01 AA                             MOV             X22, X1
        instructions.append(struct.pack('<I',0xAA0003F7))     #F7 03 00 AA                             MOV             X23, X0
        instructions.append(struct.pack('<I',0xAA0403F3))     #F3 03 04 AA                             MOV             X19, X4
        instructions.append(struct.pack('<I',0xAA0303F4))     #F4 03 03 AA                             MOV             X20, X3

        
        # 准备dlopen参数
        # 需要将"liblua_file_writer.so"字符串地址加载到X0
        # RTLD_LAZY (1) 加载到X1
        
        # 使用原始SO文件中已存在的字符串
        # "liblua_file_writer.so" 在 0x133C20
        # 当前函数在 0x8A530,计算相对偏移
        lib_string_addr = 0x133C20
        #0000000000133C68
        # GROUP
      #  lib_string_addr = 0x00DD668

        current_addr = self.luaL_loadfilex_offset + (len(instructions) * 4)
        lib_offset = lib_string_addr - current_addr
        
        # ADR X0, #lib_offset  ; 获取"liblua_file_writer.so"地址
        if abs(lib_offset) < (1 << 20):  # ADR指令范围检查
            adr_instruction = 0x10000000 | ((lib_offset >> 2) & 0x7FFFF) << 5
            instructions.append(struct.pack('<I', adr_instruction))
        else:
            # 如果超出ADR范围,使用ADRP+ADD组合
            page_offset = (lib_offset >> 12) & 0x1FFFFF
            adrp_instruction = 0x90000000 | (page_offset << 5)
            instructions.append(struct.pack('<I', adrp_instruction))
            # ADD X0, X0, #(lib_offset & 0xFFF)
            add_instruction = 0x91000000 | ((lib_offset & 0xFFF) << 10)
            instructions.append(struct.pack('<I', add_instruction))
        
        # MOV X1, #1  ; RTLD_LAZY
        instructions.append(struct.pack('<I', 0xD2800021))
        
        # 调用dlopen
        # 这里需要通过GOT表调用dlopen,简化处理:
        # 假设dlopen在已知偏移处,实际应该通过PLT调用
        # BL dlopen  ; 计算到dlopen的偏移
        # dlopen在.plt:0x17980,当前位置大约在0xB8AC0附近
        dlopen_plt_addr = 0x17980
        current_bl_addr = self.luaL_loadfilex_offset + (len(instructions) * 4)
        dlopen_offset = dlopen_plt_addr - current_bl_addr
        # BL指令格式: 0x94000000 | ((offset >> 2) & 0x3FFFFFF)
        bl_instruction = 0x94000000 | ((dlopen_offset >> 2) & 0x3FFFFFF)
        instructions.append(struct.pack('<I', bl_instruction))
        
        # 检查dlopen返回值,如果不为空则跳过异常触发指令
        # CBNZ X0, #8  ; 如果X0不为0,跳过下一条指令
        instructions.append(struct.pack('<I', 0xB5000040))
        # LDR X16, [X0]  ; 如果X0为0,这里会触发空指针异常
        instructions.append(struct.pack('<I', 0xF9400010))
        
        # 准备dlsym参数
        # X0已经是handle,需要设置X1为符号名
        # "luaL_loadbufferx" 在 0x1336DE
        symbol_string_addr = 0x1336DE;
        current_addr2 = self.luaL_loadfilex_offset + (len(instructions) * 4)
        symbol_offset = symbol_string_addr - current_addr2 +2
        
        # ADR X1, #symbol_offset  ; 获取"luaL_loadbufferx"地址
        if abs(symbol_offset) < (1 << 20):  # ADR指令范围检查
            adr_instruction2 = 0x10000000 | ((symbol_offset >> 2) & 0x7FFFF) << 5 | 1  # 目标寄存器X1
            instructions.append(struct.pack('<I', adr_instruction2))
        else:
            # 如果超出ADR范围,使用ADRP+ADD组合
            page_offset2 = (symbol_offset >> 12) & 0x1FFFFF
            adrp_instruction2 = 0x90000000 | (page_offset2 << 5) | 1  # 目标寄存器X1
            instructions.append(struct.pack('<I', adrp_instruction2))
            # ADD X1, X1, #(symbol_offset & 0xFFF)
            add_instruction2 = 0x91000000 | ((symbol_offset & 0xFFF) << 10) | (1 << 5) | 1
            instructions.append(struct.pack('<I', add_instruction2))
        
        # 调用dlsym
        # BL dlsym  ; 计算到dlsym的偏移
        # dlsym在.plt:0x17A40
        dlsym_plt_addr = 0x17A40
        current_bl_addr2 = self.luaL_loadfilex_offset + (len(instructions) * 4)
        dlsym_offset = dlsym_plt_addr - current_bl_addr2
        # BL指令格式: 0x94000000 | ((offset >> 2) & 0x3FFFFFF)
        bl_instruction2 = 0x94000000 | ((dlsym_offset >> 2) & 0x3FFFFFF)
        instructions.append(struct.pack('<I', bl_instruction2))
        
        # 检查dlopen返回值,如果为空则访问其指针值造成异常
        # CBNZ X0, #8  ; 如果X0不为0,跳过下一条指令
        instructions.append(struct.pack('<I', 0xB5000040))
     
        # LDR X16, [X0]  ; 如果X0为0,这里会触发空指针异常
        instructions.append(struct.pack('<I', 0xF9400010))
        
    
        # 保存dlsym返回的函数地址到X16
        # MOV X17, X0
        instructions.append(struct.pack('<I', 0xAA0003F1))

        instructions.append(struct.pack('<I',0xAA1703E0))     #E0 03 17 AA                             MOV             X0, X23
        instructions.append(struct.pack('<I',0xAA1603E1))     #E1 03 16 AA                             MOV             X1, X22
        instructions.append(struct.pack('<I',0xAA1503E2))     #E2 03 15 AA                             MOV             X2, X21
        instructions.append(struct.pack('<I',0xAA1403E3))     #E3 03 14 AA                             MOV             X3, X20
        instructions.append(struct.pack('<I',0xAA1303E4))     #E4 03 13 AA                             MOV             X4, X19
    
        # 跳转到目标函数
        instructions.append(struct.pack('<I',0xA9424FF4))     #F4 4F 42 A9                             LDP             X20, X19, [SP,#0x30+var_10]
        instructions.append(struct.pack('<I',0xA94157F6))     #F6 57 41 A9                             LDP             X22, X21, [SP,#0x30+var_20]
        instructions.append(struct.pack('<I',0xA9405FF8))     #F8 5F 40 A9                             LDP             X24, X23, [SP]
        instructions.append(struct.pack('<I',0xA9437BFD))     #FD 7B 43 A9                             LDP             X29, X30, [SP,#0x30+var_s0]
        instructions.append(struct.pack('<I',0xF9401FE5))     #E5 1F 40 F9                             LDR             X5, [SP,#0x38]
        instructions.append(struct.pack('<I',0x9102C3FF))     #FF C3 02 91                 ADD             SP, SP, #0xB0 ;

        instructions.append(struct.pack('<I', 0xD61F0220))    #20 02 1F D6                               BR              X17  

        
        return b''.join(instructions)
    
    def apply_patches(self):
        """应用所有补丁"""
        # 读取目标文件
        with open(self.target_so_path, 'rb') as f:
            data = bytearray(f.read())
        
        print(f"文件大小: {len(data)} 字节")
        
        # 应用补丁
        success = True
        
        if not self.patch_luaL_loadbuffer(data):
            success = False
        
        if not self.patch_luaL_loadfilex(data):
            success = False
        
        if success:
            # 写回修改后的文件
            with open(self.target_so_path, 'wb') as f:
                f.write(data)
            print(f"补丁应用成功!修改后的文件: {self.target_so_path}")
        else:
            print("补丁应用失败!")
        
        return success
    
    def restore_backup(self):
        """恢复备份文件"""
        if self.backup_path.exists():
            shutil.copy2(self.backup_path, self.target_so_path)
            print(f"已恢复备份文件: {self.target_so_path}")
        else:
            print("备份文件不存在!")

def main():
    # 文件路径
    target_so = "libtolua.so"
    custom_so = "iblua_file_writer.so"
    
    # 检查文件是否存在
    if not os.path.exists(target_so):
        print(f"目标文件不存在: {target_so}")
        return
    
    if not os.path.exists(custom_so):
        print(f"自定义SO文件不存在: {custom_so}")
        return
    
    # 创建补丁器
    patcher = LibtoluaPatcher(target_so, custom_so)
    
    # 应用补丁
    if patcher.apply_patches():
        print("\n补丁应用完成!")
        print("使用说明:")
        print("1. 修改后的libtolua.so会在luaL_loadbuffer调用时跳转到我们的hook函数")
        print("2. 如需恢复原始文件,请运行: patcher.restore_backup()")
    else:
        print("\n补丁应用失败!")

if __name__ == "__main__":
    main()


网站公告

今日签到

点亮在社区的每一天
去签到