使用Unicorn Engine绕过混淆完成算法的调用

最近在研究用Unicorn Engine调用SO时,发现网上的资料很少,并且没有一个完整的调用实例。所以我把自己做的发出来跟大家分享,共同学习进步。

下面开始: 

一、我们的目的

    

以上一串字符串中vf字段为标红部分的signature。该算法在libmcto_media_player.so+0x249BC8处。如果是Android端调用的话很简单,我们编写一个loader调用该函数传入参数获取返回值即可轻易拿到。但如果你想在Windows或linux上获取该signature就会比较麻烦。一般都是通过逆向还原代码来进行移植。但是如果遇见混淆或VM的代码,那将是痛苦的。所以这就是我为什么要介绍Unicorn Engine的原因了。我们要用Unicorn Engine来完成跨平台的调用。 


二、 用NDK编写loader用做验证用。

       

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

#include <stdio.h>

#include <string.h>

#include <dlfcn.h>

#include <jni.h>

#include <stdlib.h>

 

 

int main(int argc,char** argv)

{

 

    JavaVM* vm;

    JNIEnv* env;

    jint res;

     

    JavaVMInitArgs vm_args;

    JavaVMOption options[1];

    options[0].optionString = "-Djava.class.path=.";

    vm_args.version=0x00010002;

    vm_args.options=options;

    vm_args.nOptions =1;

    vm_args.ignoreUnrecognized=JNI_TRUE;

     

     

    printf("[+] dlopen libdvm.so\n");

    void *handle = dlopen("/system/lib/libdvm.so", RTLD_LAZY);//RTLD_LAZY RTLD_NOW

    if(!handle){

    printf("[-] dlopen libdvm.so failed!!\n");

    return 0;

    }

 

    typedef int (*JNI_CreateJavaVM_Type)(JavaVM**, JNIEnv**, void*);

    JNI_CreateJavaVM_Type JNI_CreateJavaVM_Func = (JNI_CreateJavaVM_Type)dlsym(handle, "JNI_CreateJavaVM");

    if(!JNI_CreateJavaVM_Func){

    printf("[-] dlsym failed\n");

    return 0;

    }

    res=JNI_CreateJavaVM_Func(&vm,&env,&vm_args);

        //libmctocurl.so   libcupid.so 为libmcto_media_player.so的依赖库

    dlopen("/data/local/tmp/libmctocurl.so",RTLD_LAZY);

    dlopen("/data/local/tmp/libcupid.so",RTLD_LAZY);

    void* si=dlopen("/data/local/tmp/libmcto_media_player.so",RTLD_LAZY);

    if(si == NULL)

    {

        printf("dlopen err!\n");

        return 0;

    }

 

    typedef char* (*FUN1)(char* plain);

    void *addr=(void*)(*(int*)((size_t)si+0x8c)+0x249BC9);

    FUN1 func=(FUN1)addr;

    if(func==NULL)

    {

        printf("can't find  func\n");

        return 0;

    }

    

    char *plain="/vps?tvid=11949478009&vid=7b23569cbed511dd58bcd6ce9ddd7b42&v=0&qypid=11949478009_unknown&src=02022001010000000000&tm=1519712402&k_tag=1&k_uid=359125052784388&bid=1&pt=0&d=1&s=0&rs=1&dfp=1413357b5efa4a4130b327995c377ebb38fbd916698ed95a28f56939e9d8825592&k_ver=9.0.0&k_ft1=859834543&k_err_retries=0&qd_v=1";

    char* ret=func(plain);

    printf("%s\n",ret);

    return 0;

}

我之前已经将那3个so(libmctocurl.so、libcupid.so、libmcto_media_player.so) 放到/data/local/tmp下。运行结果与上面的vf字段一致。

三、 使用Unicorn Engine

由于使用了混淆。分析起来比较麻烦,所以使用Unicorn进行调用

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

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

#include "stdafx.h"

#include <inttypes.h>

#include <string.h>

#include <math.h>

#include <unicorn/unicorn.h>

#pragma comment(lib,"unicorn.lib")

//#define DEBUG

#define _DWORD uint32_t

#define LODWORD(x)  (*((_DWORD*)&(x)))

#define HIDWORD(x)  (*((_DWORD*)&(x)+1))

#define ADDRESS 0x249BC8

#define BASE  0xaef52000

#define CODE_SIZE  8*1024*1024

#define STACK_ADDR  BASE+CODE_SIZE

#define STACK_SIZE  1024 * 1024

#define PARAM_ADDR  STACK_ADDR+STACK_SIZE

#define PARAM_SIZE  1024 * 1024

uint32_t offset=0;

static uint32_t create_mem(uc_engine *uc,char* buffer,uint32_t len)

{

    uint32_t addr = PARAM_ADDR + offset;

    uc_mem_write(uc, addr, buffer, len);

    offset += len + 1;

    return addr;

}

 

