大土豆安全笔记 | 我的锅,内存爆炸了

各位同学,好久不见呀

其实这段时间不打算发文章的,只是最近需要迅速找一份移动安全相关的工作,所以在这里占用一点点公共资源

我之前在360主要是做移动应用安全审计和安全SDL流程建设相关工作,具体的技术栈和擅长的方向我在日常发的文章里也有体现,不要有太复杂的逆向需求,VMP,OLLVM这种我真的搞不动,Base哪里都可

如果各位老板那里有合适的坑,可以扫码加我微信

IMAGE

最重要的是一定要确定有坑位,如果面试过程中坑位没了可以直接告诉我,请不要拖着不说,我已经被同样的事情坑了两次了,谢谢您各位

最近脑子抽的厉害,更正上一篇文章里提到的字符串数组数组,应该是二维字符串数组

记忆化搜索好用是好用,但算法里一般是用于存储很少的数据,在这里经常是几百万个节点,一个节点存储大量的路径,多次测试都OOM了

[INFO] [Go Scanner] Start Graphing -> Stop Graphing ==> 27.002038481s
[INFO] [Go Scanner] Start Fix Edge
	[INFO] [Go Scanner] ...
[INFO] [Go Scanner] Fixing Edge Cost ==> 1m0.991422737s
[INFO] [Go Scanner] Total Graphing Cost ==> 1m27.99349561s
[INFO] [Go Scanner] Start Scanning
	[INFO] [Go Scanner] Start Searching Intent.get*() -> Stop Searching Intent.get*() ==> 145.620887ms
	[INFO] [Go Scanner] Start Searching Context.startActivity() -> Stop Searching Context.startActivity() ==> 105.976865ms
	[INFO] [Go Scanner] Start Searching Context.startActivityForResult() -> Stop Searching Context.startActivityForResult() ==> 108.373713ms
	[INFO] [Go Scanner] Start Searching Runtime.exec() -> Stop Searching Runtime.exec() ==> 116.477956ms
	[INFO] [Go Scanner] Start Searching DexFile.loadDex() -> Stop Searching DexFile.loadDex() 345.802432ms
	[INFO] [Go Scanner] Start Searching ZipEntry.getName() -> Stop Searching ZipEntry.getName() ==> 52.740204648s

OOM的原因我不能直接说就是记忆化搜索的问题,因为每次都是在搜索解压缩路径穿越的时候出现的内存爆炸,而这个漏洞模型的搜索我是使用的反向搜索,从关键节点开始搜,搜索所有调用它的路径,即使顶层节点不是我们可控的入口也会把它打印出来

在经过了一些测试之后,得到了结论

因为搜索最终还是要将可达路径加入到一个容器中进行存储,这个路径可能有特别多,所以我动态判断了这个容器长度,如果路径大于十万条,直接退出,如果路径大于一万条则进行提醒,这两个数字是我多次测试后定下来的

至于为什么路径会大于十万条,我也不知道,可能搜索构图的时候有点问题,我需要之后找一个路径为五万条左右的样例进行反向测试,测试方法就是遍历所有的路径,反向比对有向图,观察是否能全部对应上,如果对应不上,打印出来进行排错,同时每条路径进行散列算法计算,观察是否有重复路径的情况出现

这个工作接下来再去完成

分享下最近的一些学习资料

南京大学的软件分析,确实很抽象,理解起来相当费劲,公开课的Slides可以从这里下载

  • https://pascal-group.bitbucket.io/

Path Finder作为方法调用关系的产物,在近期的漏洞模型适配中也遇到了瓶颈,这也是我决定从控制流分析转到数据流分析的一个主要原因

要说这部分的学习资料网络上搜一下都有,先学习Soot,比如最基础的Jimple三地址码IR,包括用Soot生成CFG,如何进行简单的漏洞扫描,Soot的接口看的差不多就开始看FlowDroid的一些实现思路,之前一直对这玩意绕道走,现在绕不开了

