Legu3.0脱壳心路历程 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

Legu3.0脱壳心路历程

申博_新闻事件 申博 86次浏览 未收录 0个评论

概述

本片文章形貌一次完整的脱壳进程,从java层到Native层

流程概述

Java层

  1. java层找到库函数的进口位置
  2. 过掉java层的反调试(处理要领在Native层:动态在isDebuggerConnected下断点)

Native层

  1. 绕过Anti IDA
  2. 双层解密JNI_OnLoad
  3. 动态调试JNI_OnLoad,获得注册的当地要领的详细位置
  4. 剖析load要领找到Dex动态解密的处所并dump

细致历程

此次脱壳用的测试机是Dalvik假造机4.4版本,所以底层用的libdvm.so库文件。

壳特性

有过壳履历的剖析职员能够从安装包的特性文件和lib下的libshellxxx.so中看出是TX加固过的壳

Legu3.0脱壳心路历程

java层

实锤加壳

在manifest中的进口类LoadingActivity是找不到的

<application android:theme="@style/AppTheme_Main" android:label="@string/app_name" android:icon="@mipmap/icon_launcher" android:name="com.tencent.StubShell.TxAppEntry" android:allowBackup="false" android:vmSafeMode="true" android:largeHeap="true" android:supportsRtl="true" android:extractNativeLibs="true" android:networkSecurityConfig="@xml/network_security_config" android:appComponentFactory="androidx.core.app.CoreComponentFactory">

...........
<activity android:name="com.warmcar.nf.x.ui.activity.main.LoadingActivity" android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

初探attachBaseContext

既然进口类被隐蔽了,我们依据挪用关联找到启动进口类的处所,即Application这个类,我们重要须要关注的是attachBaseContext要领,这个在onCreate要领之前实行的

弃用jadx

这个要领起首挪用e(context)举行了调试搜检,接着在b(this)要领中举行了一些库地点的初始化操纵

接着在 d(context)要领中加载不存在的库nfix、ufix,而且挪用了当地要领fixNativeResource、fixUnityResource,从称号上看应当是修复操纵

接下来重假如tx的SDK崩溃信息网络模块的功用,这块能够省略,重要看末了一个a((Context) this)要领,find Usage跳转过去发明挪用了e()要领和load(f)要领

protected void attachBaseContext(Context context) {
        super.attachBaseContext(context);
        e(context);
        SystemClassLoaderInjector.fixAndroid(context, this);
        if (b(this)) {
            d(context);
            this.k = new Handler(getMainLooper());
            String str = "3.0.0.0";
            String str2 = "900015015";
            UserStrategy userStrategy = new UserStrategy(this);
            userStrategy.setAppVersion(str);
            CrashReport.setSdkExtraData(this, str2, str);
            CrashReport.initCrashReport(this, str2, false, userStrategy);
            new Thread(new d(this)).start();
            a((Context) this);
        }
    }

private void d(Context context) {
        AssetManager assets = context.getAssets();
        String str = context.getApplicationInfo().sourceDir;
        try {
            System.loadLibrary("nfix");
            fixNativeResource(assets, str);
        } catch (Throwable th) {
        }
        try {
            System.loadLibrary("ufix");
            fixUnityResource(assets, str);
        } catch (Throwable th2) {
        }
    }

public void a(Context context) {
        e();
        load(f);
    }

而在jadx这里e要领并未天生响应伪代码,反汇编指令却是没有错,为了轻易剖析,开启我们的jeb继承剖析

Legu3.0脱壳心路历程

接盘侠:jeb探究初次加载so库

继续剖析e();要领,依据反编译后的伪代码,能够看到这里第一次举行了so库的加载,加载shell

Legu3.0脱壳心路历程

另有一个紧跟着的当地的load要领,这个须要我们在Native层举行剖析,参数是shella-3.0.0.0.so文件途径

寥寥几句onCreate

剖析完attachBaseContext,接着剖析onCreate

