从一些Android题目来学习相关知识。
攻防世界
easyjni
来到关键事件,取出输入后传入a方法,在a方法又把输入传入了实例化的一个a类对象中的a方法。全是a。。最后调用native层的ncheck函数。
a类中的a方法一个换表的base64,native层ncheck函数进行简单加密后与密文对比。
native层有JNIEnv方法,ida不会自动识别,将指定变量改一下类型就行了。JNIEnv*
用一下ida-python。
1 | import base64 |
app3
这个题涉及的知识挺多,对初学安卓挺不错的。
从010editor发现了ANDROID BACKUP,安卓备份文件。开始出现新的知识了,找了一篇文章讲这个的学习了一下,讲的真好。Android中allowBackup,知道了这个可以用android-backup-extractor(abe)工具来解析ab文件。
对于ab文件,前24字节类似文件头的东西,若文件是加密的话,可以在前24字节中看见AES-256标志,否则出现none字符。
使用java -jar abe.jar unpack 1.ab 1.tar
解析文件,这样得到一个tar压缩包。解压后发现有一个apk文件和一些数据库文件。
上学期学习了java和mysql这2门课程,真是好。hha。。
看了看反编译apk中的代码,发现创建表操作和其中包含的flag文件:
再加上外面的数据库文件,可以猜测我们的flag就那些数据库中。
查询相关资料,知道了这个要用DB Browser for SQLite中的SQLCiper的打开,下载后准备打开数据库文件发现要密码。这也和猜想的一样,题目就是要让我们找这个密码。
对于key,通过传入Stranger和123456经过a包中的a,b类中的方法加密。a类中的方法就是字符串截取操作,b类中的方法一个MD5一个SHA-1。程序是可以调试的,所以直接调试得到key。
打开数据库得到一串base64字符串,解密得到flag。
easy-apk
找到主活动,就一个变表的base64编码,python简单写一下就好:
1 |
|
easy-so
简单考了java中对动态链接库的调用。
1 | s = list('f72c5a36569418a20907b55be5bf95ad') |
easyjava
就是读繁琐的java代码,理解程序加密流程。
考点:java代码的阅读能力;简单的置换加密。
1 | enc = 'wigwrkaugala' |
Ph0en1x-100
输入字符和和so层函数获得的字符串经过md5加密后进行比较。
从这个题练习了下调试so。注意:安卓模拟器不能调试arm架构的so文件,还是最好用真机来调试native层。
首先是直接模拟程序跑flag:
1 |
|
然后就是调试了。简单记一下流程:
准备工作:
1.解包程序,apktool.bat d a.apk。在AndroidManifest.xml中的application后面加上android:debuggable=”true”。
2.重新打包程序,apktool.bat b a,这时候在a文件夹中的dist下的就是目标重新打包后的程序。
3.对重打包后的apk进行签名。
- 生成签名文件:keytool -genkey -alias abc.keystore -keyalg RSA -validity 20000 -keystore abc.keystore
- 进行签名:jarsigner -verbose -keystore abc.keystore -signedjar b.apk a.apk abc.keystore
接下来把程序装入模拟器,运行,进行调试,操作和常规的调试elf文件差不多了。
以调试模式启动程序:adb shell am start -D -n 包名/.活动名
端口转发:adb forward tcp:23946 tcp:23946
因为我用的模拟器,这里用了x86架构的so文件才调试上,虽然有点不能理解,为什么模拟器里程序都能运行,说明对so文件是运行了的啊,那调试怎么就不行呢(在之后突然想到的想法补充:可能是题目有对应结构的so文件,只是没提供给我们),且android_server在模拟器里也可以运行的。。其实这里的提示可以看见它调用的是1/lib/x86/下的so。
嗯,,初学一个东西开始总是有很多疑惑的,等接触多了来慢慢理解。
ida中成功附加上程序后,在Modules模块找到我们要调试的so文件,再继续找到我们要调试的函数。
得到结果:
黑客精神
出现新知识点:so文件中的函数为动态注册。
然后就是一个加上文件读写操作的异或加密。
2个涉及的函数功能:通常连用来计算出一个文件中数据的字节数。
- C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
- C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置。
接下来解题部分:直接输入密文值,在文件中找到异或后的值即是flag。
easy-dex
题目得到flag的难度不大,但涉及的知识还需要多学习。
jadx中发现没有dex文件,从AndroidMainfest.xml看到:NativeActivity,安卓进行ndk开发使用的,所以应该是转战so文件中了。
找到android_main函数,首先是进行解密了2个字符串,打印出来就是app包路径相关的。
然后从打印的log,可以帮助识别程序的功能和流程。就是在10s内摇动手机100次,然后会使用这之间的表示次数的数据来解密一些数据代码:就是把enc分为10组,然后前8组分别和9, 19, 29…异或解密,后2组和89异或解密。
然后解压缩数据,写入文件。从上面打印出的路径信息,可以猜测这个就是生成一个dex文件。
仿照程序逻辑使用idapython解密数据后写入文件。
1 | from ida_bytes import * |
然后就是花了点时间的的uncompress操作,试了试linux下的发现不行。然后搜索文件头:78 9c,发现可以使用Aluigi’s offzip :
提取出文件其中的数据,得到一个dex文件:
分析dex文件。发现有很多id,在res\values\public.xml中找到对应id的name,然后再到strings中找到对应name的字符串。对其注释了一下:
看了一圈后,可以知道我们输入的经过一种加密算法后与密文比较,而I have a male fish and a female fish.是key,看到只是取了它的32位,然后是key判断。
开始从字符串fish以为是blowfish加密,但简单看了下流程,完全对不上。
blowfish是加密64bit为数据,密钥也是64bit位。把密文分为每8个字节一组,然后一组分成2个部分,进行轮函数加密。
但这里是每16个字节一组,且加密流程也不一样:
最后,翻了一下这个加密中的数据用来搜索下找找相关的加密算法。
从最顶部的数据,google结果:TwoFish
然后使用python解密一下:因为33位,不是16的倍数,用0补齐成了48位。
1 | from twofish import Twofish |
最后是做完题看了别人wp学习,发现:
- 那个uncompress操作其实可以直接使用python的zlib模块,zlib.decompress(data) 就可以了。
- 可以把dex合成新的apk文件运行,接下来操作一下。
dex合成新的apk文件:
1.把之前的app解包。
2.把AndroidManifest.xml中的android:hasCode=”false”属性去掉,默认为True。原因:之前的app是用c++开发的,app中并不包含java代码,所以把这个属性设置为False。
3.把AndroidManifest.xml中activity的android:name改为当前包和MainActivity。
4.把之前得到的dex文件改名为classes.dex放到解包目录下。
5.重新打包,并签名。
成功:
你是谁
找到触摸响应的地方,可以看到sorted flag相关字符串,然后上面的getsna方法是得到汉字字符的unicode编码进行一个从小到大的选择排序,最后比较。
这里从排序结果后的汉字编码结果可以推出未经过排序后的值。
最后按照从字符串的提示,把汉字结果再转回unicode编码再套上flag{}即是。
基础android
看了下程序逻辑,解出第一个密码:
1 | s = [107-i for i in range(12)] |
然后进入MainActivity2把第二次的输入作为参数发送了一个广播:
继续跟到接受广播的地方,看到又开始了一个新的活动。
但看了后面的NextContent也没有验证图片显示码的地方,但是读了下代码,功能就是取出app中的资源文件中的timg_2.zip作为图片替换原来的图片。直接在jeb的Assets找到该文件,打开即是。
其实,既然是NextContent活动直接更新了有flag的图片,直接adb启动指定的活动,也可以得到。
1 | adb shell am start -n com.example.test.ctf02/.NextContent |
最后学习知道了那个图片显示码其实触发广播需要的密码,在清单文件有。
且android:exported=”true”属性代表可以进行外部调用,那我们还可以用adb构造一个广播来达到目的。
1 | adb shell am broadcast -a android.is.very.fun |
APK逆向
把Tenshine进行md5().hexdigest()加密后取出偶数位。
1 | s = md5(b'Tenshine').hexdigest() |
题目很简单,但是从搭环境开始通过这个题学了一下frida hook java层。
简单记录一下流程:
- 安卓端执行下载好的对应frida服务。
- 进行2次端口转发:
adb forward tcp:27043 tcp:27043
和adb forward tcp:27042 tcp:27042
- 安卓端运行app
- 执行写好的hook脚本。
贴下hook代码:
1 | import frida |
人民的名义-抓捕赵德汉1-200
一个jar包,用jd-gui看一下java代码。一个md5加密,在线解一下即可。
这里了解一下什么时候jar包:jar包是eclipse下的压缩包,由多个class文件压缩而成的。
boomshakalaka-3
飞机大战游戏。
jeb反编译后找到入口类:多次调用了a类中的d方法。
看看a类d方法功能:其中getSharedPreferences()是关键,返回一个SharedPreference对象,它会在/data/data/com.example.plane/下生成一个xml文件,以键值对的形式存储向其中输入的数据。
adb看一下:
但程序提示的打到最高分,java层没有任何与分数相关的地方,进入so中找到分数的地方:分析下且从DATA可以知道,这里就是根据我们打的分数不断向xml文件中添加写入不同的字符串。
而要写入字符串字符串的开始和结尾是确定了的,只是中间的字符串会因为分数不同而不通过,这个自己打游戏然后查看数据或者直接ida中分析都可以知道。
注意的地方:要指定的分数才会写入指定字符串,所以如果打游戏测试的话要算着打😂。
测试一波后,写入顺序就是给出的,把开始结尾确定好后向中间填充好base64解码得到flag。
MGN0ZntDMGNvUzJkX0FuRHJvMWRfRzBtRV9Zb1VfS24wdz99
android1
app进行了梆梆加固,开始准备环境安装dump dex,准备完开始安装app发现报错。
后面才发现了是因为app没有签名,签上名后还要注意:
安装时带上-t选项。原因:
Android Studio 3.0会在debug apk的manifest
文件application
标签里自动添加 android:testOnly="true"
属性。
成功安装程序,打开提示资源文件,进而从values的string.xml中找到flag。
flag{1FF9B2CCB90A2D943DBAA072DF0A279C}
Android2.0
简单考了一下so层。
flag{sosorryla}
Illusion
关键就在so层的一个加密函数,开始直接在Exports找到名称为CheckFlag的函数,为了熟悉一下so层的调试,在这个函数下了断点但每次都不断不下来。。
然后开始找原因,后面发现这个假的函数,真正是动态加载的。
进入正确的函数开始分析,函数的伪代码明显是有点问题的,看arm汇编就好了。
其中关键就是sub_CCB09028这个函数:
分析了一下没看出所以然,但这个是一位一位加密的,所以可以直接穷举的。
写ida-python,但这个穷举最后一位有2个字符都可,空格和‘}’,还好是最后一位,那肯定是字符}了。
1 | enc = "Ku@'G_V9v(yGS" |
写完仔细分析了下那个函数,其实该函数和后面对函数返回值的处理就是求模(mod)的操作,但没有使用/与mod,可以学习一下。这从最后的操作看其实很明显。
APK逆向-2
首先安装app失败。。
反编译后首先看了一下AndroidManifest.xml,发现空的。。然后看了app的主要逻辑和类,很乱和杂吧,也不清楚要做什么。。
然后我为了看其中的一个资源文件对app进行了直接解压,这个倒是没看出什么。
但在其中看到AndroidManifest.xml并不是空的,然后尝试使用apktool对apk解包,解包失败。
由此可以知道,应该是对AndroidManifest.xml文件做了手脚。
然后就是要知道安卓AndroidManifest.xml文件格式了,这是修改过的。
再看一个正常的:
通过修复后,重新压缩成压缩包,在xml文件中看到flag相关:
ill-intentions
在MainActivity中注册了一个广播接收器:
根据在设置在屏幕上的文本来看Send_to_Activity,就是根据接受到的不同广播消息执行不同的函数。
然后看了一下三个不同的函数,其执行逻辑其实是一样的,把3个指定字符串经过sha224与base64及替换加密后再经过so层函数的操作,最后把结果以广播形式发出,但这里并不是发送给Send_to_Activity。
so层的加密操作一个异或,能想到运算结果就是我们要的flag。
由于只有三种结果,用ida-python模拟了程序的运算逻辑,在IsThisTheRealOne活动得到flag。这里开始一直出错得不到结果,后面调试发现是getClass().getName()得到的类名我没有把整个包加上。。
1 | from hashlib import sha224 |
另外,由于app中是多个活动,开始我是想直接用adb命令来启动每个活动,通过调试看输出是什么,但都启动失败,什么权限禁止。通过计算得到flag后,通过搜集发现是要对活动没有指定属性android:exported=”true”,
android.intent.action.MAIN:决定应用的入口Activity,也就是我们启动应用时首先显示哪一个Activity。
android.intent.category.LAUNCHER:表示activity应该被列入系统的启动器(launcher)(允许用户启动它)。Launcher是安卓系统中的桌面启动器,是桌面UI的统称。
指定后通过adb命令启动成功,可直接通过调试查看生成的字符串,我尝试修改smali来插入打印Log的语句,然后同伙查看日志同样得到flag。
最后查看日志:
d3ctf
No Name
找到入口活动,将输入传入FlagChecker类的checkFlag函数,然后又是实现接口中的check函数,最后调用,感觉有点套娃,,实质就是调用native层的check函数。。
但是注意清单文件中application中的android:name属性:这里指定了app启动时关联的一个application,这个类的作用就是做一些初始化,放一些全局变量和程序上下文相关的东西,默认是android.app.Application。
来到app启动时指定的这个类:NoNameApp。比较简单的代码,就是得到资源文件data.enc然后获取一个aeskey对其进行aes解密,且这里指定的解密得到的文件和存放路径。然后进行dex加载并把FlagChecker中mFlagChecker的实例进行了替换,也就是把上面我们分析出的native层那个check函数进行了替换。
所以可以知道app真正检查的逻辑在解密出的jar文件中,现在的目的就是得到这个jar,首先想到的就是直接在程序运行后在指定的路径下去找。
虽然app在加载完解密后对解密出的文件进行了删除,但是可以将app的smali修改,删除file.delete();后对其重新打包然后签名。
这里签名完后安装遇到了问题,一个是之前知道的app有android:testOnly=”true”,解决办法:1.安装时加上-t选项。2.在清单文件中删除该属性。
第二个问题:
搜索到这篇文章 https://testerhome.com/topics/18463
如果 AndroidManifest.xml 中未设置 extractNativeLibs=true,使用 apktool 反编译时有可能导致 extractNativeLibs 被设置为 false。
至于extractNativeLibs属性,开启的话就是把apk中的so解压缩提取到本地存取一份,关闭的话不在本地存储一份,在运行时直接调用app中的so。
如果关闭的话,且我们apk是未对齐或so文件是压缩状态的话将阻止我们安装apk,也就是上面的报的错。
然后导致我们默认开启extractNativeLibs的属性关闭的原因:使用apktool对app进行反编译和回编译。
最后把这个属性改为true,回编译签名后,运行app,在data/data/包名/下得到解密出的jar文件,简单的异或。。
最后说一下,在AK中反编译apk遇到com.googlecode.d2j.DexException: not support version的问题。
这个是dex2jar版本与dex版本不兼容主动抛出的异常,从报错找到dex-reader-2.1-20190905-lanchon.jar文件中的DexFileReader函数,看源码。
解决办法,更换最新的dex2jar版本就好了。https://www.jianshu.com/p/55bf5f688e9a