2021浙江大学研究生暑期学校(系统安全)

  • https://icsr.zju.edu.cn/news/325.html

其中有几个议题是我非常感兴趣的

俄亥俄州立大学Zhiqiang Lin教授的《Unveiling Insecure and Privacy-Risky Practice in Mobile Apps with Automated Program Analysis》

  • https://icsr.zju.edu.cn/uploads/new/3-1.pdf

新加坡国立大学Zhenkai Liang副教授的《Multi-level Observation and Understanding of Program Behaviors》

  • https://icsr.zju.edu.cn/uploads/new/3-2.pdf

盘古联合创始人王铁磊的《The power of variant analysis in software vulnerability discovery》

  • https://icsr.zju.edu.cn/uploads/new/3-3.pdf

悉尼科技大学Yulei Sui老师的《Software Security Analysis from Automation to Intelligence》

  • https://icsr.zju.edu.cn/uploads/new/3-5.pdf

清华大学白家驹助理研究员的《操作系统的静态分析与缺陷检测》

  • https://icsr.zju.edu.cn/uploads/new/4-4.pdf

华为网络安全首席架构师付天福的《计算机安全架构演进与HarmonyOS安全设计实践》,对应的Slides是《HarmonyOS 2安全技术白皮书》

  • https://icsr.zju.edu.cn/uploads/new/5-2.pdf

华为奇点实验室主任陈良的《移动安全攻防的趋势分析及鸿蒙时代面临的挑战》,最关心的一个议题竟然没有Slides下载

就在我准备厚着脸皮准备找大佬要一份Slides的时候,突然发现有位好心人录了会议视频放B站了,真是一个神奇的网站,各位同学可以搜索直接搜索议题学习

下面这个纯属猎奇,虽然我本身是硬件专业,但是毕业后一直从事偏软件的安全行业,对于这类硬件类型的议题多是吃瓜的心态

龙芯实验室张福新主任的《LoongArch指令集设计》

  • https://icsr.zju.edu.cn/uploads/new/4-2.pdf

我自己实现了一套用于追踪污点传播的框架,目前还不是很完善,只实现了Intraprocedural Analysis,Interprocedural Analysis正在实现

代码还是比较简单的,因为并不像学术研究那样用比较严谨的理论来指导实践,我主要是用经验来写,所以会有不少漏掉的地方,但对于我所关心的漏洞模型来说是足够用了

我抽象出其中一种漏洞模型给大家分享下我的处理思路

通过startActivity()打开MainActivity会调用方法onCreate(),将传入Intent里的字段赋值给类变量,再通过startActivityForResult()打开MainActivity会调用方法onActivityResult(),此时会使用到被污染的类变量进行任意私有组件调用操作

public class MainActivity extends Activity {
    private String mPackageName;
    private String mActivityName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = getIntent();
        mPackageName = intent.getStringExtra("packageName");
        mActivityName = intent.getStringExtra("activityName");
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Intent intent = new Intent();
        intent.setClassName(mPackageName, mActivityName);
        startActivity(intent);
    }
}

反编译生成的APK,观察对应的Smali代码

初始化部分相对简单,instance fields区域是之前控制流分析不曾关注过的,由于我们要进行代码细节分析,所以这部分需要思考如何进行利用

.class public Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"

# instance fields
.field private mActivityName:Ljava/lang/String;
.field private mPackageName:Ljava/lang/String;

# direct methods
.method public constructor <init>()V
    .locals 0

    .line 9
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    return-void
.end method