能够看到出了一个反调试和崩溃信息网络,我们的关注重点就在当地要领runCreate

public void onCreate() {
        TxAppEntry.isDebugger(((Context)this));
        TxAppEntry.runCreate(((Context)this));
        this.sdkcrash(TxAppEntry.context);
    }

private static native void runCreate(Context arg0) {
    }

再度回忆加壳包目次

加固重要行动都在这里,能够从目次称号看出,多个反调试类

刨去没什么太紧急的类,只需一个TxReceiver类值得专注

Legu3.0脱壳心路历程

经由过程交织援用,并未发明有处所注册播送来实行这里,消除静态注册,剩下只需动态注册能够,都须要Native层的剖析。而且他的回调要领onReceive的内部完成是经由过程当地要领reciver完成的,是须要第二个关注的点

public class TxReceiver extends BroadcastReceiver {
    public static String TX_RECIEVER;

    static {
        TxReceiver.TX_RECIEVER = "com.tencent.StubShell.TxReceiver";
    }

    public TxReceiver() {
        super();
    }

    public void onReceive(Context arg1, Intent arg2) {
        TxAppEntry.receiver(arg2);
    }
}
######################################################TxAppEntry.java
public static void receiver(Intent arg0) {
    TxAppEntry.reciver(arg0);
}

private static native void reciver(Intent arg0) {
}

短暂小结,再度起程

壳的剖析基本到这里停息下来

重要剖析效果

​ 找到了唯一一个要加载的库shella3.0.0.0.so,依据剖析流程继承剖析native层的load、runCreate要领

留下的迷惑

​ 修复ufix、nfix是不是获得挪用

​ 播送行动

Native层

剖析shella3.0.0.so,初次加载的so库

剖析目的

  1. 当地要领runCreate
  2. java层修复ufix、nfix的fixNativeResource、fixUnityResource要领是不是获得挪用,做了哪些行动
  3. 实锤播送注册,探究播送行动

出师未捷,匹敌IDA

IDA6.8翻开libshella-3.0.0.0.so弹出未辨认的节花样,反编译失利,什么东西都没有!这不禁引发了我对人生的思索,是匹敌反编译吗、照样匹敌IDA呢?这是我须要探究的题目

Legu3.0脱壳心路历程

Legu3.0脱壳心路历程

运用010edit翻开so文件,能够看到剖析文件是没有题目的,然则text、init等一般节头表内的数据都被抹空了,一般节头没有,如.dynstr、.dynsym

思索

【1】假如IDA依据节数据举行反汇编,这里数据都为空,确切会反编译失利,那末怎样恢复这些节表呢?然则在看到参考【4】中文章的时刻,依据之前运用履历得出一些主意,IDA在辨认节头失利后会去通历顺序头表来举行剖析

【2】上面这类报错:检测出不辨认的section花样致使住手反编译的行动很明显是匹敌IDA这类反编译东西的,这也回覆了上面须要探究的题目。

​ 为了处理其匹敌IDA行动,我们这里直接将节内数据置空或许将包括字符的节数据置0,让他辨认无意义或不法的节声明,接着运用顺序头来举行剖析即可。终究定位到.dynsym表的s_size字段,将这个字段置0即可

Legu3.0脱壳心路历程

Anti不能停:JNI_OnLoad加密

过掉AntiIDA后,再次加载so文件,能够看到导出JNI_OnLoad函数已被加密了(假造内存地点=0x274C),那末合理向上推导,只能在.init节或许.init_array节中

接下来的目的就是找到init、init_array节地点的地点

Legu3.0脱壳心路历程

处理思绪

【1】修复section节头

【2】动态调试so,经由过程在linker.so高低断点

Legu3.0脱壳心路历程

section修复,觅得init_array

修复之前多个节都是置空的,另有一般节毛病数据来Anti IDA

Legu3.0脱壳心路历程

经由过程开源代码对so文件举行修复后,在linux平台用readelf能够看到已将许多节头的偏移恢复了,

