现在的VMP的比较常见了,应该也是稳定性满足要求了,今天来分析一波,如有不当还请各位大佬指正

实际上 libdexjni.so在不同的APP中体积会不一样,应该是硬编码写入字符串和指令导致的

1-VMP还是先看下opcode部分知识,DEX指令格式

代码转换成DEX指令先看代码

对应的第一条指令是

每条指令是2字节,所以先看第一条 6f 20,根据官方文档  6F的解释是 invoke-super    格式为35c

A | G | op BBBB F|E|D|C

根据opcode克制总共6个字节,对应的就是

A=1  G=0 op=6f  BBBB就是 331c,然后是C=1 D=0 E=0 F=1

所以这里转换过来就是

invoke-super {p0}, Landroidx/fragment/app/FragmentActivity;->getResources()Landroid/content/res/Resources;

2-反调试

通过常规手段,在关键的open函数观察,然后逆向查找

发现几处反调试

0x47CAC 处是创建线程,检测运行时间,getpid 然后  linux_eabi_syscall(__NR_kill, a1, a2)来杀死进程

0x047C70  处是cmdline反调试,https://bbs.pediy.com/thread-223460.htm  这位大佬提到过

0x489EC    处是 /proc/status检测反调试

实际可能还有,但是在找到这三处之后,我发现特殊的地方是刚好在JNI_OnLoad处有个总的入口,所以直接

nop指令反调试就gg了

我用 arm64调试的    mov w1,w1 对应的的hex是E103012A

然后dump出dex,先内存找到dex.035

import struct

start = 0x75172191ec

dump_so = "/Users/beita/tmp/bangbang/dump_vmp.dex"

length = 0x6ee27c

file = open(dump_so,'w')

file.close()

fn = AskStr(dump_so ,"save as:")

with open(fn,"wb+") as f:

for addr in range(start , start+length):

f.write(struct.pack("B" , Byte(addr)))

print "success to save as "

3-VMP的具体分析

得到dex之后,转成jar,看了下,大部分函数是 JniLib.cV等来做的,但是有一个Integer.valueof,是一个函数索引,用来查找指令的

附加调试发现实际在这里解开这个java数组也就是 new Object的这个数组

这里用onCreate来分析  索引是18=0x12

JniLib.cV(new Object[] { this, paramBundle, Integer.valueOf(18) });

调试往下走,根据这个索引,会取出一个结构体信息,结合上下文信息

这里取出  0x7517a96b50的值 是  0x12

strut JavaInfo {

uint32_t index;     // 0x12   这是java层传递的

uint32_t unknow2;   // 0x2e   未知

uint64_t dexcode;   // dexcode指针

uint32_t unknow4;   // 0x03

uint32_t unknow5;   // 0x02

uint32_t unknow6;   // 0x02  这里看起来没有用到  但是貌似是DexCode的内容

};

跳转到dexcode的位置看下内容

struct DexCode {

u2  registersSize;   // 3

u2  insSize;           、、 2

u2  outsSize;

u2  triesSize;

u4  debugInfoOff;       /* file offset to debug info stream */

u4  insnsSize;          /* size of the insns array, in u2 units */

u2  insns[1];

}

registerSize = 3

insSize = 2

outsSize = 0

.....

主要看

insnsSize = 0xf

共15条指令   ,但是这个指令不是 标准的dex指令 opcode被改过,且字符串信息也是被改过,就是是说他不是系统来解析的,而且会有一个对应关系

A3 20 5C 00 21 00 6B 10  CC 20 13 02 01 00 55 11

6D 00 53 10 6D 00 72 10  60 01 00 00 69 00

进入到vm_parse函数之前的代码还能F5看下逻辑,但是到  vm_parse地址是29b70位置处,F5不好用了,貌似是刻意把这个函数写的非常大,

有点像dalvik里边的HANDLE那种搞到一起, 这样在加固过程中OLLVM混淆之后,更加复杂