第一个流程,onCreate()内由Source为getIntent()的方法对类变量进行污染,第19行调用方法getIntent()并将返回值赋值寄存器v0,第20行将字符串"packageName"赋值给寄存器v1后调用方法getStringExtra()获取指定键名的键值赋值给寄存器v1,寄存器v1的值又通过指令iput-object赋值给类变量mPackageName,第21行开始的逻辑同理,获取到键名为"activityName"的键值后将结果赋值给类变量mActivityName

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 2
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;

    .line 16
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    .line 17
    const v0, 0x7f0b001c

    invoke-virtual {p0, v0}, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->setContentView(I)V

    .line 19
    invoke-virtual {p0}, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->getIntent()Landroid/content/Intent;

    move-result-object v0

    .line 20
    .local v0, "intent":Landroid/content/Intent;
    const-string v1, "packageName"

    invoke-virtual {v0, v1}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    iput-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;

    .line 21
    const-string v1, "activityName"

    invoke-virtual {v0, v1}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    iput-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;

    .line 22
    return-void
.end method

第二个流程,第28行初始化一个Intent类型的变量,第29行通过指令iget-object将类变量mPackageName的值赋值给寄存器v1,同理将类变量mActivityName的值赋值给寄存器v2,寄存器v1v2完成赋值操作后,通过指令invoke-virtual调用方法setClassName()v0存储的Intent类型对象进行操作,第30行通过指令invoke-virtual调用方法startActivity(),这也是我们关注的Sink

.method protected onActivityResult(IILandroid/content/Intent;)V
    .locals 3
    .param p1, "requestCode"    # I
    .param p2, "resultCode"    # I
    .param p3, "data"    # Landroid/content/Intent;

    .line 26
    invoke-super {p0, p1, p2, p3}, Landroid/app/Activity;->onActivityResult(IILandroid/content/Intent;)V

    .line 28
    new-instance v0, Landroid/content/Intent;

    invoke-direct {v0}, Landroid/content/Intent;-><init>()V

    .line 29
    .local v0, "intent":Landroid/content/Intent;
    iget-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;

    iget-object v2, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;

    invoke-virtual {v0, v1, v2}, Landroid/content/Intent;->setClassName(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;

    .line 30
    invoke-virtual {p0, v0}, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->startActivity(Landroid/content/Intent;)V

    .line 31
    return-void
.end method

从这个例子可以看出来,Path Finder使用的技术是没有办法对这类漏洞进行分析的,所以我开始探索如何使用数据流分析对这类漏洞进行自动化的挖掘工作

接下来的分析基于纯顺序执行的逻辑,不考虑存在分支的情况,具有分支的情况我们后面会提到

IMAGE

我们对方法onCreate()做过程内分析,记录污点传播过程,初始化两个字典用于记录寄存器和全局字符串字段的污染情况,分析结束的时候,可以看到两个全局字符串字段都被标记被污染了

invoke-super ==> invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
{'result': {'is_taint': False}, 'p2': {'is_taint': True}}

const ==> const v0, 0x7f0b001c
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': False}}

invoke-virtual ==> invoke-virtual {p0, v0}, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->setContentView(I)V
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': False}, 'p0': {'is_taint': False}}

invoke-virtual ==> invoke-virtual {p0}, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->getIntent()Landroid/content/Intent;
{'result': {'is_taint': True}, 'p2': {'is_taint': True}, 'v0': {'is_taint': False}, 'p0': {'is_taint': False}}

move-result-object ==> move-result-object v0
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}}

const-string ==> const-string v1, "packageName"
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': False}}

