首页
社区
课程
招聘
[原创] 自写简易Arm64模拟执行去除控制流平坦化
发表于: 2024-12-19 17:50 39062

[原创] 自写简易Arm64模拟执行去除控制流平坦化

2024-12-19 17:50
39062

为什么要自写模拟执行?

现在比较流行的反控制流平坦化 一般由Unidbg、 frida Stalker、这种runtime下线性的记录块的信息来完成patch, 此种方式有优势,也有缺陷。比如对else分支无法记录,如果手动干预else分支 那么就需要在上下文下补环境,简易的分支下还算较为简单,但是复杂的情况下,实现难度非常大(其实是地狱式的级别)。于是在猜想能不能实现一个基于特征的模拟执行,由于之前写过类似IDA的F5汇编解析,也手写过伪模拟执行的仿真器,所以写一个基于ollvm特征的模拟执行难度也不大,于是就开始了开发之路。。

先附上源码链接:AntiOllvm

功能

这是一个基于函数级别的模拟执行,并不分析整个So,也不需要补环境,基于IDA的CFG信息生成并且自动patch所有的真实块。但是需要对主分发块、次分发块进行标识(仅此而已,你并不需要对汇编、CFG构建有所了解 只需要标识这2个即可还原Fla)。Demo里包含比较通用的特征查找,具体可查看源码。如何食用请跳转github 。PS: 如果是魔改的Ollvm 请自行理解框架,并结合实际情况进行代码调整。目前仅兼容标准的ollvm特征。 更多兼容正在调整中.

原理介绍

现在先回顾一下Fla的特征。 先来一段伪代码

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
Label1:
      int a = 100;
      switch (a)
      {
          case 98:
          {
              //Logic
              a = 101;
              goto Label1;
          }
          case 99:
          {
              //Logic
              a = 98;
              goto Label1;
          }
          case 100:
          {
              //Logic
              a = 99;
              goto Label1;
          }
          default:
              break;
      }

以上其实就是比较简易的一个分发块伪代码; 一个简单的if逻辑编译成So 经过Fla会变成这样的CFG图

