android so文件攻防实战-美团libmtguard.so反混淆


BEQ和BNE是一对条件相反的跳转,这之前计算出接下来指令的地址0x3430赋值给R4,loc_340C处的代码通过POP指令将R4的值给PC,看0x3430处的代码。



接下来的代码很多都是这种动态计算跳转地址的。0x3432处的指令从0x3438中取一个dword给R0,跳到0x3440然后再跳到0x407C。0x3444处开始不是指令而是一个跳转表,索引就是R0,值就是接下来指令地址的偏移,0x407C开始的代码就是取这个偏移。所以我们可以写一个脚本patch,计算出偏移,在0x3434处就直接跳过去。


同时还会有一些跳转到0x4094的指令,像上面这样0x3464跳到0x4094之后会返回到0x346C,从0x3462到0x346C这些指令我都直接patch成NOP了。
修复脚本:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

import keystone

from capstone import *

import idc

import ida_bytes

import subprocess

arch = keystone.KS_ARCH_ARM

mode = keystone.KS_MODE_THUMB

ks = keystone.Ks(arch, mode)

md = Cs(CS_ARCH_ARM, CS_MODE_THUMB)

def is_BLX_sub407C(ea):

    ldr_addr = ea

    ldr_flags = idc.get_full_flags(ldr_addr)

    if not idc.is_code(ldr_flags):

        return False

    if idc.print_insn_mnem(ldr_addr) != 'BLX':

        return False

    if idc.print_operand(ldr_addr, 0) != 'sub_407C':

        return False

    return True

def is_BLX_sub4094(ea):

    ldr_addr = ea

    ldr_flags = idc.get_full_flags(ldr_addr)

    if not idc.is_code(ldr_flags):

        return False

    if idc.print_insn_mnem(ldr_addr) != 'BLX':

        return False

    if idc.print_operand(ldr_addr, 0) != 'sub_4094':

        return False

    if idc.print_insn_mnem(ldr_addr - 2) != 'PUSH':

        return False

    if idc.print_insn_mnem(ldr_addr + 8) != 'POP':

        return False

    return True

def func_patch():

    ins_addr = idc.next_head(0)

    while ins_addr != idc.BADADDR:

         if is_BLX_sub407C(ins_addr):

            for i in CodeRefsTo(ins_addr, False):

                if idc.get_wide_word(i + 4) == 18112:

                    index = idc.get_wide_word(i + 6)

                    patch_qword(i + 6, 0x46C046C0)

                    idc.create_insn(i + 6)

                else:

                    index = idc.get_wide_word(i + 4)

                    patch_qword(i + 4, 0x46C046C0)

                    idc.create_insn(i + 4)

                print("i:" + hex(i))

                index = index * 4 + ins_addr + 4

                offset = ida_bytes.get_dword(index)

                target = ins_addr + 0x4 + offset

                command = "BL " + hex(target)

                print("command:" + command)

                pi = subprocess.Popen(['D:\\keystone-0.9.2-win64\\kstool.exe', 'thumb', command, \

                hex(i)], shell=True, stdout=subprocess.PIPE)

                output = pi.stdout.read()

                ins = str(output[-15:-4])[2:-1]

                ins = ins.split(" ")

                ins = "0x" + ins[3] + ins[2] + ins[1] + ins[0]

                print("ins:" + ins)

                patch_dword(i, int(ins, 16))

         if is_BLX_sub4094(ins_addr):

            patch_dword(ins_addr - 2, 0xbf00)

            patch_dword(ins_addr, 0xbf00)

            patch_dword(ins_addr + 2, 0xbf00)

            patch_dword(ins_addr + 4, 0xbf00)

            patch_dword(ins_addr + 6, 0xbf00)

            patch_dword(ins_addr + 8, 0xbf00)

         ins_addr = idc.next_head(ins_addr)

func_patch()

修复结束之后跟踪指令,0x3434处的代码跳到了0x345A,0x345A处的代码主要逻辑是在0x34C6跳到0x34EC通过0x34EC动态计算地址跳到了0x3520,0x3524处的代码跳到了0x365C,0x366E处的代码跳到了0x35A8,0x35A8是JNI_OnLoad的主要逻辑。


如上图所示,0x35A8基本就干了两件事:FindClass(com/meituan/android/common/mtguard/NBridge)和RegisterNatives(com/meituan/android/common/mtguard/NBridge, main, 1)。

 


sub_3680里面调用sub_36B4通过异或解密了一些字符串,上图我已经把解密之后的结果patch进去了。
当然用unidbg是可以直接跑JNI_OnLoad的:


不过这个系列文章主要是想讨论一下so混淆和反混淆,有精力的话还可以对这个so进行完整分析,混淆应该没有啥新的了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值