首页
社区
课程
招聘
[原创]某视频平台关键 so vm 解释器还原
发表于: 2024-7-29 14:48 28347

[原创]某视频平台关键 so vm 解释器还原

2024-7-29 14:48
28347

曾经的曾经,我们还是懵懂无知 de 看待这个世界,直到被世界鞭挞的体无完肤,才对世界有一点点理解。

将 metasec_xx.so 拖入 ida,ida 很自信的帮我们把 so 分析完毕,查看区段信息,ok,没有压缩或加密,可以直接静态分析了(ida server 总是断,用 unidbg 调试没有 ida 直观,lldb/gdb 又太古老了,不如使用 idapython 调 unicorn),找到入口,开始分析。

跟踪关键算法从 java 层调用到 native 层之后,最终会调用一个很大的函数

F5 状态下

可以确定该函数是 vm 的入口,并且有混淆,使 ida 反编译失败,看看汇编

ok,不是 ollvm 的混淆。

分析一下这段混淆的含义

可以看到在写地址前,和比较前的一些运算完全没用,关键的只是后面地址和判断条件,那么去混淆的时候只要保存这块就行。

先手动去混淆

再看看后面是不是都是这样的

可以确定,该类混淆是通过在跳转指令中将跳转地址转为计算值,比较后写入到寄存器中实现跳转,ida 默认没有自动给你分析这个结果,导致反编译失败(中间的多余计算也是一个干扰)。

根据正确的跳转方式,保留关键的寄存器,其他和跳转无关的全部 nop(全是无意义的指令(ps:其实有些也不是))

写去混淆脚本

去混淆后,ida 再次反编译查看

可以正常解析了,接下来我们要做的就简单了只需要对这个函数进行逐 byte 解析并记录其过程即可。

前面说过,该函数是一个 VM 解释的函数,VM 里执行的二进制代码我把他统称为 vm_opcode,这个 vm 里的 vm_opcode 大概长这样

前面去混淆的代码就是在对这段数据进行一个翻译执行。动静态执行分析一下,解释流程大概是这样的

关于如何分析 vm 和 vm 的结构可以看看看雪的月板第一的文章 https://bbs.kanxue.com/thread-282300.htm

在上面的文章中,大佬已将伪代码一一列出,最后的做法是在这个基础上纯看代码静态分析,在他的基础上做了点改进

将伪代码用函数表示,并用真实的堆栈和真实的c语句将其打印,对于跳转的指令在前面加上c的跳转指示表,然后通过gcc编译,将编译的结果放入到ida中进行反编译,这样做的目的是在编译时会对编译语句进行编译优化,去除一下冗余和重复计算,这样之后再用ida的反编译优化,再进行了一次的优化,使得其读起来没那么复杂

大概样子

这样之后就比纯看来的好多了

暂时没做,只做了 vm 解释器的还原,后面看看有没有需求,然后做做看。