Legu3.0脱壳心路历程

在ida6.8翻开时,起首涌现下面两个弹窗中的涌现的毛病,悉数确认

Legu3.0脱壳心路历程

Legu3.0脱壳心路历程

我们依据觅得的init_array地点,抱着高兴的心情举行G跳转到0x3e84,这里牢记别乱改数据类型,这里应当是DCD代表双字,代表的地点是0x944。

这里我犯了个错,由于不太熟吧,乱改数据类型,改成DCB字节型,效果转成代码后就懵了,在意气消沉下我翻开了IDA7.2,看到下面谁人图,一度让我预备和IDA 6.8 say 拜拜。然则由于7.2 F5大法不管用(缘由临时未知),6.8照样很棒的,照样和它做好基友吧

Legu3.0脱壳心路历程

这里假如用IDA7.2版本,他这里会辨认出init节并标记(觉得棒棒哒)

Legu3.0脱壳心路历程

通读伪代码,剖析init_array

这里重要剖析出:

  • 解密算法是从0x1000最先,对0x2AB4字节数据举行解密(JNI_OnLoad地点为0x274c必定被包括在内)
  • 挪用JNI_OnLoad

剖析出解密算法,能够自身写剧本举行解密,这里我们挑选别的一种,往下看

Legu3.0脱壳心路历程

另辟蹊径,解密JNI_OnLoad

思绪:so库一经加载到内存后,要处于解密后的状况才能够一般被顺序挪用,所以从内存中dump出shella-3.0.0.0.so文件,即完成对JNI_Onload解密的操纵

无意之举吧,:)

当时预备经由过程调试猎取init_array内存地点的时刻没有胜利,当时想着dump下so文件应当包括有解密后的节头表,厥后看到一篇文章连系ELF装载知识才晓得节头表并不会被装载进内存更谈不上dump下来,然则用IDA翻开后的JNI_OnLoad确是解密后的

解密剧本,详细内存地点和加载进内存的段长度,须要自身调试的时刻Ctrl+S自身看和盘算

static main()
{
    auto i,fp;
    fp = fopen("d:\\dump","wb");
    auto start = 0x75FFD000;
    auto size = 32768;
    for(i=start;i<start+size;i++)
    {
        fputc(Byte(i),fp);
    }
}

实在挪用

在动态调试的历程当中,挪用JNI_OnLoad要领的处所不是init_array节内,而是libdvm.so文件中的dvmLoadNativeCode要领。

Redis 4.x RCE分析

Redis 4.x RCE 本文介绍由LCBC战队队员Pavel Toporkov在zeronights 2018上介绍的redis 4.x RCE攻击。会议slide链接:https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf 攻击场景: 能够访问远程redis的端口(直接访问或者SSRF) 对redis服务器可以访问到的另一台服务器有控制权 背景知识 redis协议 redis支持两种传输协议,一种是明文传输,其命令如下: SET keyname value\n 另一种是经过编码的传输协议: *3\r\n$3\r\nSET\r\n$7\r\nkeyname\r\n$5\r\nval
ue\r\n 将其格式化大概长这个样子:

Legu3.0脱壳心路历程

剖析不能停,探究JNI_OnLoad

初遇小坑

图中圈起来的函数,终究跳到相似0x3FB8地点出的处所,为何这个处所的函数地点是找不到的呢?

Legu3.0脱壳心路历程

Legu3.0脱壳心路历程

蓦然回首,本来是重定位

由于这里挪用的是第三方库函数,这里就用到了PLT表,每次挪用第三方库函数都邑跳到PLT条目中。这个表有关第三方函数的每个条目都指向了GOT表条目的值,第一次接见第三方库函数的时刻,现实上去实行了剖析函数,将第三方库函数的内存地点存储到GOT表内而且挪用,背面再次执库函数的时刻,在PLT条目中就会直接实行到第三方库函数的内存地点处,而不必再次剖析。