在解析opcode之前会进行数据保存

信息看起来是保存到一组结构中

struct Infos1{

uint64_t data1;

uint64_t *data2;  // data2 = malloc(32)   是根据JavaInfo的dexCode来的

uint64_t data3;

uint64_t data4;

uint64_t data5;

uint64_t data6;

uint64_t data7;

uint64_t data8;// JavaInfo的data3的值

};

调试继续往下走,来到 j___Sl_I5_lO000_0SSIO_I0_O__OI_5I___lSSl0_lO5_0I5I5S5_  这个函数,这个函数不能F5了,要根据汇编来分析具体的vm是如何

解析opcde来实现代码运行的

最终的 入口是  29b70这个函数

调用获取GetMethodID的过程是

vm_parse   29b70  -  29bb0 -  4ae80 - 4aeb4  -  4e78c - 3f92c 开始获取名称和GetMethodID

第一个参数 结合全局变量可以获得这些内容Class MethodSig MethodName

前面提到,vmp可能会借助jni来实现,所以现在GetMethodID下段点,查看数据,方法名称和签名

寄存器x2

寄存器x3

但是因为被ollvm混淆过,体积非常大,可能是fla和bcf都加上去了

这个函数IDA识别基本上是卡死状态,所以只能是找关键点切入

看一下OLLVM的图,被混的 单个switch有几千个,而且F5卡死了

所以用快速定位关键汇编位置

分析ollvm个人觉得一点技巧是找到关键的block,下好断点,走一遍,逆向查找,基本上如果不是很大的代码块都能梳理清楚逻辑

大致如下图

现在反方向去找到是从哪里获取到的字符串,这个字符串是如何从DexCode取出来的,那么这个vm解释执行的逻辑差不多就清楚了

倒推代码来了解逻辑

上面的onCreate是根据在函数j__$S$0l0$SOOII$0lIll$SI_O0$S0ll__Il_S5lIl5lOlI5SO0S5$ 这里,根据一个输入值返回的结构体来得到的

计算处一个全局变量的偏移值

return *(_QWORD *)(qword_7517D666A0 + 8LL * a1);

其实是个结构体

用IDA直接取字符串看一下

idc.GetString(idc.Qword(idc.Qword(idc.Qword(0x7517D666A0) + 0x5C * 1) + 8 *n))

n=0是类名   android/support/v4/app/FragmentActivity

n=1是方法的参数签名 (Landroid/os/Bundle;)V

n=2是方法名称 onCreate

看起来是个如下的结构结构

struct {

void *class_name;

void *method_sig;

void *method_name;

}

所以JNI调用的onCreate来自这个结构体,实际上如果做过java2c的一看就知道是调用super.onCreate在

然后再网上查看汇编,找到这个结构体是从哪里来的

函数入参存放在x1寄存器 就是w1,而且是在站栈上

