iOS移动命令渗透测试备忘录

iOS移动命令渗透测试备忘录

本文转自 周边 并作补充

注意:iOS应用程序与Android应用程序具有不同的环境,此处的某些命令仅适用于MacOS。

**iOS**指南

Install Brew, 打开 terminal (Finder -> Application -> Utilities -> Terminal) 键入命令 :
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Install XCode, 打开 terminal 并运行命令 :
$ xcode-select --install
或者您可以手动下载 Apple Website

将您的Apple ID注册为Apple Developer Account。您无需为此部分的Apple Developer Program付费,只需将您的帐户注册到Developer中,因为我们只需要获得用于在XCode或其他工具上签名ipa文件的证书即可。

通过USB的(iPROXY)

http://iphonedevwiki.net/index.php/SSH_Over_USB

从应用商店或iPhone/iPad设备下载.ipa文件

需要越狱

Clutch

Clutch

Frida Script

Frida-ios-dump command: $ iproxy 2222 22 $ ./dump.py BundleID

iOS二进制分析

ios-Analysis

Download : ios-analysis

安装 :

1
2
3
$ git clone https://github.com/IAIK/ios-analysis
$ cd ios-analysis
$ git submodule update --init --recursive

如果您遇到这样的错误 : error: RPC failed; curl 56 LibreSSL SSL_read: SSL_ERROR_SYSCALL, errno 60

运行这个命令: git config http.postBuffer 524288000

使用Windows登录并安装.ipa

  • 下载altsigner
  • 安装最新的iTunes(注意:使用二进制文件安装,请勿从Microsoft Store安装)
  • 打开Itunes并选择设备以复制设备的UDID
  • 打开altsigner.exe,填写您的[电子邮件,密码,UDID,.ipa文件路径]

使用我们的配置文件签名IPA文件

该工具非常有用:) iOS App Signer基于GUI。要生成我们的配置文件,您可以在安装应用程序时在XCODE上进行配置。

疑难问题

如果您收到这样的错误消息 If you have previously trusted your certificate using Keychain, please set the Trust setting back to the system default

不要惊慌,这样做:

  1. 进入Xcode并从“首选项”中删除您的帐户
  2. 去 ~/Library/MobileDevice/Provisioning 在finder中配置文件并删除其中的文件
  3. 进入钥匙串并删除所有提及Mac Developer,iOS Developer等的个人证书等
  4. 将您的帐户重新添加到Xcode中,然后选择吊销现有证书(如果无法吊销,请保留该证书)
  5. 转到xcode,然后尝试将虚拟应用程序安装到设备中。此步骤将触发苹果生成新的我们的证书。
  6. 然后,打开iOS App Signer

记一次重签名错误

记一次重签名错误

本文转自 听月 并作补充

什么问题?

最近打开Cydia就闪退,我就重新去下载了uncOver,打算重新越狱一下,但是在重签名的时候一直失败,提示”No codesigning certificates found”的错误。

image

什么原因导致?

由于我之前使用iOS App Signer这个MAC软件一直没有问题,今天遇到这个问题,我就想去github把源码克隆到本地,然后调试一下,看看具体是什么原因导致。
打开源码直接搜索No codesigning发现,这个错误是一个Alert弹框,和我们遇到的弹框信息一致。

image

最终调试发现,在程序调用/usr/bin/security find-identity -v -p codesigning命令时由于返回值为空,才触发错误弹框。

image

我把命令拷贝到终端执行,发现返回有效的identity的数量是0:

1
2
$ /usr/bin/security find-identity -v -p codesigning
0 valid identities found

此处的命令的意思是:

1
2
3
4
5
6
7
8
9
10
11
12
/usr/bin/security:
A simple command line interface which lets you administer keychains,
manipulate keys and certificates, and do just about anything the
Security framework is capable of from the command line.
(是一个管理钥匙串、keys和证书的命令行接口)

find-identify:
查找证书和私钥key的命令

-v: 只显示有效的证书,默认显示所有的证书

-p: 指定策略

具体的命令行的文档查看一下:

1
2
3
4
5
6
7
8
9
10
11
$ /usr/bin/security find-identity --help
find-identity: illegal option -- -
Usage: find-identity [-p policy] [-s string] [-v] [keychain...]
-p Specify policy to evaluate (multiple -p options are allowed)
Supported policies: basic, ssl-client, ssl-server, smime, eap,
ipsec, ichat, codesigning, sys-default, sys-kerberos-kdc, macappstore, appleID
-s Specify optional policy-specific string (e.g. DNS hostname for SSL,
or RFC822 email address for S/MIME)
-v Show valid identities only (default is to show all identities)
If no keychains are specified to search, the default search list is used.
Find an identity (certificate + private key).

怎么解决?