所以上面之所以找不到库函数地点,是由于重定位后被改写后的内存地点,在静态文件中是不能辨认的。

绕过也是很简单的,由于我们解密的数据长度有限,我们将解密部份替代到本来的shella-3.0.0.0.so文件中即可,再次翻开如下图所示,都是一些偏移能够被IDA辨认出来

Legu3.0脱壳心路历程

再现加密

第一次解密中的行动,这里i自身就是libshella-3.0.0.0.so文件的内存基址,这里将地点存进dword_4008变量中

Legu3.0脱壳心路历程

这里实在就是读取shella-3.0.0.0.so文件的称号到变量中

Legu3.0脱壳心路历程

接着将获得的libname和一个偏移值0x6D88(恰好指向libshella-3.0.0.0.so文件尾部附加数据最先的位置)作为参数传进函数内,实行以下操纵

– 统共三次从尾部读取一切数据到内存,并举行解密运算

Legu3.0脱壳心路历程

真JNI_OnLoad

这里挪用了dlsym来在so文件中找到JNI_Onload标记地点并举行挪用。

剖析到这里实在除了之前的解密操纵,我们并没有看到任何动态注册当地要领的处所,那末连系这里涌现标记挪用能够斗胆勇敢猜测,这里能够会是二次解密后获得的JNI_OnLoad要领的源码位置,上面剖析的应当只是一层加密的壳JNI_OnLoad要领,下面依据猜测举行警惕求证

Legu3.0脱壳心路历程

动态调试跟进解密后的JNI_OnLoad要领

这里将壳进口类名作为参数传进函数,下面推断假如返回效果为0则打印出注册当地要领失利如许的字符串

Legu3.0脱壳心路历程

依据传入壳的进口类名作为参数举行类定位和注册当地要领

Legu3.0脱壳心路历程

惊现:0x35C

Legu3.0脱壳心路历程

发明偏移0x35C,这正是registerNatives相对于JNINativeInterface的偏移。他的第三个参数是JNINativeMethod构造体数组,第四个参数就是构造体数组的长度,注册要领数目。只需经由过程剖析JNINativeMethod构造体即可获得注册当地要领的实在地点

typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
} JNINativeMethod;

剖析当地要领

注册要领数目为5。

当地要领对应内存地点

load 0x75700B1D

runCreate 0x756fc469

changeEnv 0x756FB37D

receiver 0x756f7621

txEntries 0x756FB0F9

0B 9E 70 75 10 9E 70 75  1D 0B 70 75 2D 9E 70 75
10 9E 70 75 69 C4 6F 75  37 9E 70 75 10 9E 70 75
7D B3 6F 75 41 9E 70 75  49 9E 70 75 21 76 6F 75
65 9E 70 75 6F 9E 70 75  F9 B0 6F 75

骤现非常

要剖析上面当地要领,就须要合营动态调试综合来举行。然则当我们在load要领高低断点后,顺序并不能实行到这里,从日记中反应一个signal 11的毛病,而且顺序也不能一般跑起来,弹出运用已住手的窗口。

思索:我这里为了调试,第一:只是将AndroidManifest.xml文件添加了一个可调式属性。第二解释掉了几个public.xml中的几个无关属性防备反编译失利。第三就是签上了自定义署名。怎么会涌现signal 11

Legu3.0脱壳心路历程

在网上找到一些相似的处理要领,先用addr2line敕令定位失足的处地点库文件的什么处所,依据栈回溯backtrace打印出的内容来定位:arm-none-linux-gnueabi-addr2line.exe 00022108 -e libc.so,返回效果为??:?

这里我们卡在了脱壳的历程当中,该解密的区段都已解密胜利了,就在即将要最先挪用java层的native要领的时刻,这里涌现signal 11的毛病,怎么办呢?

退一步海阔天空,注入大法好

虽然临时没法肯定出题目的细节,然则大抵方向是能够把握的:由于重打包后,顺序涌现崩溃