static void print_reg(uc_engine *uc, uint32_t address)

{

#ifdef DEBUG

    uint32_t pc = 0;

    uc_reg_read(uc, UC_ARM_REG_PC, &pc);

    if (pc == address)

    {

        printf("========================\n");        printf("Break on 0x%x\n", pc);

        uint32_t values = 0;

        uc_reg_read(uc, UC_ARM_REG_R0, &values);        printf("R0 = 0x%x \n", values);

        uc_reg_read(uc, UC_ARM_REG_R1, &values);        printf("R1 = 0x%x \n", values);

        uc_reg_read(uc, UC_ARM_REG_R2, &values);        printf("R2 = 0x%x \n", values);

        uc_reg_read(uc, UC_ARM_REG_R3, &values);        printf("R3 = 0x%x \n", values);

        uc_reg_read(uc, UC_ARM_REG_R4, &values);        printf("R4 = 0x%x \n", values);

        uc_reg_read(uc, UC_ARM_REG_R5, &values);        printf("R5 = 0x%x \n", values);

        uc_reg_read(uc, UC_ARM_REG_R6, &values);        printf("R6 = 0x%x \n", values);

        uc_reg_read(uc, UC_ARM_REG_PC, &values);        printf("PC = 0x%x \n", values);

        uc_reg_read(uc, UC_ARM_REG_SP, &values);        printf("SP = 0x%x \n", values);

        printf("========================\n");

    }

#endif // DEBUG

}

static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data)

{

#ifdef DEBUG

    printf(">>> Tracing instruction at 0x%" PRIx64 ", instruction size = 0x%x\n", address, size);

#endif // DEBUG

    switch (address)

    {

        //strlen

        case BASE + 0x249BEE:

        {

            uint32_t r0 = 0;

            char buffer[4096] = "";

            uc_reg_read(uc, UC_ARM_REG_R0, &r0);

            uc_mem_read(uc, r0, buffer, 4096);

            r0 = strlen(buffer);

            uc_reg_write(uc, UC_ARM_REG_R0, &r0);

            uint32_t pc = address;

            pc += 5;

            uc_reg_write(uc, UC_ARM_REG_PC, &pc);

            break;

        }

        //malloc

        case BASE+ 0x249f3c:

        case BASE+ 0x249f06:

        case BASE + 0x249c02:

        {

            uint32_t r0 = 0;

            uc_reg_read(uc, UC_ARM_REG_R0, &r0);

            char* buffer = (char*)malloc(r0);

            r0=create_mem(uc, buffer, r0);

            free(buffer);

            uc_reg_write(uc, UC_ARM_REG_R0, &r0);

            uint32_t pc = address;

            pc += 5;

            uc_reg_write(uc, UC_ARM_REG_PC, &pc);

            break;

        }

        //memcpy 后为THUMB指令

        case BASE+0x249c68:

        case BASE+0x249c0e:

        case BASE+0x24947A:

        case BASE+0x249456:

        {

            uint32_t r0 = 0;

            uint32_t r1 = 0;

            uint32_t r2 = 0;

            uc_reg_read(uc, UC_ARM_REG_R0, &r0);

            uc_reg_read(uc, UC_ARM_REG_R1, &r1);

            uc_reg_read(uc, UC_ARM_REG_R2, &r2);

            char *buffer =(char*)malloc(r2);

            uc_mem_read(uc, r1, buffer, r2);

            uc_mem_write(uc, r0, buffer, r2);

            free(buffer);

            uint32_t pc = address;

            //memcpy 后为ARM指令

            if (address == BASE + 0x249c68)

                pc += 4;

            else

                pc += 5;

            uc_reg_write(uc, UC_ARM_REG_PC, &pc);

            break;

        }

        //特殊处理4字ARM指令

        case BASE + 0x249C6C:

        {

            uint32_t pc = address;

            pc += 5;

            uint32_t r0 = 0x2c0;

            uc_reg_write(uc, UC_ARM_REG_R0, &r0);

            uc_reg_write(uc, UC_ARM_REG_PC, &pc);

            break;

        }

        //跳过stack_guard错误的内存地址

        case BASE + 0x249BD8:

        {

            uint32_t pc = address;

            pc += 7;

            uc_reg_write(uc, UC_ARM_REG_PC, &pc);

            break;

        }

        //sin函数

        case BASE+0x249EE8:

        {

            uint32_t r0 = 0;

            uint32_t r1 = 0;

            uc_reg_read(uc, UC_ARM_REG_R0, &r0);

            uc_reg_read(uc, UC_ARM_REG_R1, &r1);

            double value = 0;

            memcpy(&value, &r0, 4);

            memcpy((char*)&value+4, &r1, 4);

            double ret=sin(value);

            r0 = LODWORD(ret);

            r1 = HIDWORD(ret);

            uc_reg_write(uc, UC_ARM_REG_R0, &r0);

            uc_reg_write(uc, UC_ARM_REG_R1, &r1);

            uint32_t pc = address;

            pc += 5;

            uc_reg_write(uc, UC_ARM_REG_PC, &pc);

            break;

        }

        //free

        case BASE+ 0x24a68c:

        case BASE+0x249f24:

        {

            uint32_t pc = address;

            pc += 5;

            uc_reg_write(uc, UC_ARM_REG_PC, &pc);

        }  

        default:

        {

            print_reg(uc, address);

            break;

        }

    }

}