由于使用/usr/bin/security find-identity -v -p codesigning命令无法查询本地有效的重签名证书,我首先想到的就是更换查询策略,根据命令行文档可以看出,策略有很多种,包括basic, ssl-client ssl-server, smime, eap,ipsec, ichat, codesigning, sys-default, sys-kerberos-kdc, macappstore, appleID。经过尝试,发现策略改成appleID的时候是可以查询到有效证书的。但是这两个命令是有区别的。具体的区别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
poet@poetdeMacBook-Pro ~ % /usr/bin/security find-identity -v -p codesigning
1) C766972EC48052D14E3A2D715F03960E10B4XXXX "Apple Development: Zhang San (MGRQJNV411)"
2) 8EA5E415105CEE96F75EAFA443B6AC992FC9CCCC "Apple Development: Li Si (SB6BFS5U22)"
3) AEF5450BA48F61EE1B5A718B7563D855E306DDDD "iPhone Developer: Zhang San (MGRQJNV433)"
3 valid identities found
poet@poetdeMacBook-Pro ~ % /usr/bin/security find-identity -v -p appleID
1.2.840.113635.78.1.30
1) F87912EA9F939D6F044F1B86A165C5D8AC8CAAAA "com.apple.idms.appleid.prd.5675685073322f516b6a565a55775471374c72424b671234" (CSSMERR_TP_CERT_EXPIRED)
2) 8751799A43C85FEF3914DE664E2A05251F40BBBB "Mac Developer: Lao Wang (H2C8DTYK00)" (CSSMERR_TP_CERT_EXPIRED)
3) C766972EC48052D14E3A2D715F03960E10B4CCCC "Apple Development: Zhang San (MGRQJNV411)" (Missing required extension)
4) 8EA5E415105CEE96F75EAFA443B6AC992FC9DDDD "Apple Development: Li Si (SB6BFS5U22)" (Missing required extension)
5) AEF5450BA48F61EE1B5A718B7563D855E306EEEE "iPhone Developer: Zhang San (MGRQJNV433)" (Missing required extension)
5 valid identities found

经过上面的对比,发现策略是codesigning的时候查询出来的是所有有效的重签名的证书,不包含其他的证书(比如:苹果的idms官方证书、MAC电脑的证书等),但是策略是appleID的时候查询出来的证书一定包含策略codesigning查询出来的证书。所以,当使用策略codesigning无法查询出证书情况的时候,可以考虑把策略换成appleID来查询。所以解决办法就是将codesigning策略换成appleID的策略。

提交Pull Request

解决这个问题之后,我去github仓库的issue里面看,发现很多人也有类似的问题,我就自己尝试提交了一个Pull Request到作者的仓库,虽然作者很久没有更新了,但是如果后续还有人碰到此问题可以通过类似的办法解决。如何提交Pull Request,我是参考github上的一个帖子,具体步骤如下:

