以Xp0intCTF的so fun为例利用Zjdroid安卓脱壳步骤(以及排查出错问题)
本帖最后由 whklhh 于 2017-10-26 21:55 编辑在最后补上了linux通过srand48和lrand48得到genstr的值,从而得到flag的过程,以及附加模拟器(失败,得用真机{:301_999:})
==========================更新10.26======================================
首先,也许zjdroid这个工具的确已经很老了,不过作为学习、脱一些简单的壳应该还是可以的
(主要是官方Wp中说的这个工具,那肯定它是可以的嘛。当然优先复现它咯~)
遇到了很多坑,换了俩模拟器最后在真机上搞定,折腾了数个小时才搞定,感觉自己的排错能力真的提升了不少OTZ
Xposed和Zjdroid这命令,debug得我都轻车熟路了……
使用Zjdroid这个工具需要先安装Xposed框架,注意【安装框架】和【安装模块】完后都需要重启
(不注意看提示会掉坑撒)
重启后开启两个命令行,一个命令行查看log中的返回数据,另一个命令行来执行命令
Zjdroid的命令结果全都是通过broadcast返回的,因此需要logcat来接收
第一步:adb shell dumpsys activity top
这个命令可以显示当前显示程序的包名和pid
有了这个包名以后我们就可以筛选log了(否则太多啦)
在一个命令行使用
adb logcat -s zjdroid-shell-com.jnu.ctf2017
意思是从log中筛选zjdroid-shell-com.jnu.ctf2017
当发现程序在运行以后就会显示hook到的数据:
http://img.blog.csdn.net/20171025013322536?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
然后另一个命令行进入shell中开始执行zjdroid的命令就好了:
首先得到内存中dex的信息
root@x86:/ # am broadcast -a com.zjdroid.invoke --ei target 2102--es cmd '{"action":"dump_dexinfo"}'
-a com.zjdroid.invoke --ei target 【目标程序PID】--es cmd '{"action":"dump_dexinfo"}'
http://img.blog.csdn.net/20171025013602192?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
注意要在shell中输入哦
我刚开始用adb shell am xxxxxx的方式输入时无限报错,最后去掉单引号就能成功dump出dex信息了,但是后一步就没办法了╮(╯_╰)╭试了好久好久
【搜索的时候发现有不少人问log中显示the cmd is valid,都没有查到结果;除了命令确实输错以外,就可能是这个原因,请仔细检查单引号、cmd后的空格、以及是否在shell中和是否具有root权限(在Shell中输入su一下即可)】
这样在另一边的Log窗口就能看到信息啦:
http://img.blog.csdn.net/20171025023605913?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
两个filepath就是内存中的文件了,壳解密出的东西就是它们了
我们只需要把它们dump下来就OK咯!
(注意mCookie:-1,这是第一个大坑)
dump命令:root@x86:/ # am broadcast -a com.zjdroid.invoke --ei target 【目标程序PID】--es cmd '{"action":"backsmali",dexpath:"/data/app/com.jnu.ctf2017-1/base.apk"}'
结果:
http://img.blog.csdn.net/20171025030038448?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
嗯?!咋回事儿?
试了几遍都是这样
按照提示去源码中查了一下:
http://img.blog.csdn.net/20171025024725911?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
我知道mCookie=-1肯定不对劲儿,可是这Cookie是你给的啊(╯‵□′)╯︵┻━┻
权限也都是最高的了,没道理出错啊
那我们乖乖看log呗:
http://img.blog.csdn.net/20171025024229022?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
我选中的部分就是核心出错点,上半部分是正常的log,加载了backsmali命令
下半部分是出错位置的调用堆栈,可以看到,位置在backsmaliDexFile方法中的getCookie中出错了
又去翻译了一下,这个错误似乎是long到int的类型转换出错了……
估计是程序返回的cookie太大,超过了int的范围;而Zjdroid中的变量是int类型,导致溢出直接转换报错,返回-1了……
说到这我想起来,所用模拟器(雷电模拟器)是安卓5.1版本,似乎应用了64位系统。
我用它也正是因为上一次做的某个CM要求64位系统。也许是这个原因吧。
当时也差不多猜到可能这个系统太新了的缘故,于是换回以前使用的夜神模拟器。
值得一说的是,Xposed的APK我是从官网下的最新版,结果拖进夜神直接报错
查了一波发现有旧版的可以给夜神用,遂安装成功
同样步骤进行下来,返回的mCookie终于正常了:
http://img.blog.csdn.net/20171025025529171?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
看到这个巨大的负数简直让我兴奋的不行,暗想幸亏我还留着这个以前跟着我的模拟器……
没想到这是另外一个坑
继续下一步,dump:
好嘛,这次更恐怖,直接结束运行了……
http://img.blog.csdn.net/20171025025911149?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
又试了一次,换别的app dump也一样,说明还是Zjdroid和系统的适配问题……
这次不猜了,乖乖看Log吧:
http://img.blog.csdn.net/20171025030403192?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
同样的位置,从开始向下看一下就找到了
这次Cookie对了,但是找不到框架中的libdvmnative.so库了
我查了一下,似乎有人说这个库是闭源的啊,有人说是安装过程的问题啊等等
报错的进程是davikvm虚拟机,我感觉跟模拟器是x86处理器架构,而真机是ARM处理器架构有关
于是我就掏出尘封已久的价值一百块的二手中兴真机,再来一遍
(别问我为啥不一开始就上真机,太菜了不好意思掏出来(而且懒得充电 没电关机落灰很久了
再值得一说的是,那个适用于夜神的老版Xposed居然不适用于辣鸡中兴
我本来以为脱壳无望了,查了一波发现有人修改出了努比亚专用的Xposed……
安装竟然成功了,自此一帆风顺这次终于没问题了……
http://img.blog.csdn.net/20171025030740231?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
以上是脱壳和查错教程
(其实一开始就上真机就一点问题都没了啊(╯‵□′)╯︵┻━┻)
下面开始本题的WP
这一题来自前两天的Xp0int(暨南大学校赛)的re,题名为so fun
查壳的时候发现有两个文件,apk和secData.jar
这个jar其实我比赛的时候就看到了,感觉有点问题但是反编译不出来,就没太在意
原来这个就是关键的加密dex啊!
dump下来以后拖入jeb反编译,查看MainActivity:
http://img.blog.csdn.net/20171025031148921?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
过程太简单了好感动
取到输入字符串以后传入checkstr方法,这个方法是native导出的,要去so库中找
IDA反编译以后找到checkstr导出函数
http://img.blog.csdn.net/20171025031558803?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
checkstr调用的时候实际上送入了一个参数,就是这里的v0了
v1来自于genstr生成
然后cmpstr比对genstr里还算简单,不过随机数有点麻烦,需要模拟环境
今天太晚了我就不开虚拟机搞了,明天再说吧
http://img.blog.csdn.net/20171025032211040?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
另外strcmp函数并不是简单的字符串对比,里面还有一个处理:
http://img.blog.csdn.net/20171025032346501?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
再异或一下0x6a,内部生成flag
官方WP的方法是直接新建一个工程调用so库文件,动态调试cmpstr,找到v2的值后脚本跑出v3
我觉得其实直接IDA附加到这个库上应该也可以
再不济通过linux还原genstr应该也行吧;不过不知道linux的x86处理器和安卓的ARM处理器会不会对随机数算法有什么影响……应该没有吧,只要库文件相同,算法就相同的
明天尝试一下几个解决方案
总体来说Zjdroid的步骤其实很简单,hook以后得到pid,再查看dexinfo,最后dump出来即可;这么多步骤主要都是各个模拟器下的问题,如果一开始就用真机就半小时解决了……
不过也算锻炼了一下纠错查错的能力,玄学,玄学
想起来去年的CTF国赛也有一个带壳安卓,当时看的WP用的是另外一个通用脱壳程序,明天有空再翻出来试试吧(还有AndroidHunter,虽然现在估计对付新时代的壳已经没用了……)
这个题目不脱壳其实也能做来着,直接反编译这个库然后研究返回值就行……虽然不脱壳也不知道APK里有没有对JNI方法返回的值再做变换就是了……
感觉好可惜~
题目链接: https://pan.baidu.com/s/1boGHz0z 密码: hxwq
===============================================更新====================================================================
补上昨天的遗漏和测试
首先是代码纠正:
http://img.blog.csdn.net/20171026113847317?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2hrbGhoaGg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast
之前想的太简单了,if判断内部其实是进一步的校验,而不是我以为的flag生成(估计太晚了脑子有坑了吧,输入的就是flag还生成个鸡毛哟)
if判断第一个值,通过以后在循环中判断其余13个值,注意i的初值为1,也就是说实际上就是连续判断,写成这种结构纯粹多此一举……又不是在服务器上节省资源233
虽然看起来有点像DO..WHILE结构,不知道是不是IDA反编译差了
不过不影响效果╮(╯_╰)╭
我们知道这玩意儿是将genstr的结果+下标i以后异或一个数组中的对应值后,与input比较就够了
用同样的种子和算法就能生成相同的伪随机数序列,由此得到genstr,进而算出flag
验证代码的时候顺便查了一下,srand48是指一个线性同余法和48位整数运算的生成算法,windows下没有这个函数,但是可以自己参考linux的库文档,手动写一个函数(貌似很短,百度一下就有)
我直接在linux上进行了,注意头文件stdlib.h
代码(Linux C):
#include <stdio.h>
#include <stdlib.h>
int main()
{
char s[] = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
int key[]={106, 124, 104, 31, 16, 39, 43, 6, 62, 39, 8, 70, 56, 20};
char flag;
int j;
srand48(0);
for(j=0;j<14;j++)
{
flag = s + j;
flag ^= key;
}
puts(flag);
return 0;
}
PS:用应该就不会被吃了吧{:301_1003:}
然后确认了IDA附加:
将/dbgsrv下的android_server 通过adb push进安卓端以后进入shell执行:
C:\Users\hasee>adb push E:\ctf\IDA_Pro_v6.8_and_Hex-Rays_Decompiler_(ARM,x64,x86)_Green\dbgsrv\android_server /data/
941 KB/s (523480 bytes in 0.543s)
C:\Users\hasee>adb shell
root@x86:/ # su
su
root@x86:/ # cd /data
cd /data
root@x86:/data # ./android_server
./android_server
IDA Android 32-bit remote debug server(ST) v1.19. Hex-Rays (c) 2004-2015
Listening on port #23946...
这样就在23946端口进行监听了,然后在端口转发以后就可以用IDA附加上去啦
C:\Users\hasee>adb forward tcp:23946 tcp:23946
附加上以后只要运行到断点就报错:
FFFFFFFF: got SIGILL signal (Illegal instruction) (exc.code 4, tid 2240)
查了一下应该是模拟器为x86架构,而so程序是ARM架构的原因只能附加真机咯~出来玩没带那货╮(╯_╰)╭回去再补上下文~
时光不老时 发表于 2017-10-25 12:19
大神,请问一下,我新手刚入门应该从哪方面开始
{:301_999:}我也还很菜呢
单纯APK入门的话,从Android Killer或APK改之理、jd-gui或jeb2等工具的使用开始学咯
(当然最好还是先学正向写APK)
论坛里有很多教程,搜索一下就能找到的 root@x86:/ # am broadcast -a com.zjdroid.invoke --ei target 【目标程序PID】--es cmd '{"action":"backsmali",dexpath:"/data/app/com.jnu.ctf2017-1/base.apk"}'
{:17_1084:}这一步,只要一执行,手机上app就奔溃了 支持一下。 厉害厉害 厉害厉害 厉害学习了 支持以下。。。 感谢分享, 学习了 不明觉厉 收藏,感谢分享 慢慢来就好了了