为何要重打包?由于要要修正AndroidManifest.xml文件增添可调式属性,不然jdb没法启动运用。

那末有方法替代修正调试属性的操纵吗?有,参考【9】,init注入或许xposed。这里直接用写好的东西mprop,实行./mprop ro.debuggable 1即可。

绕过反调试,手动绕过isDebuggerConnected

当我们最先调试的时刻,实在java层有一个反调试,就在壳代码中,最最先是经由过程反编译smali代码,删除响应代码来匹敌它的,然则由于反编译后会涌现顺序非常,我们这里经由过程mprop绕过,接下来就须要绕过这里的反调试。

思绪:

Legu3.0脱壳心路历程

【1】patch掉该处代码,从新修正dex文件头的signature和checksum

【2】动态修正isDebuggerConnected的返回值,参考【10】

这里我用的第二种要领

load:中心逻辑

顺遂到在load函数中下上断点。

Legu3.0脱壳心路历程

下面重要剖析中心内容。

  • 猎取odex基址,0x750DD000

Legu3.0脱壳心路历程

  • 猎取dex文件偏移、地点,而且解密dex头部数据到内存中

Legu3.0脱壳心路历程

  • 依据解密后的DEX头部0xE0字节数据+DEX偏移指向的盈余部份数据,连系起来就是原始DEX文件

dump解密后的头部0xe0字节数据

static main(void)
{
    auto fp, begin, end, ptr;
    fp = fopen("d:\\header.dex", "wb");
    begin = 0x74fd7000;
    len = 0xe0;
    for ( ptr = begin; ptr < begin+len; ptr ++ )
        fputc(Byte(ptr), fp);
}

ida剧本打印ODEX文件在内存中的一切数据

static main(void)
{
    auto fp, begin, end, ptr;
    fp = fopen("d:\\dump.odex", "wb");
    begin = 0x74fd7000;
    end = 0x75b2f000;
    for ( ptr = begin; ptr < end; ptr ++ )
        fputc(Byte(ptr), fp);
}
  • dump出Dex header和全部ODEX文件数据后,然后依据Dex Header中的file_size字段dump出Dex文件,接着用准确的Dex Header头替代毛病的头部即可(010edit:ctrl+shift+a,运用select Range即可)

Legu3.0脱壳心路历程

脱壳思绪

  1. 搜刮DEX文件的magic字符64 65 78 0a 30 33 35,截取前0xE0长度的字符并dump到classes.dex当地文件中。猎取偏移0x20处的文件大小长度。

  2. 接着搜刮/proc/<pid>/maps猎取odex的内存基址,依据下面盘算,获得dex文件偏移地点。a1+0x6C=data_off,a1+0x68=data_size</pid>

  1. dex偏移 + ODex基址 + 0x28即Dex文件内存地点。连系文件大小dump出dex文件数据,接着去除前0xE0字节数据,将盈余内容写入classes.dex文件中

小结

【1】IDA在辨认节头失足的情况下,会去辨认顺序头继承剖析

【2】ELF基本:ELF节头表不能被装载进内存。由于ELF顺序装载历程当中只用到了顺序头表

【3】#define HIDWORD(l) ((DWORD)(((DWORDLONG)(l) >> 32) & 0xFFFFFFFF))

【4】Alt+S:修正段属性,将须要保留的段内存勾上loader选项,TakeMemorySnapshot(1);:IDC语句,直接打下内存快照

【5】0x28为odex文件花样中dex_header的相对偏移地点,所以(odexAddr + 0x28)为该odex文件花样中dex header的相对地点

总的来说,是一次因陋就简的脱壳旅程,然则从效果看照样胜利的。半途涌现许多题目,耐烦是必需的。不足也是许多的:

  • JNI当地要领注册挪用逻辑不熟悉,历程当中的许多处所是参考其他文章进修到的。
  • 伪代码也不是完整弄懂了,许多代码细节是隐约的

申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明Legu3.0脱壳心路历程
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址