invoke-virtual ==> invoke-virtual {v0, v1}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
{'result': {'is_taint': True}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': False}}

move-result-object ==> move-result-object v1
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': True}}

iput-object ==> iput-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': True}}
{'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;': {'is_taint': True}}

const-string ==> const-string v1, "activityName"
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': False}}

invoke-virtual ==> invoke-virtual {v0, v1}, Landroid/content/Intent;->getStringExtra(Ljava/lang/String;)Ljava/lang/String;
{'result': {'is_taint': True}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': False}}

move-result-object ==> move-result-object v1
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': True}}

iput-object ==> iput-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': True}}
{'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;': {'is_taint': True}, 'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;': {'is_taint': True}}

return-void ==> return-void
{'result': {'is_taint': False}, 'p2': {'is_taint': True}, 'v0': {'is_taint': True}, 'p0': {'is_taint': False}, 'v1': {'is_taint': True}}
{'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;': {'is_taint': True}, 'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;': {'is_taint': True}}

但这只是过程内分析,我们并没有考虑在方法onCreate()里存在方法调用的情况,过程间分析需要后续再进行探讨,而且我们不考虑隐式流分析

现在我们进行类之间的组合搜索,比如对方法onCreate()进行搜索之后,以当前Override方法污染情况作为基本环境,进行其它Override方法的搜索

这里的其它Override我们就定为onActivityResult(),在调用startActivity()的时候,参数寄存器v0被标记为污染,所以这里可以提示存在漏洞

invoke-super ==> invoke-super {p0, p1, p2, p3}, Landroid/app/Activity;->onActivityResult(IILandroid/content/Intent;)V
{'result': {'is_taint': False}}

new-instance ==> new-instance v0, Landroid/content/Intent;
{'result': {'is_taint': False}, 'v0': {'is_taint': False}}

iget-object ==> iget-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;
{'result': {'is_taint': False}, 'v0': {'is_taint': False}, 'v1': {'is_taint': True}}
{'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;': {'is_taint': True}, 'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;': {'is_taint': True}}

iget-object ==> iget-object v2, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;
{'result': {'is_taint': False}, 'v0': {'is_taint': False}, 'v1': {'is_taint': True}, 'v2': {'is_taint': True}}
{'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;': {'is_taint': True}, 'Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;': {'is_taint': True}}

invoke-virtual ==> invoke-virtual {v0, v1, v2}, Landroid/content/Intent;->setClassName(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;
{'result': {'is_taint': False}, 'v0': {'is_taint': True}, 'v1': {'is_taint': True}, 'v2': {'is_taint': True}}

invoke-virtual ==> invoke-virtual {p0, v0}, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->startActivity(Landroid/content/Intent;)V
发现漏洞!!!!!!!!!!
{'result': {'is_taint': False}, 'v0': {'is_taint': True}, 'v1': {'is_taint': True}, 'v2': {'is_taint': True}, 'p0': {'is_taint': False}}

return-void ==> return-void
{'result': {'is_taint': False}, 'v0': {'is_taint': True}, 'v1': {'is_taint': True}, 'v2': {'is_taint': True}, 'p0': {'is_taint': False}}

这里的标记除了做TrueFalse之外,还可以进行污染路径的存储,比如这个污染源来自哪个方法,传递路径是怎么样的,都可以进行提示

接着来看代码里存在分支的情况

IMAGE

在最后几行的逻辑里,如果按照我们之前基于顺序解析执行的污点传播分析,会先执行if分支里的逻辑,但并不会通过goto进行跳转,而是继续往下执行else分支的逻辑,将v1寄存器置为空字符串并通过指令iput-object对全局类变量Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;进行赋值,同时清除这两个类变量的污点标记,这样的处理过程明显是不正确的

.method protected onCreate(Landroid/os/Bundle;)V
    ...
    
    .line 23
    iget-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;

    const-string v2, "com.wnagzihxa1n"

    invoke-virtual {v1, v2}, Ljava/lang/String;->startsWith(Ljava/lang/String;)Z

    move-result v1

    if-eqz v1, :cond_0

    .line 24
    const-string v1, "[TAG]"

    const-string v2, "Check mPackageName and mActivityName"

    invoke-static {v1, v2}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I

    goto :goto_0

    .line 26
    :cond_0
    const-string v1, ""

    iput-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mPackageName:Ljava/lang/String;

    .line 27
    iput-object v1, p0, Lcom/wnagzihxa1n/simpletaintanalysis/MainActivity;->mActivityName:Ljava/lang/String;

    .line 29
    :goto_0
    return-void
.end method

所以,我们需要构建Basic Block,然后通过对图的遍历来完成污点传播分析