混淆还原的几种方式实践
2024-12-6 14:55:36 Author: www.freebuf.com(查看原文) 阅读量:2 收藏

freeBuf

主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

下面都是使用goron的混淆进行符号执行以及模拟执行处理的结果

还原前
image

还原后
image

符号执行和ollvm还原思路相同:
找序言块、真实块、ret块、分发器;在deflate,不同的是需要续住寄存器的值来精准找到下一真实块
image

发现赋值在序言块
image

思路
在执行真实块之前对x29偏移出进行初始化赋值或者直接将序言块寄存器状态续到真实块对代码进行修改,结合https://github.com/cq674350529/deflat的解混淆修改即可简单实现

分析间接跳转如下,通过手动计算跳转地址(这里是模拟执行获取跳转地址)再根据条件判断将br指令进行替换即可手动还原
image
替换指令为br指令以及前一条指令,根据条件指令替换为ture和false的分支跳转
image

先处理间接跳转,通过汇编代码特征找到判断分支csel的两个寄存器值并获取条件指令,条件true和false的值,通过条件获取ldr的两个值,add固定值,然后替换br和上一条指令:b+条件指令true的地址,bfalse地址,找找前人造的轮子https://bbs.kanxue.com/thread-277086.htm进行修改即可

while (!finish && !instructions.empty())
                {
                    instructions.pop();
                    ins = instructions.peek().getIns();
                    if(ins.getMnemonic().toLowerCase(Locale.ROOT).equals("add"))
                    {
                        String[] split = ins.getOpStr().split(",");
                        if(split.length == 4)
                        {
                            //split[0].toLowerCase(Locale.ROOT).trim().equals("x12") &&
                            if(split[3].toLowerCase(Locale.ROOT).trim().equals("sxtw"))
                            {
                                String reg = split[2].trim().toLowerCase(Locale.ROOT);
                                base = getRegValue(reg,instructions.peek().getRegs()).longValue();
                                addinstaddr = instructions.peek().getAddr() - module.base;
                            }
//                            else {
//                                break;
//                            }
                        }
//                        else
//                        {
//                            break;
//                        }
                    }

                    if(ins.getMnemonic().toLowerCase(Locale.ROOT).equals("ldr"))
                    {
                        String[] sp = ins.getOpStr().toLowerCase().split(",");
                        if(sp.length == 4)
                        {
                            //sp[0].trim().toLowerCase(Locale.ROOT).equals("x12") &&
                            if(sp[3].trim().toLowerCase(Locale.ROOT).equals("uxtw #3]"))
                            {
                                String reg = sp[1].toLowerCase(Locale.ROOT).trim().substring(1);
                                listoffset = getRegValue(reg,instructions.peek().getRegs()).longValue()-module.base;
                                ldaaddr =  instructions.peek().getAddr()- module.base;
                            }
                        }
                    }

                    if(ins.getMnemonic().trim().toLowerCase(Locale.ROOT).equals("csel") || ins.getMnemonic().trim().toLowerCase(Locale.ROOT).equals("csinc"))
                    {
                        String[] sp = ins.getOpStr().toLowerCase(Locale.ROOT).split(",");
                        if(sp.length == 4)
                        {
                            cond = sp[3].trim();
                            if(sp[0].trim().equals("w10")&& !sp[2].trim().equals("wzr"))
                            {
                                String reg1 = sp[1].trim();
                                String reg2 = sp[2].trim();
                                cond1 = getRegValue(reg1,instructions.peek().getRegs()).longValue();
                                cond2 = getRegValue(reg2,instructions.peek().getRegs()).longValue();
                                selectaddr = instructions.peek().getAddr() - module.base;
                            }
                            if(sp[0].trim().equals("w10")&& sp[2].trim().equals("wzr"))
                            {
                                String reg1 = sp[1].trim();

                                cond1 = getRegValue(reg1,instructions.peek().getRegs()).longValue();
                                cond2 = 1;
                                selectaddr = instructions.peek().getAddr() - module.base;
                            }
                        }
                    }



                    if(ins.getMnemonic().trim().toLowerCase(Locale.ROOT).equals("subs") && ins.getOpStr().trim().toLowerCase(Locale.ROOT).equals("w8, w9, w8"))
                    {
                        if(base == -1 || listoffset == -1 || cond1 == -1 || cond2 == -1 || cond.equals("") || addinstaddr == -1 || ldaaddr == -1 || selectaddr == -1)
                        {
                            break;
                        }
                        else
                        {
                            long offset1 = base + readInt64(emulator.getBackend(), module.base+listoffset+cond1*8) - module.base;
                            long offset2 = base + readInt64(emulator.getBackend(),module.base+listoffset+cond2*8) - module.base;
                            if( brinsaddr - addinstaddr != 4)
                            {
                                System.out.println("add ins and br ins gap more than 4 size,may make mistake");
                            }
                            String condBr = "b"+cond.toLowerCase(Locale.ROOT) + " 0x"+ Integer.toHexString((int) (offset1 - addinstaddr));
                            String br = "b 0x" + Integer.toHexString((int)(offset2 - brinsaddr));

还原前不能f5:
image
还原后还存在平坦化:
image
获取执行流
image
再根据执行流patch之后即可还原
image

符号执行在处理类似ollvm每个块都已经初始化好的比较好处理,模拟执行处理复杂运算的跳转好用,发现都得结合手动还原,工具只是代替手动部分的批量实现,所以本质还是手动还原的结果;或许ai训练总结算式自动编写d810的配置可能效果更要好一些

参考:
https://github.com/cq674350529/deflat
https://bbs.kanxue.com/thread-277086.htm
https://github.com/amimo/goron

已在FreeBuf发表 0 篇文章

本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022


文章来源: https://www.freebuf.com/articles/mobile/417063.html
如有侵权请联系:admin#unsafe.sh