static unsigned char* read_file(char* path, uint32_t* len)

{

    FILE* fp = fopen(path, "rb");

    if (fp == NULL)

        return nullptr;

    fseek(fp, 0, SEEK_END);

    *len = ftell(fp);

    fseek(fp, 0, SEEK_SET);

    unsigned char* code = (unsigned char*)malloc(*len);

    memset(code, 0, *len);

    fread(code, 1, *len, fp);

    fclose(fp);

    return code;

}

static void test_thumb(void)

{

    uc_engine *uc;

    uc_err err;

    uc_hook trace1, trace2;

    uint32_t sp = STACK_ADDR; 

    offset = 0;

    err = uc_open(UC_ARCH_ARM, UC_MODE_THUMB, &uc);

    if (err) {

        printf("Failed on uc_open() with error returned: %u (%s)\n",

            err, uc_strerror(err));

        return;

    }

    char plain[] = "/vps?tvid=11949478009&vid=7b23569cbed511dd58bcd6ce9ddd7b42&v=0&qypid=11949478009_unknown&src=02022001010000000000&tm=1519712402&k_tag=1&k_uid=359125052784388&bid=1&pt=0&d=1&s=0&rs=1&dfp=1413357b5efa4a4130b327995c377ebb38fbd916698ed95a28f56939e9d8825592&k_ver=9.0.0&k_ft1=859834543&k_err_retries=0&qd_v=1";

    uc_mem_map(uc, PARAM_ADDR, PARAM_SIZE, UC_PROT_ALL);

    uc_mem_map(uc, BASE, CODE_SIZE, UC_PROT_ALL);

    uint32_t r0 = PARAM_ADDR;

    uint32_t sp_start = sp + STACK_SIZE;

    int ret=uc_mem_map(uc, sp, sp_start - sp, UC_PROT_ALL);

    uint32_t len = 0;

    unsigned char* code = read_file("./aef52000_36e000.so", &len);

    uc_mem_write(uc, BASE, code, len);

    free(code);

    create_mem(uc, plain, strlen(plain) + 1);

    uc_reg_write(uc, UC_ARM_REG_R0, &r0);

    uc_reg_write(uc, UC_ARM_REG_SP, &sp);

    uc_hook_add(uc, &trace2, UC_HOOK_CODE, hook_code, NULL, 1, 0);

    err = uc_emu_start(uc, BASE + 0x249BC8 + 1, BASE + 0x24a692, 0, 0);

    if (err) {

        printf("Failed on uc_emu_start() with error returned: %u\n", err);

    }

    char buffer[4096] = "";

    uc_reg_read(uc, UC_ARM_REG_R0, &r0);

    uc_mem_read(uc, r0, buffer, 4096);

    printf("result:%s\n", buffer);

    uc_close(uc);

}

int main()

{

    test_thumb();

    system("pause");

    return 0;

}

 

 

代码已经给了,就不多说了,

我没有直接使用libmcto_media_player.so因为data段需要重定位。所以我写了一个dump工具。

将SO从内存中dump出来。直接调用这段已经重定位过的内存。

修复内存报错的位置。实现该算法中涉及的几个API 包括 strlen memcpy malloc free  sin 函数。 

主要就是注意BLX调用完API的时候下一条指令是THUMB模式还是ARM模式就好。

最后运行,运行结果也与vf字段一致。

dump通过命令

1

2

3

4

5

shell@hammerhead:/data/local/tmp $./dump ./libmcto_media_player.so ./libmctocurl.so ./libcupid.so

[+] dlopen ./libmctocurl.so

[+] dlopen ./libcupid.so

[+] dlopen libdvm.so

[+] save 0xaf009000_0x377000.so

四、总结

       这只是一个简单的算法函数,涉及的API并不多,如果是复杂的算法涉及API数量庞大这种自己实现API的方式就并不可取。所以接下来有时间会继续研究SO的完整的调用。让他像loader一样方便。

五、参考

Android SO 高阶黑盒利用

挑战4个任务:迅速上手Unicorn Engine


 

[招聘]欢迎市场、营销人员加入看雪学院团队!

最后于  2018-3-8 13:27 被scxc编辑 ,原因:

上传的附件:

  •  unicorn.7z (4.42MB,305次下载)
  •  dumpso.7z (39.82kb,303次下载)
  •  loader.7z (2.03MB,280次下载)
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值