F5的结果是这样的

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
__int64 __fastcall sub_181E0C(__int64 a1)
{
  int v2; // w8
  __int64 v3; // x0
  __int64 v4; // x1
  __int64 v5; // x2
  __int128 v7; // [xsp+0h] [xbp-70h] BYREF
  char v8; // [xsp+10h] [xbp-60h]
  __int64 v9; // [xsp+28h] [xbp-48h]
 
  v2 = 1999739781;
  v9 = qword_7289F8;
  while ( 1 )
  {
    while ( v2 == 1087191716 )
    {
      v8 = -12;
      v7 = xmmword_587FDD;
      qword_7289F8 = (__int64)sub_1815C0(&v7, 17);
      v2 = -1880484146;
    }
    if ( v2 != 1999739781 )
      break;
    if ( v9 )
      v2 = -1880484146;
    else
      v2 = 1087191716;
  }
  sub_165910(*(_QWORD *)(a1 + 8), aVIsvSvS, *(_QWORD *)(a1 + 32));
  v3 = sub_15D4E4(*(_QWORD *)(a1 + 8), 3LL);
  return sub_181EE0(v3, v4, v5);

结合一下伪代码可以看出分支的控制由CSEL指令来决定的 即为

1
CSEL            W8, W24, W21, EQ

那么此项值在哪初始化呢? 在主分发器开始进入之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MOV             W21, #0x16CE
MOV             W23, #0x9B85
MOV             W24, #0x3AA4
ADRP            X26, #xmmword_587FDD@PAGE
MOV             W8, #0x9B85
MOV             X20, X1
MOV             X19, X0
MOVK            W21, #0x8FEA,LSL#16
MOVK            W23, #0x7731,LSL#16
MOVK            W24, #0x40CD,LSL#16
MOV             W25, #0xF4
ADD             X26, X26, #xmmword_587FDD@PAGEOFF
MOVK            W8, #0x7731,LSL#16
STR             X4, [SP,#0x70+var_48]

[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2024-12-19 20:12 被IIImmmyyy编辑 ,原因:
收藏
免费 12
支持
分享
最新回复 (41)
雪    币: 2691
活跃值: (3767)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-12-19 17:56
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享,向大佬学习
2024-12-19 18:14
0
雪    币: 2592
活跃值: (7113)
能力值: ( LV7,RANK:102 )
在线值:
发帖
回帖
粉丝
4
这种方式效果有限,遇到复杂的就处理不鸟了
2024-12-19 18:40
0
雪    币: 206
活跃值: (720)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
fjqisba 这种方式效果有限,遇到复杂的就处理不鸟了
恰恰相反,我写这个的目的就是处理复杂情况,无论是何种情况下 块和块的链接只有2个结果 真实块或者分发块。满足条件即可遍历出结果
2024-12-19 18:45
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
fjqisba 这种方式效果有限,遇到复杂的就处理不鸟了
大佬为啥arm的vmp没有ida插件呢,可否引用你的vm3.5插件呢
2024-12-19 19:12
0
雪    币: 5416
活跃值: (4252)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
7
谢谢分享
2024-12-20 08:54
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
IIImmmyyy 恰恰相反,我写这个的目的就是处理复杂情况,无论是何种情况下 块和块的链接只有2个结果 真实块或者分发块。满足条件即可遍历出结果
请问大佬,arm32 thumb适用吗
2024-12-20 13:44
0
雪    币: 16678
活跃值: (6961)
能力值: ( LV13,RANK:923 )
在线值:
发帖
回帖
粉丝
9
IIImmmyyy 恰恰相反,我写这个的目的就是处理复杂情况,无论是何种情况下 块和块的链接只有2个结果 真实块或者分发块。满足条件即可遍历出结果
可曾遇到共用块
2024-12-20 14:06
1
雪    币: 206
活跃值: (720)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
mb_ldbucrik 请问大佬,arm32 thumb适用吗
不适用 暂未有arm32 和x86的支持计划。 如果要做多架构的话 需要抽出一个中间语言作为桥接。 我闲麻烦没有做了
2024-12-20 14:25
0
雪    币: 206
活跃值: (720)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
11
大帅锅 可曾遇到共用块

for循环就是标准的共用块情况。 查找过程中会对正在查找的block进行标记,如果递归发现是已经正在查找的则返回,同时链接已经存在的block。此处理就可完成for循环的处理

```

if (block.isFind)
{
   Logger.WarnNewline("block is Finding  " + block.start_address);
   return block.RealChilds;
}

```

最后于 2024-12-20 14:28 被IIImmmyyy编辑 ,原因:
2024-12-20 14:27
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
IIImmmyyy 不适用 暂未有arm32 和x86的支持计划。 如果要做多架构的话 需要抽出一个中间语言作为桥接。 我闲麻烦没有做了
好的,retdec优化貌似支持所有的架构
2024-12-20 14:51
0
雪    币: 206
活跃值: (720)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
mb_ldbucrik 好的,retdec优化貌似支持所有的架构
其实这个也支持,也不难。 你可以仿造arm64的写法进行逻辑的剥离。 代码量很少 并不多的。不想支持的原因主要还是目前工作中需要单独去分析arm32 的情况实在是太少了。 arm64已经是主流了
2024-12-20 14:59
0
雪    币: 944
活跃值: (4605)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
14
可以滴
2024-12-20 15:38
0
雪    币: 1168
活跃值: (837)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
先拿 tersafe 练练手
2024-12-20 15:42
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
读取 else 分支的时候,仅读取 else 分支下一个 block,还是会继续读取完整个 else 分支继续下去的情况呢?粗看你的实现是会继续读取的,能把所有可能的节点都走过去,和我之前的处理反混淆的时候差不多,我的之前也基本是可用状态,当时碰到过一些问题,目前也没想到好办法,不知道你有没有碰到

1. 因为会按照非常规进入的某段逻辑,所以有时可能会出现错误,导致未遍历完整
2. 要遍历很多,所以速度比较慢

另外一个想讨论的是公共块的问题(上面也有人提出来,不知道和我说的是不是一样),我碰到的公共块问题是,复杂情况下,真实区域中也是好几个块组成的,所以我叫真实区域,没说真实块,然后真实区域之间还会存在块是共用的,即公共块,部分公共块还是真实区域中的出口位置,可能这个块出去有四五个后继区域,这种情况下,我是将 patch 指令复制到了原本的分发器所在位置上,好像并没有看到你有这样分配的情况,不知道你是怎么处理的
2024-12-20 17:13
1
雪    币: 206
活跃值: (720)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
17

首先关于

1. 因为会按照非常规进入的某段逻辑,所以有时可能会出现错误,导致未遍历完整
2. 要遍历很多,所以速度比较慢 

这个我并没有办法回答你,可能是因为样本的原因。我目前没有碰到。

我的设计理念是块只有2种情况,真实块或者分发块。 真实块无论是以什么样的形式存在 只要不是分发器它都是真实块。

当然这种就需要特殊处理真实块,比如情况 

```loc_17F750

STR             XZR, [SP,#0xF0+var_C8]

```

这个也是公共块 它的后继块 要么是分发器 要么是真实块。甚至他的后继块可能是它自己。 但是无论如何是什么块,都会进入循环进行标记。我的设计理念就是找到所有的分支。排除分发器。

另外一个问题关于patch指令的 不知道我理解的对不对 我附上一个情况

```

//loc_15E604
// LDR             X9, [SP,#0x2D0+var_2B0]
// ADRP            X8, #qword_7289B8@PAGE
// LDR             X8, [X8,#qword_7289B8@PAGEOFF]
// STR             X9, [SP,#0x2D0+var_260]
// LDR             X9, [SP,#0x2D0+var_2A8]
// STR             X8, [SP,#0x2D0+var_238]
// MOV             W8, #0x561D9EF8
// STP             X19, X9, [SP,#0x2D0+var_270]

// loc_15E628
// CMP             W8, W23
// B.GT            loc_15E6C0

```

loc_15e628 是在下一个连接块中。 此时对15e604进行patch修复的 我选择删除掉MOV W8  这个分发指令 然后调整STP X19 X9 的位置 让跳转指令下沉。这样分发块就可以正常的NOP掉。 如果没有MOV 指令, 其实这个块就不是分发块 也不需要进行NOP 修复 因为下个连接块大概率是一个中转指令 就一个 B locXXXX 这个可能连接到分发块 也有可能是真实块。 但是不重要 因为块与块之间是单独修复的 只要管好自己就行。

Patch 的原则就是不要动到真实块的位置。可以删减 但是不能对原块的指令进行扩充。 因为扩充在复杂的情况下我觉的会有分支错误的问题


最后于 2024-12-20 18:37 被IIImmmyyy编辑 ,原因:
2024-12-20 18:04
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
IIImmmyyy 其实这个也支持,也不难。 你可以仿造arm64的写法进行逻辑的剥离。 代码量很少 并不多的。不想支持的原因主要还是目前工作中需要单独去分析arm32 的情况实在是太少了。 arm64已经是主流了
好的,试着搞搞
2024-12-21 11:53
0
雪    币: 1643
活跃值: (3296)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
打开安卓app一看,主流是arm64
2024-12-22 12:12
0
雪    币: 366
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
很好的帖子 如果所依赖的寄存器值是动态获取的 ida的静态分析就没用了 就需要动态修复了
2024-12-22 15:37
0
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21

感谢开源,尝试了一下发现报错,缺少MUL | CSET等指令识别。FormatOpCode这个函数(InstructionsExtension.cs .line 119)需要补齐这几个吗

最后于 2024-12-23 11:14 被mb_hsxnkqcc编辑 ,原因:
2024-12-23 11:10
0
雪    币: 206
活跃值: (720)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
22
mb_hsxnkqcc 感谢开源,尝试了一下发现报错,缺少MUL | CSET等指令识别。FormatOpCode这个函数(InstructionsExtension.cs .line 1 ...

这个是在修复过程中对指令的兼容度不够大。 在runtime的过程中 对其他指令并不敏感 补上对应的OpCode即可。 主要是需要识别CSEL, MOV,MOVK,CMP指令作为runtime的特征运行

最后于 2024-12-23 11:31 被IIImmmyyy编辑 ,原因:
2024-12-23 11:18
0
雪    币: 16678
活跃值: (6961)
能力值: ( LV13,RANK:923 )
在线值:
发帖
回帖
粉丝
23
mb_ldbucrik 好的,试着搞搞





稍微大点的函数都会遇到这个问题,这是因为,编译器优化导致的,很多真实块会跳转到同一个地方,然后再跳转到分发器

2024-12-23 11:59
0
雪    币: 16678
活跃值: (6961)
能力值: ( LV13,RANK:923 )
在线值:
发帖
回帖
粉丝
24
IIImmmyyy 首先关于1. 因为会按照非常规进入的某段逻辑,所以有时可能会出现错误,导致未遍历完整2. 要遍历很多,所以速度比较慢 这个我并没有办法回答你,可能 ...
这个东西可不好patch
2024-12-23 12:01
0
雪    币: 206
活跃值: (720)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
25
大帅锅 稍微大点的函数都会遇到这个问题,这是因为,编译器优化导致的,很多真实块会跳转到同一个地方,然后再跳转到分发器
这应该是一个函数内包含多个不同的操作分发器引起的 比如CMP W10,W28,这个是主分发器, 然后分支下出现了CMP W8,W30 这种情况导致次的主分发器被认定成了真实块。 不知是否能附上样本?
2024-12-23 12:02
0
游客
登录 | 注册 方可回帖
返回