LDR             W1, [SP,#0x15A0+var_7F4]

根据这局汇编反向推一下            LDR取值必然有一个STR赋值

STR             X1, [SP,#0x15A0+var_7F4]

借助IDAPython来查找一下,之所以不用快捷键x去直接找,是因为需要找到调用顺序,所以在2b970的位置开始用脚本

last_insns = ''

def fn_f8():

idaapi.step_over()

GetDebuggerEvent(WFNE_SUSP | WFNE_CONT, -1)

def fn_f9():

idaapi.continue_process()

GetDebuggerEvent(WFNE_SUSP | WFNE_CONT, -1)

last_ins = ''

def run_next():

fn_f8()

asm_str = idc.GetDisasm(idc.GetRegValue('pc'))

cur_match = re.match(r'STR\s+(\S+),\s\[SP,#0x15A0\+var_1460\]', asm_str,  re.M | re.I)

if cur_match  :

reg1 = cur_match.group(1)

value = hex(idc.Word(idc.Qword(reg1) + 2))

print('nop addr', hex(cur_addr), asm_str)

return

else:

last_ins = asm_str

run_next()

run_next()

最终找到这个上面输入的5C是从最开始的 结构体里面的DexCode取出来的

如下

A3 20 5C 00 21 00

然后利用这个思路,找到指向指令insns的指针,实际就是在29b70处判断,当前的STR放入的指针是否是前边解出来的insns地址

找到指令指针是在存放在栈上的一个地址     [SP,#0x15A0+var_1460]

看上图所示,从栈 SP,#0x15A0+var_1460的地址  07517AEA460 得到的正是insns的地址

A3 20 5C 00 21 00

---------------------------------------------------------------------------------------------------------------------------------------------------------

此时联系到onCreate的地方,用到了5c  而我们根据1460推导出了5c的来源

这里很清楚了,实际上指令的格式还是没变  则,解释执行OPCODE

还是6字节

A3 20 5C 00 21 00

A | G | op BBBB F|E|D|C

对应一下就是   A=2 G=0 op=A3     BBBB是0x5C    C=0  D=0  E=0  F=1

这里的第三组也就是 0021解密一下     0021 & 0xf  = 0001

0x5c在Android自带虚拟机里变解释执行为  MethodID,这里vmp使用的是自定义存放的一个结构体,估计是为了快速查找,因为按照逻辑,是要从DEX里边取查找,可能是为了提高效率,所以保存起来

并且我看到vmp虚拟化的java函数越多,libdexjni.so的体积越大

继续调试往下走,你会看到  CallNonVirtualMethod   正是 super.onCreate

很熟悉的格式

--------------------------------------------------------------------------------------------------------------------------------

a320 是invoke-super

005c是取MethodID

0021 解密0001 实际上是参数v0  但是我觉得这个解密多余的 因为取前边2位的

则这条指令是   invoke-super {p0, p1}, Landroidx/fragment/app/FragmentActivity;->onCreate(Landroid/os/Bundle;)V

--------------------------------------------------------------------------------------------------------------------------------

同样往下走   用脚本跑,到这里停下,刚好是前边的6个字节的指令执行完了的地方    下一组指令

这里取出来是

6B 10

调试发现实际就是  定义了一个数值

--------------------------------------------------------------------------------------------------------------------------------

6b  const

10 v0,0x1

结果就是   const v0,0x1

--------------------------------------------------------------------------------------------------------------------------------

next

--------------------------------------------------------------------------------------------------------------------------------

CC 20 13 02 01 00[A=2]op{vC, vD},kind@BBBB

CC 20  invoke-virtual

0213   取MethodiD  requestWindowFeature     (I)Z

0001  参数

invoke-virtual {p0, v0}, Lcom/abing/appvmp/BaseActivity;->requestWindowFeature(I)Z

--------------------------------------------------------------------------------------------------------------------------------

next

--------------------------------------------------------------------------------------------------------------------------------

5511   6d00[A=2]op{vC, vD},kind@BBBB

1155 invoke-virtual

006d  取MethodiD  requestWindowFeature     (I)Z

0001  参数

iput-object p0, p0,Lcom/wangzhong/fortune/ui/activity/BaseActivity;->a:Lcom/wangzhong/fortune/ui/activity/BaseActivity;;

--------------------------------------------------------------------------------------------------------------------------------

next  继续往下走,

在5feac处找到 这三句代码,运气不错,这里刻意F5,可以看到是 取出一个对象的值,根据分析得知是 BaseActivity的属性a

--------------------------------------------------------------------------------------------------------------------------------

53 10 6D 00         [A=2]op{vC, vD},kind@BBBB

1053 invoke-virtual

006d  取MethodiD  requestWindowFeature     (I)Z

0001  参数

iget-object p0, p0,Lcom/wangzhong/fortune/ui/activity/BaseActivity;->a:Lcom/wangzhong/fortune/ui/activity/BaseActivity;

--------------------------------------------------------------------------------------------------------------------------------

next  脚本执行结果如下

--------------------------------------------------------------------------------------------------------------------------------

72 10  60 01 00 00    [A=2]op{vC, vD},kind@BBBB

10 72  invoke-virtual

0160   取MethodiD  requestWindowFeature     (I)Z

0000  参数编号

invoke-static {v0}, Lcom/wangzhong/fortune/f/c;->a(Landroid/app/Activity;)V

--------------------------------------------------------------------------------------------------------------------------------

next

--------------------------------------------------------------------------------------------------------------------------------

69 00   这个指令比较简单就是

return-void

--------------------------------------------------------------------------------------------------------------------------------对应到dex指令  ,0x5c这些部分需要自己取dex里边查找MethodID和ClassName对应起来,就是算出MethodID的索引就行

这里的5c最终是要到dex里取查找的

把下面这部分指令的根据分析经过转换

A3 20 5C 00 01 00 6B 10  CC 20 13 02 01 00 55 11

6D 00 53 10 6D 00 72 10  60 01 00 00 69 00 00 00

用流程图来说明下

得到

修复前的指令  实际上 JNILib.cv这部分代码是填充的 只有一个索引有用,所以直接覆盖

修复后的指令

实际指令是 0xF所以其他的nop掉 最后给一个return void 就可以了

这里比较坑的一点是寄存器的数量一定要改,不然的话dex2jar转不了

修复前

修复后

总结 :

1-是用JNI来解释执行opcde的

2-op被替换了,但是 A G 那部分参数寄存器数字是不会变的,因为vmp也需要指定是几个参数,来使用

3-做过java2c的都比较熟悉,对dex的opcode比较熟悉的情况下,联系上下文很容易得到结果

4-这里的op可能被加密了,个人愚见人为这个Op加密不加密无所谓,因为最终实际上是个对应关系  0xff个opcode对应0xff个opcode

hookjni 可以看到很多输出信息 就是说vmp实际采用的还是 jni来实现

如果要全部都替换掉,需要挨个分析指令,做一个映射表岀来

--------------------------------------------------------------------------------------------------

目前来看还是java2c + arm指令虚拟化应该是比较保险的操作,因为自己写一个解释器,纯自己实现指令,肯定问题非常多,所以指令还是通过Jni来实现的,

但是效率貌似低了些,如果这种方式加上ARM指令虚拟化,分析起来可就难受很多了

------------------------------------------------------------------------------------

样本是以前的版本,目的是为了分析和学习,这里只提供so文件,交流经验,需要样本私聊我

最后于 2020-1-21 11:13

被贝a塔编辑

,原因:

上传的附件:

libdexjni.so

(627.17kb,107次下载)

python 工程结构加固_[原创]某企业级加固[四代壳]VMP解释执行+指令还原相关推荐

  1. python编程语言优缺点_原创001 第一次接触这个神奇而又无所不能的编程语言:Python...

    ***********************人生苦短,我用Python,不定期更新博客,小伙伴们记得关注******************** OK,说到Python这个语言,相信大家.对它还是有 ...

  2. python机器交易_[原创]Python 机器学习之 SVM 预测买卖 -- 基于 BotVS 量化平台

    Python 机器学习之 SVM 预测买卖 Python入门简单策略 sklearn 机器学习库的使用 回测系统自带的库有 numpy pandas TA-Lib scipy statsmodels  ...

  3. python rbf神经网络_原创,基于径向基函数(RBF)神经网络RBF网络的举例应用!

    function RBF_NN_Example() clc clear all %  创建训练样本 %  线性函数的训练 Mn_Train=100*[rand(1,5) rand(1,5)+0.5 r ...

  4. 如何运用python画名字_[原创]如何使用Python在好友画我上画一个标准的汉字震惊朋友圈...

    准备工作: 1.一部Android手机 文中测试机型为三星S8. 2.一个Python3.*环境. 3.安装好adb测试桥. 测试环境:macOS 10.13.2* Python3.6 import ...

  5. python idle退出_【ZZ】windows+python2.7在IDLE中执行sys.exit()出现的问题及解决方案

    <简明Python教程>中第13章讲述"异常"时,有这样的一个实例, import sys try: s = raw_input('Enter something -- ...

  6. python实现数据恢复_使用sklearn进行对数据标准化、归一化以及将数据还原的方法...

    在对模型训练时,为了让模型尽快收敛,一件常做的事情就是对数据进行预处理. 这里通过使用sklearn.preprocess模块进行处理. 一.标准化和归一化的区别 归一化其实就是标准化的一种方式,只不 ...

  7. python自动修图_有码变高清!AI修图PULSE一秒还原马赛克

    [实例简介]PULSE是一种新型超分辨率算法,它通过潜在空间探索对照片采样,可以将16x16像素的低分辨率(Low Resolution,简称LR)放大到1024x1024像素的高分辨率(High R ...

  8. 拟真机器人拯救者奖励_惠普战66四代 11代处理器与拯救者Y7000 2020款哪个更值得入手。专业机器人工程不怎么玩游戏?...

    回答问题前我建议你把自己需要用到的软件,需要实现的功能拿出纸和笔罗列出来,然后才能有针对性的去选择相应的笔记本电脑. 然后我先科普下笔记本的大致分类:1.轻薄本,2.游戏本,3.全能本 轻薄本,顾名思 ...

  9. python 工程结构加固_【安卓逆向】360加固-脱壳修复

    360加固-脱壳修复 最近花了一些时间学习逆向脱壳,这方面一直投入的时间比较少.样本经过某加固宝进行加固,这里简单记录一下脱壳过程和思路,感谢某数字公司对安全加固的无私贡献,让我有机会小小的提高一下这 ...

最新文章

  1. 回顾 | Apache Flink x TiDB Meetup · 北京站
  2. python使用线性回归实现房价预测
  3. Asp.net2.0:如何使用ObjectDataSource
  4. GitHub 被爆开始实名制,以便于执行美国贸易制裁;特斯拉推出超大储能产品Megapack;高通宣布与腾讯游戏达成战略合作……...
  5. 神奇的applycall
  6. windows server 2008r2 如何隐藏iis版本号_如何拥有自己炫酷的个人博客
  7. 循环队列和链队的表示和实现
  8. VS编译器各版本代号
  9. 8款逆天的在线实用工具
  10. 利用selenium webdriver下载不同类型的文件(pdf,txt等等)
  11. 如何处理pagefile.sys占用太多C盘空间
  12. 阿里云RDS-NAS-OSS
  13. 便签内容如何从旧手机转到新手机?
  14. 技术分享:2.0mm小间距多接枝刚挠结合板制作工艺研究
  15. 利用新浪API实现数据的抓取\微博数据爬取\微博爬虫
  16. 2018年计算机考研408操作系统真题(客观题)
  17. Keras.layers.BatchNormalization的批归一化方法
  18. 9-visual_feature_VINS-Mono
  19. FLUENT-UDF日记-14-DEFINE_HEAT_FLUX
  20. github搜索,不要包含关键字

热门文章

  1. ASP.NET工作笔记014---用VB.NET封装服务器端控件
  2. error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int错误的解决方法
  3. mxnet window10 cpu 模式的安装
  4. LLVM每日谈之一 LLVM是什么
  5. opc client for php,使用vb/vba作为OPC client
  6. android 循环引用,spring循环引用
  7. ftk学习记(进度条篇)
  8. 更改via浏览器字体_【安卓】我心中的最佳手机浏览器
  9. python ocr 文字识别软件,Python文字截图识别OCR工具实例解析
  10. java string范围_java,String