MOV             W9, #9        ; 全新的变量赋值
STR             X9, [SP,#0xA0+var_58] ; 将这个写入到堆栈中
LDR             X9, [SP,#0xA0+var_58] ; 又取出
MOV             W15, #0x11    ; 全新的变量赋值
STR             X15, [SP,#0xA0+var_58] ; 写入又取出
LDR             X15, [SP,#0xA0+var_58]
ADD             X0, X9, #1    ; x0=x9+1=10
ADD             X3, X9, #2    ; x3=x9+2=11
MUL             X9, X0, X9    ; x9=x0*x9=90
MOV             X7, #0xAAAAAAAAAAAAAAAA ; 全新的变量赋值
ADD             X0, X15, #1   ; x0=x15+1=0x12
MUL             X9, X9, X3    ; x9=x3*x9=11*90=(x9*(x9+1)*(x9+2))=0x3de
MOVK            X7, #0xAAAB   ; x7=0xAAAAAAAAAAAAAAAB
ADD             X3, X15, #2   ; x3=x15+2=0x13
MUL             X15, X0, X15  ; x15=x15*x0=0x11*0x12
UMULH           X0, X9, X7    ; x0=(x9*x7)>>48=0x294
MUL             X15, X15, X3  ; x15=x15*x3=0x11*0x13=(x15*(x15+1)*(x15+2))
LSR             X3, X0, #1    ; x3=x0/2
LSL             X3, X3, #1    ; x3=x3*2=x0
ADD             X0, X3, X0,LSR#1 ; X0 = X3 + (X0 >> 1)=x0+x0/2=1.5*x0=0x3de(应该是用了数学技巧,矩阵转换?)
UMULH           X3, X15, X7
SUB             X9, X9, X0    ; x9=x9-x0=0
LSR             X0, X3, #1
LSL             X0, X0, #1    ; x0=x3
ADD             X0, X0, X3,LSR#1 ; X0 = X0 + (X3 >> 1)=x15
ADRP            X3, #loc_4A85C@PAGE ; 地址0
SUB             X15, X15, X0  ; x15=x15-x0=0
ADRP            X0, #loc_4ACF8@PAGE ; 地址1
ADD             X3, X3, #loc_4A85C@PAGEOFF
ADD             X0, X0, #loc_4ACF8@PAGEOFF
ADD             X9, X9, X3    ; x9=地址0
ADD             X15, X15, X0  ; x15=地址1
CMP             W14, #3       ; 获取bytecode的控制码
CSEL            X9, X9, X15, CC ; 根据上面的比较判断是去地址0,还是地址1
BR              X9
MOV             W9, #9        ; 全新的变量赋值
STR             X9, [SP,#0xA0+var_58] ; 将这个写入到堆栈中
LDR             X9, [SP,#0xA0+var_58] ; 又取出
MOV             W15, #0x11    ; 全新的变量赋值
STR             X15, [SP,#0xA0+var_58] ; 写入又取出
LDR             X15, [SP,#0xA0+var_58]
ADD             X0, X9, #1    ; x0=x9+1=10
ADD             X3, X9, #2    ; x3=x9+2=11
MUL             X9, X0, X9    ; x9=x0*x9=90
MOV             X7, #0xAAAAAAAAAAAAAAAA ; 全新的变量赋值
ADD             X0, X15, #1   ; x0=x15+1=0x12
MUL             X9, X9, X3    ; x9=x3*x9=11*90=(x9*(x9+1)*(x9+2))=0x3de
MOVK            X7, #0xAAAB   ; x7=0xAAAAAAAAAAAAAAAB
ADD             X3, X15, #2   ; x3=x15+2=0x13
MUL             X15, X0, X15  ; x15=x15*x0=0x11*0x12
UMULH           X0, X9, X7    ; x0=(x9*x7)>>48=0x294
MUL             X15, X15, X3  ; x15=x15*x3=0x11*0x13=(x15*(x15+1)*(x15+2))
LSR             X3, X0, #1    ; x3=x0/2
LSL             X3, X3, #1    ; x3=x3*2=x0
ADD             X0, X3, X0,LSR#1 ; X0 = X3 + (X0 >> 1)=x0+x0/2=1.5*x0=0x3de(应该是用了数学技巧,矩阵转换?)
UMULH           X3, X15, X7
SUB             X9, X9, X0    ; x9=x9-x0=0
LSR             X0, X3, #1
LSL             X0, X0, #1    ; x0=x3
ADD             X0, X0, X3,LSR#1 ; X0 = X0 + (X3 >> 1)=x15
ADRP            X3, #loc_4A85C@PAGE ; 地址0
SUB             X15, X15, X0  ; x15=x15-x0=0
ADRP            X0, #loc_4ACF8@PAGE ; 地址1
ADD             X3, X3, #loc_4A85C@PAGEOFF
ADD             X0, X0, #loc_4ACF8@PAGEOFF
ADD             X9, X9, X3    ; x9=地址0
ADD             X15, X15, X0  ; x15=地址1
CMP             W14, #3       ; 获取bytecode的控制码
CSEL            X9, X9, X15, CC ; 根据上面的比较判断是去地址0,还是地址1
BR              X9
CMP             W14, #3
B.CC            loc_4A85C
B               loc_4ACF8
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
CMP             W14, #3
B.CC            loc_4A85C
B               loc_4ACF8
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
import idaapi
import idc
from unicorn import *
from unicorn.arm64_const import *
from keystone import *
 
BASE_reg=0x81
class txxxxk:
    def __init__(self,address,size) -> None:
        self.start_addre=address
        self.size=size
        self.mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
        self.mu.mem_map(address&0xfff000,0x20000000)
        data = idaapi.get_bytes(address,size)
        self.mu.mem_write(address,data)
        self.mu.reg_write(UC_ARM64_REG_SP,0x11000000)
        self.mu.hook_add(UC_HOOK_CODE, self.hook_code)
        self.cmp_reg_num=0
        self.no_nop=[]
        self.br_remake=[]
        self.br_reg=[]
        self.b_addr1=0
        self.b_addr2=0
        self.cmp_seg=""
        self.ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
         
    def hook_code(self,mu, address, size, user_data):
        print("%x"%address)
        insn=idaapi.insn_t()
        idaapi.decode_insn(insn,address)
        dism=idc.generate_disasm_line(address,0)
        if insn.itype==idaapi.ARM_cmp:
            self.cmp_reg_num=insn.Op1.reg-BASE_reg
            self.no_nop.append(address)
        if insn.itype == idaapi.ARM_csel:
            self.b_addr1=self.mu.reg_read(UC_ARM64_REG_X0+insn.Op2.reg-BASE_reg)
            print(insn.Op3.reg-BASE_reg)
            self.b_addr2=self.mu.reg_read(UC_ARM64_REG_X0+insn.Op3.reg-BASE_reg)
            self.br_reg.append(insn.Op2.reg-BASE_reg)
            self.br_reg.append(insn.Op3.reg-BASE_reg)
            print("跳转地址 %x"%self.b_addr1)
            print("跳转地址 %x"%self.b_addr2)
            self.br_remake.append(address)
            self.no_nop.append(address)
            self.cmp_seg=dism.split(",")[-1].split(" ")[-1]
        if insn.itype==idaapi.ARM_br:
            self.br_remake.append(address)
            self.no_nop.append(address)
 
    def start(self):
        try:
            self.mu.emu_start(self.start_addre,self.start_addre+self.size)
        except UcError as e:
            if e.errno==UC_ERR_EXCEPTION:
                print("go on")
            else:
                print(e)
                print("ESP = %x" %self.mu.reg_read(UC_ARM64_REG_SP))
                return
        self.check_reg()
        print("no_nop list ")
        print(self.no_nop)
        print("br_reg list ")
        print(self.br_reg)
        print("br list")
        print(self.br_remake)
        self.nop()
        self.change_ida_byte()
 
    def check_reg(self):
        i=self.size
        nop_list=[]
        while(i>=0):
            insn=idaapi.insn_t()
            idaapi.decode_insn(insn,self.start_addre+i)
 
            flag=False
            for op in insn.ops:
                if op.reg!=0 and (op.reg-BASE_reg) in self.br_reg:
                    flag=True
            for op in insn.ops:
                if flag:
                    if op.reg!=0 and (op.reg-BASE_reg) not in self.br_reg and op.reg!=0xa1:
                        self.br_reg.append(op.reg-BASE_reg)
                    print("%x 参与计算的其他寄存器 %d"%(self.start_addre+i,op.reg-BASE_reg))
                    nop_list.append(self.start_addre+i)
            i-=4
        for no_nop_i in self.no_nop:
            if no_nop_i in nop_list:
                nop_list.remove(no_nop_i)
        j=0
        while(j<self.size):
            if j+self.start_addre not in nop_list:
                self.no_nop.append(self.start_addre+j)
            j+=4
 
     
    def nop(self):
        i=0
        while(i<self.size):
            if i+self.start_addre in self.no_nop:
                i+=4
                continue
            idaapi.patch_dword(i+self.start_addre,0xD503201F)
            i+=4
 
    def change_ida_byte(self):
        code="B"+self.cmp_seg+" "+hex(self.b_addr1)
        print(code,self.br_remake[0])
        encoding, count = self.ks.asm(code,self.br_remake[0])
        i=0
        print(code)
        for cc in encoding:
            idaapi.patch_byte(self.br_remake[0]+i,cc)
            i+=1
        code="B"+" "+hex(self.b_addr2)
        encoding, count = self.ks.asm(code,self.br_remake[1])
        print(code)
        i=0
        for cc in encoding:
            idaapi.patch_byte(self.br_remake[1]+i,cc)
            i+=1
import idaapi
import idc
from unicorn import *
from unicorn.arm64_const import *
from keystone import *
 
BASE_reg=0x81
class txxxxk:
    def __init__(self,address,size) -> None:
        self.start_addre=address
        self.size=size
        self.mu = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
        self.mu.mem_map(address&0xfff000,0x20000000)
        data = idaapi.get_bytes(address,size)
        self.mu.mem_write(address,data)
        self.mu.reg_write(UC_ARM64_REG_SP,0x11000000)
        self.mu.hook_add(UC_HOOK_CODE, self.hook_code)
        self.cmp_reg_num=0
        self.no_nop=[]
        self.br_remake=[]
        self.br_reg=[]
        self.b_addr1=0
        self.b_addr2=0
        self.cmp_seg=""
        self.ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
         
    def hook_code(self,mu, address, size, user_data):
        print("%x"%address)
        insn=idaapi.insn_t()
        idaapi.decode_insn(insn,address)
        dism=idc.generate_disasm_line(address,0)
        if insn.itype==idaapi.ARM_cmp:
            self.cmp_reg_num=insn.Op1.reg-BASE_reg
            self.no_nop.append(address)
        if insn.itype == idaapi.ARM_csel:
            self.b_addr1=self.mu.reg_read(UC_ARM64_REG_X0+insn.Op2.reg-BASE_reg)
            print(insn.Op3.reg-BASE_reg)
            self.b_addr2=self.mu.reg_read(UC_ARM64_REG_X0+insn.Op3.reg-BASE_reg)
            self.br_reg.append(insn.Op2.reg-BASE_reg)
            self.br_reg.append(insn.Op3.reg-BASE_reg)
            print("跳转地址 %x"%self.b_addr1)

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-7-29 14:49 被zhnnmsl编辑 ,原因:
收藏
免费 7
支持
分享
打赏 + 10.00雪花
打赏次数 1 雪花 + 10.00
 
赞赏  mb_vjpiriil   +10.00 2024/07/31 感谢分享~
最新回复 (9)
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
有个疑问,遇到几个vm.。样本,伪代码 和正常代码 看不出来啥区别
大佬是通过啥特征确定 是否是vm的?
2024-7-29 21:22
0
雪    币: 1378
活跃值: (2505)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2024-7-30 18:32
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
大神
2024-8-1 12:57
0
雪    币: 664
活跃值: (1983)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
佬 有样本或版本号吗
2024-8-8 11:16
0
雪    币: 318
活跃值: (633)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
wx_墨_198 佬 有样本或版本号吗
34.4.4 
2024-8-9 09:40
0
雪    币: 318
活跃值: (633)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
mb_ldbucrik 有个疑问,遇到几个vm.。样本,伪代码 和正常代码 看不出来啥区别 大佬是通过啥特征确定 是否是vm的?
问题太宽泛了,你要不先去看看什么是vm
2024-8-9 10:13
0
雪    币: 0
活跃值: (532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
如果有handle 代码,可以在handle代码上面加打印,把opcode跑一遍,流程就出来了。
2024-8-19 11:00
0
雪    币: 527
活跃值: (1462)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
mark!
2024-10-18 19:58
0
雪    币: 1935
活跃值: (2518)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
mark~
2024-10-22 08:51
0
游客
登录 | 注册 方可回帖
返回
//