如何在别人的开源项目中提交自己的Pull Request ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.先在本地创建一个空文件夹,里面准备放克隆过来的代码. --> 我在本地Downloads文件夹下创建了一个名为 gitMessagekit 的文件夹.
2.在"终端"中通过cd命令进入到gitMessagekit文件夹下(将"自己电脑的用户名"换成你自己的电脑的用户名). --> cd /Users/自己电脑的用户名/3.Downloads/gitMessagekit
3.在"终端"输入克隆命令 git clone 开源项目源代码的url. --> git clone https://github.com/MessageKit/MessageKit.git
4.进入到克隆所在的文件夹. --> cd /Users/自己电脑的用户名/Downloads/gitMessagekit/MessageKit
5.用查看命令查看一下开源项目都有多少个分支. --> git branch -a
6.找到自己要切换的分支,准备切换分支,在这里我要切换到3.0.0-beta分支. --> git checkout remotes/origin/3.0.0-beta
7.基于远程分支新建本地分支(3.0.0-beta),2条命令. --> git branch 3.0.0-beta git checkout 3.0.0-beta
8.打开/Users/自己电脑的用户名/Downloads/gitMessagekit/MessageKit该路径下的代码,对代码进行修改.
9.添加修改. --> git add 你修改的文件
10.提交修改. --> git commit -m "fix 某某问题"
11.去自己的git仓库,准备fork一下开源项目MessageKit到自己的仓库(repository)中.
12.即将关联自己fork过的项目. --> git remote add upstream git@github.com:xxjldh/MessageKit.git
13.推送本地的分支(3.0.0-beta)到自己fork过的仓库中,2条命令. --> git fetch origin git merge origin/3.0.0-beta
14.在即将提交时出现这样一个错误,git@github.com: Permission denied (publickey).解决办法(https://www.jianshu.com/p/f22d02c7d943)
15.最后push自己的分支到自己fork过的仓库中. --> git push upstream 3.0.0-beta
16.在开源项目https://github.com/MessageKit/MessageKit.git的pull request中添加自己刚修改过的文件, 点"comment pull request"即可.

下载链接

下载App重签名工具

参考链接

Mac Security工具使用总结
security命令
如何给开源项目贡献代码

Android加固之后Apk重签名

Android加固之后Apk重签名

本文转自 ganzhijie 并作补充

Java:jarsigner java自带的jar签名,也就是我们Android打包的v1签名,签名方案只能v1。
Android:apksigner Android特有的签名,也就是打包的v2签名,支持多种签名方案(v1~v4)。

需要注意的是,因为 apksigner 是Google在 Android 7.0 Nougat 推出的,所以我们的版本号的选择需要 >= 24.0.3 ,否则只能选择 jarsigner 方式打v1包。

如果您使用的是 apksigner,则必须在为 APK 文件签名之前使用 zipalign。如果您在使用 apksigner 为APK 签名之后对 APK 做出了进一步更改,签名便会失效。
如果您使用的是 jarsigner,则必须在为 APK 文件签名之后使用 zipalign。

下面是检查apk是否对齐的方法,打开终端输入:
zipalign -c -v 4 apk路径
例如:
zipalign -c -v 4 /android/tools/jiagu/apk/jiagu.apk

接下来就是利用终端,实现apk对齐操作,在打开的终端输入:
zipalign -v 4 「需要对齐操作的apk地址」 「对齐之后生成的地址」
例如:
zipalign -v 4 /android/tools/jiagu/apk/jiagu.apk /android/tools/jiagu/apk/zipaligned.apk
出现 “Verification succcessful” 为对齐成功。

下面是检查是否签名的终端语句,在打开的终端输入:

apksigner verify -v 检查的apk路径
例如:
apksigner verify -v /android/tools/jiagu/apk/zipaligned.apk

接下来继续在终端输入「apksigner」重签名语句

apksigner sign -verbose –ks 「jks文件路径」 –v1-signing-enabled (「true/false」v1打包开启/关闭) –v2-signing-enabled (「true/false」v2打包开启/关闭) -ks-key-alias (jks别名 key-alias) –ks-pass pass: (jks密码,key store password) –key-pass pass:(key 密码,key password) –out 「生成的apk路径,重签名后的」 「对齐之后的apk路径」
例如:
apksigner sign -verbose –ks /android/tools/jiagu/apk/ks/abc.keystore –v1-signing-enabled true –v2-signing-enabled true –ks-key-alias abc.keystore –ks-pass pass:123456 –key-pass pass:123456 –out /android/tools/jiagu/apk/signed.apk /android/tools/jiagu/apk/zipaligned.apk
终端运行结果如下:出现 Signed 则为重签名成功。

总结:apksigner签名流程。

加固并下载 ===> 检查是否对齐 ===> 对齐「zipalign」 ===> 检查是否对齐 ===> 检查apk是否签名 ===> 「apksigner」重签名 ===> 是否重签名成功

灰产-如何破解请求验签(下)

灰产-如何破解请求验签(下)

image

写在前面

再来个前情提要:灰产-如何破解请求验签(上)

以下都是情节模拟,并非具体情况,请细加甄别

阿哈!Showmak……啊不,是新的 Sign 替换后不久就,两周左右就又被破解了呢——

“我们新的加签,可是从多个随机数池取值生成的 sign1,然后把sign1和多个参数混合在一起按时间戳不同取模排列生成的sign2,怎么可能……”

啊,虽然很想吐槽这反派一般被反击了感叹自己部署被打乱了的感觉,但是来不及为已经逝去的 sign2 感到悲伤了,接下来赶到战场的是……

是我这个苦力!

我打灰产?真的假的?要上吗?

image

总而言之

总而言之,在解释了旧 Sign 是怎么泄露的后,调查新 Sign 是怎么泄露的这个任务又又又落到我这里了= =,已经不想吐槽了

实现漏洞

“怎么实现的……额,实际上我们等于是接了一个生成库,你可以理解为类似SDK,里面有各种 Sign1 和 Sign2 的实现方式,什么移位啊取模啊都多久前的了,我都快忘了,你想看的话文档给你你可以看一下”

“有没有可能是这个实现的模块泄露了或者可以被调用啥的?”

“我觉得没有吧,感觉也有可能是 Hook 了,但就不知道是怎么弄的”

然后直到最后我除了在研发显示屏上看到了一眼实现文档,我再也没看到那实现方式一眼了

image

摸索开始了——

首先通过请求日志我发现,灰产用到的 sign1 有一批量重复的(说好的随机池随机取呢),但是生成的 sign2 是不重复的(sign1 只作为一个参数参与生成 sign2 的计算),我们并没有对 sign1 进行校验,所以导致这个 sign1 可以在请求中被替换,重复利用。

顺带一提的是,在其他应用里很早就出现这个情况了,不是我的话都还没发现……

image

定向Hook

sign1 的问题因为没有校验可以重复利用解决了,那 sign2 呢?

前端没有找到泄露 sign2 计算点,因为前端都可能没用新的 Sign;

应用端源码没看到 sign2 的计算方式,因为做了较好的混淆;

……

那选择就只有一个了!(折断拐杖)

对它使用 Hook 吧!

image

早在 MobSF移动安全测试框架|自动化静动态app分析框架 的使用中,我就接触了 Frida,why?因为一开始装不上,并且因为种种原因(MobSF 自带证书冲突等)执行动态分析的效果很差,虽然后来还是能试出来。

扯远了,回到现在,利用 Frida 对应用进行 Hook,我是在 Genymotion 上做的,当然,由于应用配置的关系,你可能需要先:

这里就要说了,为什么涉及到 Brida?

因为一开始是想用 Brida 直接 Hook 调用函数,传到 BurpSuite 直接修改请求,但是后来发现你根本不知道是哪个函数计算 sign2 ,别说改了,Hook 都不知道 Hook 谁,怎么办?

image

java.security.MessageDigest

还记得旧 Sign 最后的加密算法吗?SHA1-Hash

再看看新 Sign,尤其是 sign2 的样式,分明也是 SHA1!

嘛,灵光一现:不如利用 js 跟踪“java.security.MessageDigest”的调用,并输出加密入参与之后的值,js 参考:

image

入参值反解出来是一堆字节码,需要用 java 重新转一下回去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class exchange {
public static void main(String[] args) {
byte[] b = { 108, ……, 102,
105, ……, 105,
111, ……, 107,
45, ……, 61,
50, ……, 52,
32, ……, 117,
115, ……, 118,
105, ……, 110,
118, ……, 73,
70, ……, 78,
74, ……, 85,
85, ……, 108,
107, ……, 107,
45, ……, 110,
61, ……, 48,
67, ……, 45,
116, ……, 112,
61, ……, 104,
73, ……, 57 };
String s = new String(b);
System.out.println(s);
}
}



Output:
……data={"userId":"……"}……sign1=GORNJPBOGXJXZQRRKYKYZNGZXZSYOUUSCMCM……timestamp=1714030269543……

sign1&timestamp

ok,入参也出来了,现在只剩一个问题:sign1 在入参中的位置。

其实到这里直接去遍历都可以,但是我还是采集了几份 sign1 在 sign2 生成位置与 timestamp 的关系进行比对,然后发现比对了个寂寞——差一位数的时间戳里 sign1 的插入位置可以相同的 = =:

image

所以不整了,收工!

image

写在后面

下篇也写完了,感觉好像也就这样,后续把加固啥的弄上议程吧,不然好像还有些硬编码问题也是头疼,但还要验证一下效果。

沉默,然后……事已至此,先吃(pao)饭(lu)吧

image

参考引用

灰产-如何破解请求验签(上)

MobSF移动安全测试框架|自动化静动态app分析框架

Genymotion_A11_libhoudini

How to install Xposed/EdXposed/LSPosed + Magisk with Genymotion Desktop?

brida从0配置

安卓逆向 - Frida Hook(抓包实践)

Frida java层自吐加密算法

灰产-如何破解请求验签(上)

灰产-如何破解请求验签(上)

image

前情提要

先来个前情提要:灰产-如何对APK修改后重签

在写那篇文章的我,是否会想到我竟然还有有关“灰产”的下文来写?而且就标题来说,还有分上下两篇!所以这4个月不到的时间我又经历了什么呢?

image

写在前面

以下都是情节模拟,并非具体情况,请细加甄别

让我们把时间拨回到年后。

年后回来一阵子,接到业务风控同学的反馈,我们有用户发现自己的账户被盗了,自己购买了不是自己的商品(不产生金额)并对这个商品统一匿名评价……

我第一反应卧槽哥们我们应用不是做了端校验,不同设备必须验证码登录,怎么连手机一起丢了才有后面能说漏洞导致账密泄漏的事?

image

后面通过网关日志、应用日志、接口日志等排查,发现这个用户的登录凭证(token)有从境外服务器请求服务的情况,可以基本确认是 token 的泄漏;

再和用户沟通以及进一步的深入排查,我们发现 token 的泄漏源于用户被钓鱼,这里细节不表;被社工得到的 token 会先从 ip 1 被收集并转发给 ip 2 进行购买请求与评论……

这里涉及到一个问题,我们的请求在各端做了验签,Sign 参数会根据不同端走不同的计算方式,想要更改 token 并发起购买请求,不重新计算 Sign是不可能通过校验的,只有两种可能:

  • 我们的 Sign 计算方式被破解
  • 我们的 Sign 计算方式可被任意调用

总而言之

总而言之,这个任务又落到我这里了= =

会议讨论对齐的时候,研发一致认为是在 H5 端的 Sign 计算方式太简单了,如何计算已经被破解,应该换上现在端上更安全的 Sign 计算就可以了!

直接破解一个计算方法理论上是不太容易的,我更在意的是是否是哪个地方泄漏了 Sign 的计算方法,或者在什么地方的计算代码可以被调用,直接入参入值然后得到结果。

但这里就要提到另一个事情了,研发不告诉你!

他们只表述这个好像比较简单就能破,但问他们怎么破的,不知道;问他们在哪里可能泄漏了,不知道:

“大概是从前端 JS 泄漏的?你看我们这里都做了混淆了,你看是不是就是这个位置,sign 关键词都命中了……”“可是为啥我调试/下断点没反应啊?”

image

后来嘛,在把钓鱼的口子堵了一下后,换上“更安全”的 Sign 计算方式后,喜闻乐见的钓鱼的口子也被绕过了,“更安全”的 Sign 计算也被破解了……

然后呢?

遭殃的还是我好吧,本来他们都认为可行的解决办法没用的时候,我就不得不花时间和精力来看看不在我 OKR 上的问题了

image

泄漏发现

算研发很配合安全工作,虽然告诉了我一个错误的泄漏位置,但起码提醒了我可能是 JS 就已经泄露了,没必要一来就去逆向找源码。

开始的思路还是去利用前端给到的 JS 复用:Burp Suite作为代理,复用目标站点的js代码处理指定的内容

后来发现 JSFlash 必须要能够指定调用代码函数才行,前端 JS 混淆了,指向不出来,直接分析不了

然后我就在前端登录页 Source - js 找到了 Sign 计算点,并且下断点步进调试看到了所有 Sign 的计算入参:

image

image

它端验证

在前端 JS 调试的过程中能看到有一个 secret 参数是不会暴露在 request 里的,其他端的如何获得?

Jadx 逆向反编译一下应用端,以 SIGN 为关键词,在源代码中发现了硬编码在其他端使用的 secret,以及使用的 Sign 最后加签算法:

image

image

写在后面

上篇就到这里,写的好像很简单,解过程也没遇到什么麻烦,但是还得说和大伙配合的还是不好,我还是认为不管是锻炼或者检验个人能力也好,工作任务忙不想接手深入这个事情也好,如果是以把事情完成的结论来说,相互配合是很重要的。

以旧 Sign 为例,大家说的好像每个人都知道这个是怎么生成的,但就是没人和你说,告诉的还是错误的位置,即使真的可能知道是怎么生成的不知道是在哪里泄露的,那信息汇通还是更重要的。研发完全可以告诉你“我猜是在前端 JS 泄漏的,位置是在这里,他的调用是这样的……”给你演示出来,如果真的在前端直接 Debug 下断点走一遍,就会知道一开始的位置告诉错了,并且一下就把泄漏点找出来了。

不会浪费双方的时间的。

image

参考引用

灰产-如何对APK修改后重签

Burp Suite作为代理,复用目标站点的js代码处理指定的内容

JS逆向-数据包解签名实战案例

JS逆向-数据包解签名实战案例

本文转自ichi9o 并作补充

0x00 前言

通常情况下,数据包中的签名字段会包含sign字符串,如signappsign等等,然后根据字段在JS代码中寻找签名的过程,并进行分析。
以下内容为一个真实案例,撒,哈气灭路!

0x01 获取被签名数据

通过数据包可知该网站的签名字段是 sign

image

在浏览器中的源代码搜索字段sign,找到签名代码
首当其冲的就是app.*.js样式的文件,在app.72b81572.js文件中发现了可疑点

image

分析 function k(e) 可知,是要对 r 参数进行签名的,r 又跟e、t、a、n 这几个参数相关,因此要搞清楚这几个参数的值是怎么来的,所有就在函数开始的第一行就下断点来跟进。

1
2
3
4
5
6
7
8
9
10
11
12
function k(e) {
const t = g()
, n = m();
let a;
a = e ? b(e) : {};
const r = e ? `${c.a.stringify(a.hasParams)}&${t}&time=${n}` : `&${t}&time=${n}`;
return e = Object.assign({}, a.params, {
sign: h()(decodeURIComponent(r)),
time: n
}),
e
}

首先是 t,跟进 g 函数,执行到 return 就可以发现 t = 年 + “5616” + 月 + 日

image

然后就是 n,这个 e 的值呢,根据跟进可知是 cookie 里面的,不过这个 e 在本案例的作用不大,最后通过 return 就可以知道返回的值就是:当前时间戳- e

image

接下来就是 a,根据返回的结果可知 a 的值和 e 相同,通过分析数据包可知,e的内容即请求参数(GET)或请求体(POST)

image

然后 r 的值就出来了,也就是签名的明文

image

综上所述
r = 请求内容 + & + t + & + n(这里的请求内容需要注意的是,POST Data是Json格式的要转换为:key1=value1&key1=value1…)
其中
t = 年 + “5616” + 月 + 日
n = 当前时间戳(本案例可不减e也可成功)

0x02 分析加密算法

将断点打在签名代码行,跟进代码的执行,发现加密的类名是 Md5,所有可以计算 r 的 md5 值,与代码执行的 sign 结果进行比较。

image

可以看到,该网站使用的是 md5 计算的 sign

image

0x03 编写 mitmproxy 脚本

mitmproxy 脚本是实时加载的,因此 mitmproxy 只要带着脚本运行,就可以边调试了。
mitmproxy 使用命令

1
mitmdump -p 777 -s .\mitmscript.py --flow-detail 0

image

关于脚本的编写这里给出以下 mitmproxy 的一些常用的属性和方法ctx:

  • ctx.log.info(): 用于在 mitmproxy 的日志中输出信息。
  • ctx.options: 用于访问 mitmproxy 的配置选项,您可以在配置文件中定义这些选项。
  • ctx.master: mitmproxy 的 Master 对象,提供了一些控制代理行为的方法。
  • ctx.proxy: mitmproxy 的 ProxyConfig 对象,提供了有关代理配置的信息。
  • ctx.client: mitmproxy 的 ClientConnection 对象,表示客户端连接的相关信息。
  • ctx.server: mitmproxy 的 ServerConnection 对象,表示服务器连接的相关信息。
  • ctx.protocol: mitmproxy 的 ProtocolHandler 对象,表示处理请求和响应的协议处理器。

在 mitmproxy 脚本中,可以使用以下一些回调函数来处理不同阶段的 flow 对象:

  • def request(flow: mitmproxy.http.HTTPFlow) -> None: 当 mitmproxy 拦截到请求时调用此回调函数,可以获取和处理请求的各种信息。
  • def response(flow: mitmproxy.http.HTTPFlow) -> None: 当 mitmproxy 拦截到响应时调用此回调函数,可以获取和处理响应的各种信息。
  • def error(flow: mitmproxy.http.HTTPFlow) -> None: 当请求或响应出现错误时调用此回调函数,可以获取和处理错误信息。
  • def clientconnect(flow: mitmproxy.tcp.TCPFlow) -> None: 当客户端连接到 mitmproxy 时调用此回调函数,可以获取和处理客户端连接的相关信息。
  • def serverconnect(flow: mitmproxy.tcp.TCPFlow) -> None: 当 mitmproxy 连接到服务器时调用此回调函数,可以获取和处理服务器连接的相关信息。
  • flow.request.method: 获取请求的方法(GET、POST等)。
  • flow.request.scheme: 获取请求的协议(http 或 https)。
  • flow.request.host: 获取请求的主机名。
  • flow.request.port: 获取请求的端口号。
  • flow.request.path: 获取请求的路径部分。
  • flow.request.url: 获取完整的请求URL。
  • flow.request.headers: 获取请求的头部信息,是一个字典对象,可以通过键来访问特定的头部字段。
  • flow.request.cookies: 获取请求中的Cookie信息,是一个字典对象,可以通过键来访问特定的Cookie。
  • flow.request.query: 获取请求的查询参数,是一个字典对象,可以通过键来访问特定的查询参数。
  • flow.request.content: 获取请求的内容,如果请求是POST请求且带有内容,则可以通过该属性来访问请求的内容。
  • flow.request.text: 获取请求的内容,并以文本形式返回。
  • flow.request.urlencoded_form: 获取请求的URL编码表单数据,是一个字典对象,可以通过键来访问特定的表单字段。
  • flow.request.multipart_form: 获取请求的多部分表单数据,是一个列表对象,列表中的每个元素都是一个字典,表示一个表单字段。
  • flow.request.content: 获取请求的原始内容,以字节形式返回。

附上本案例的代码供参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from datetime import datetime
from mitmproxy import ctx
import hashlib
import random
import json
import time


class Modify:
def __init__(self):
self.plaintext = ''
self.new_sign = ''
current_datetime = datetime.now()
self.today = f"{current_datetime.year}5616{current_datetime.month:02d}{current_datetime.day:02d}"

def md5(self):
md5_hash = hashlib.md5()
md5_hash.update(self.plaintext.encode('utf-8'))
self.new_sign = md5_hash.hexdigest()

def request(self, flow):
if flow.request.method == "POST":
ctx.log.info(f'\n原POST请求体:{flow.request.text}')
data = json.loads(flow.request.get_text())
if data != '{}' and 'sign' in data:
del data['sign']
del data['time']
for k in data:
self.plaintext += f"{k}={str(data[k])}&"
timestamp = int(time.time() * 1000)
self.plaintext += f"{self.today}&time={timestamp}"
self.md5()

data["sign"] = self.new_sign
data["time"] = timestamp
flow.request.set_text(json.dumps(data).replace(' ', ''))
ctx.log.info(f'\n新POST请求体:{flow.request.text}')
self.plaintext = ''
else:
ctx.log.info('无参数,无需改签')
elif flow.request.method == "GET":
ctx.log.info(f'\n原GET请求体:{flow.request.query}')
query = flow.request.query
if query != '{}' and 'sign' in query:
del query['sign']
del query['time']
if query:
for k in query:
self.plaintext += f"{k}={str(query[k])}&"
else:
self.plaintext = "&"
timestamp = int(time.time()) * 1000
self.plaintext += f"{self.today}&time={timestamp}"
self.md5()

query["sign"] = self.new_sign
query["time"] = timestamp
ctx.log.info(f'\n新GET请求体:{flow.request.query}')
self.plaintext = ''
else:
ctx.log.info('无参数,无需改签')

@staticmethod
def response(flow):
if '签名校验失败!' in flow.response.text:
ctx.log.error(f'\n签名异常:\nurl => {flow.request.path}\n异常信息 => {flow.response.text}')


addons = [
Modify()
]

灰产-如何对APK修改后重签

灰产-如何对APK修改后重签

image

写在前面

前段时间没有事做的时候,组织上仿佛看穿了我很咸,突然让我去研究下灰产包,让我分析用了什么技术&做出了什么改动。

image

总而言之

虽然入职就听说了公司应用之前有被灰产“薅羊毛”的问题,但真让我弄我也没专门弄过啊,而且这部分之前是交给风控组去做的,这么久了整不来靠我嘛?而且我也想吐槽这灰产包你们又是怎么弄来的,还有两份不同技术实现的……

image

签名篡改

如果想对apk内部内容进行修改,从而达到实现某些功能或规避某些审查的目的,就一定要对签名进行重签,这一点上只需要和正版签名比对一下便能发现。如何检测使用了这些篡改应用的用户等不在这次的任务范围内,篡改功能点内容也不展开讨论,此处仅讨论绕过审查重签的技术。

image

MT APP签名检查及绕过

对灰产包简单的逆向后,我在第一个包很快地就发现了一个KillApplication.java

image

很显然,做篡改的兄弟还不够细,或者根本没懂相应的原理性知识,留下了如此明显的痕迹,并且在URL参数直接暴露了篡改方式:

ApkSignatureKillerEx

image

算是让我直接回忆起之前用过的MT管理器,好像酷安就能下到,不过我已经很久没用过了,不知道MT现在是自带这种签名方式还是作为外部插件使用的。

而MT APP签名检查及绕过在Android逆向-获取APP签名 一文中有详细表述,在此不做赘述(懒)

NP APP签名检查及绕过

那我们再来看下一个,啊,还不用我找,MobSF都给我翻出来了:

image

你是?

image

顺着FuckSign.java2863678687@qq.com 这两个信息(痕迹也是太明显了),我找到了另一种篡改方式:

NP-Manager

image

直接获取下来也很容易就复现了篡改与绕过:

image

写在后面

首先啊,风控安全真是和什么都斗争不断,除开apk包篡改,也还有模拟GPS定位、虚拟用户接码手机号等等,还好不是我直接负责:

image

然后,之前文章提到过外网可能是MobSF官方开的一个在线检测的平台,直接暴露了非常多的检测应用报告,无独有偶,我在做这次灰产分析的时候,在搜索引擎寻找FuckSign.java,发现了国内一个仿MobSF的在线检测平台,从搜索引擎记录来看,市场上很多其他应用也不堪灰产侵扰:

image

我在发现这个检测平台的时候,它给检测项部分“需要开通VIP”解锁,该说是刻意而为导致规避了部分问题还是啥呢……总之,我在几周后重新访问的时候,他已经是崩溃状态,嘛,不好评价:

image

最后,要做灰产黑产,就要从原理性把痕迹去除或转化,不能留太明显的技术痕迹,虽然可以说这些最后找到的都是开源项目,是卖刀者不是作恶人,但淹死的多是会水的,这里分享一篇文章:灰产,赚点快钱?

image

参考引用

ApkSignatureKillerEx

Android逆向-获取APP签名

NP-Manager

Firebase Installations API 密钥硬编码

灰产,赚点快钱?

Android逆向-获取APP签名

Android逆向-获取APP签名

本文转自Taardisaa 并作补充

很久以前开的blog,关于如何获取APP签名。不知道为啥要写这个了。

Android逆向-APP签名

生成JKS签名

Android studio 如何生成jks签名文件 - 简书 (jianshu.com)

打开AndroidStudio

1
Build-->Generate Signed APK-->APK

然后Key store path选择Create New

然后设置好存储路径,密码也设置一下(偷懒写个123456)

Key的别名就叫key,密码一样简单。

然后剩下的Certificate全填taardisCountry Code填11451。

反正创建成功后,就在选定路径下出现了jks密钥文件。

APK签名

将APK魔改,重新打包后,需要重新签名。

参考:Android之通过 apksigner 对 apk 进行 手动签名_恋恋西风的博客-CSDN博客

1
apksigner.bat sign --verbose --ks D:\Android\Keystore\taardis.jks --v1-signing-enabled false --v2-signing-enabled true --ks-key-alias key --ks-pass pass:123456 --key-pass pass:123456 --out D:\Android\Frida\gadget\bs.apk D:\Android\Frida\gadget\b.apk

成功后提示:

1
Signed

获取APK签名

首先APK解包:

1
apktool d <apk>

然后在 META-INF 文件夹拿到 CERT.RSA 文件。之后:

1
keytool -printcert -file CERT.RSA

不过Keytool似乎是Java的工具,不管了现在用不上。

JEB/JADX

这种反编译器也能直接看到APK的签名信息。

MT APP签名检查及绕过

L-JINBIN/ApkSignatureKillerEx: 新版MT去签及对抗 (github.com)

从“去除签名验证”说起 - 腾讯云开发者社区-腾讯云 (tencent.com)

过签名校验(2) – MT 的 IO 重定向实践 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

MT提供的签名绕过方式能够实现对API和APK方式的绕过。但是对于SVC的则无能为力。

1
2
3
4
String signatureExpected = "3bf8931788824c6a1f2c6f6ff80f6b21";
String signatureFromAPI = md5(signatureFromAPI());
String signatureFromAPK = md5(signatureFromAPK());
String signatureFromSVC = md5(signatureFromSVC());

API检测

PackageManager直接获得签名。

1
2
3
4
5
6
7
8
9
private byte[] signatureFromAPI() {
try {
@SuppressLint("PackageManagerGetSignatures")
PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
return info.signatures[0].toByteArray();
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}

APK检测

找到APP私有文件夹下的base.apk,然后得到签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private byte[] signatureFromAPK() {
try (ZipFile zipFile = new ZipFile(getPackageResourcePath())) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().matches("(META-INF/.*)\\.(RSA|DSA|EC)")) {
InputStream is = zipFile.getInputStream(entry);
CertificateFactory certFactory = CertificateFactory.getInstance("X509");
X509Certificate x509Cert = (X509Certificate) certFactory.generateCertificate(is);
return x509Cert.getEncoded();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

SVC检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private byte[] signatureFromSVC() {
try (ParcelFileDescriptor fd = ParcelFileDescriptor.adoptFd(openAt(getPackageResourcePath()));
ZipInputStream zis = new ZipInputStream(new FileInputStream(fd.getFileDescriptor()))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (entry.getName().matches("(META-INF/.*)\\.(RSA|DSA|EC)")) {
CertificateFactory certFactory = CertificateFactory.getInstance("X509");
X509Certificate x509Cert = (X509Certificate) certFactory.generateCertificate(zis);
return x509Cert.getEncoded();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

绕过

Java层的东西不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private static void killOpen(String packageName) {
try {
// Native层Hook
System.loadLibrary("SignatureKiller");
} catch (Throwable e) {
System.err.println("Load SignatureKiller library failed");
return;
}
// 读取/proc/self/maps读取APP路径
String apkPath = getApkPath(packageName);
if (apkPath == null) {
System.err.println("Get apk path failed");
return;
}
// 读取自身APK文件(私有目录下的base.apk)
File apkFile = new File(apkPath);
// 在APP私有目录下创建origin.apk文件
File repFile = new File(getDataFile(packageName), "origin.apk");
try (ZipFile zipFile = new ZipFile(apkFile)) {
// 将APK中的origin.apk给提取出来(origin.apk是MT去签是生成的,是初始没有被去签的APK)
String name = "assets/SignatureKiller/origin.apk";
ZipEntry entry = zipFile.getEntry(name);
if (entry == null) {
System.err.println("Entry not found: " + name);
return;
}
// 读取出来
if (!repFile.exists() || repFile.length() != entry.getSize()) {
try (InputStream is = zipFile.getInputStream(entry); OutputStream os = new FileOutputStream(repFile)) {
byte[] buf = new byte[102400];
int len;
while ((len = is.read(buf)) != -1) {
os.write(buf, 0, len);
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
// 传入底层so Hook
hookApkPath(apkFile.getAbsolutePath(), repFile.getAbsolutePath());
}

然后看Native层,实际上是XHook,用于替换libc函数。

1
mt_jni.c

实际上就做了个字符串替换,有意将原本要打开的APK替换成origin.apk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const char *apkPath__;
const char *repPath__;

int (*old_open)(const char *, int, mode_t);
static int openImpl(const char *pathname, int flags, mode_t mode) {
//XH_LOG_ERROR("open: %s", pathname);
if (strcmp(pathname, apkPath__) == 0){
//XH_LOG_ERROR("replace -> %s", repPath__);
return old_open(repPath__, flags, mode);
}
return old_open(pathname, flags, mode);
}

JNIEXPORT void JNICALL
Java_bin_mt_signature_KillerApplication_hookApkPath(JNIEnv *env, __attribute__((unused)) jclass clazz, jstring apkPath, jstring repPath) {
apkPath__ = (*env)->GetStringUTFChars(env, apkPath, 0);
repPath__ = (*env)->GetStringUTFChars(env, repPath, 0);

xhook_register(".*\\.so$", "openat64", openat64Impl, (void **) &old_openat64);
xhook_register(".*\\.so$", "openat", openatImpl, (void **) &old_openat);
xhook_register(".*\\.so$", "open64", open64Impl, (void **) &old_open64);
xhook_register(".*\\.so$", "open", openImpl, (void **) &old_open);

xhook_refresh(0);
}

通过Hook open函数,可以把基于APK读取的签名方式给绕过。

下面提供一个绕过基于PackageManager的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
private static void killPM(String packageName, String signatureData) {
// 构造一个假的签名
Signature fakeSignature = new Signature(Base64.decode(signatureData, Base64.DEFAULT));
Parcelable.Creator<PackageInfo> originalCreator = PackageInfo.CREATOR;
Parcelable.Creator<PackageInfo> creator = new Parcelable.Creator<PackageInfo>() {
@Override
public PackageInfo createFromParcel(Parcel source) {
PackageInfo packageInfo = originalCreator.createFromParcel(source);
if (packageInfo.packageName.equals(packageName)) { //
if (packageInfo.signatures != null && packageInfo.signatures.length > 0) {
packageInfo.signatures[0] = fakeSignature; // 将虚假的签名放入packageInfo,取代原来的
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (packageInfo.signingInfo != null) {
Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners();
if (signaturesArray != null && signaturesArray.length > 0) {
signaturesArray[0] = fakeSignature;
}
}
}
}
return packageInfo;
}

@Override
public PackageInfo[] newArray(int size) {
return originalCreator.newArray(size);
}
};
try {
// 用假的creator替换原来的PackageInfo.CREATOR
findField(PackageInfo.class, "CREATOR").set(null, creator);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// 解除某些Android系统API的使用限制?
HiddenApiBypass.addHiddenApiExemptions("Landroid/os/Parcel;", "Landroid/content/pm", "Landroid/app");
}
try {
// 清空签名缓存
Object cache = findField(PackageManager.class, "sPackageInfoCache").get(null);
// noinspection ConstantConditions
cache.getClass().getMethod("clear").invoke(cache);
} catch (Throwable ignored) {
}
try {
// 清空签名缓存
Map<?, ?> mCreators = (Map<?, ?>) findField(Parcel.class, "mCreators").get(null);
// noinspection ConstantConditions
mCreators.clear();
} catch (Throwable ignored) {
}
try {
// 清空签名缓存
Map<?, ?> sPairedCreators = (Map<?, ?>) findField(Parcel.class, "sPairedCreators").get(null);
// noinspection ConstantConditions
sPairedCreators.clear();
} catch (Throwable ignored) {
}
}

参考

Java Keytool 介绍 - 且行且码 - 博客园 (cnblogs.com)

获取Android应用签名的几种方式 - 简书 (jianshu.com)

Android studio 如何生成jks签名文件 - 简书 (jianshu.com)

apktool重新打包时报错_apktool 忽略错误信息__y4nnl2的博客-CSDN博客

Android之通过 apksigner 对 apk 进行 手动签名_恋恋西风的博客-CSDN博客

apksigner | Android 开发者 | Android Developers (google.cn)