Automne's Shadow.

BSidesSF CTF 2019 WeatherCompanion WriteUp

2019/03/10 Share

Android

参考链接:https://aadityapurani.com/2019/03/07/bsidessf-ctf-2019-mobile-track/#weather

感谢大佬分享。

拿到apk,在JEB中打开,找到MainActivity,先看一下全局的逻辑。

automne

在上图中,v6_1这个StringBuilder()拼接成了一个json字符串,然后和上面的v2、v3一起走到下面的代码段里,最终组成一个URL地址。

1
2
3
URL v1_3 = ((g)v1_1).a(c.a(v2, v3).a(), TimeUnit.DAYS, new com.b.c.c.g$a[]{com.b.c.c.g$a.a(com.b.b.b.i.a(new ByteArrayInputStream(v6_1.toString().getBytes())))});

String v4 = v1_3.toString();

接上面的代码继续往下看,使用v1_3这个URL去打开一个网络连接,然后在v2_1中取得响应内容【v1_4.getInputStream()】并输出,最终函数a()的返回值v0为网络请求的响应内容。

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
private String a(){

......省略此处代码......

try{
......省略此处代码......

URLConnection v1_4 = v1_3.openConnection();
v1_4.connect();
v1_4.getContentLength();
BufferedReader v2_1 = new BufferedReader(new InputStreamReader(v1_4.getInputStream()));
StringBuffer v1_5 = new StringBuffer();
while(true) {
v0 = v2_1.readLine();
if(v0 == null) {
break;
}

v1_5.append(v0 + "\n");
}

v0 = v1_5.toString();
}
catch(IOException v1_2) {
v1_2.printStackTrace();
}

if(v4 != null) {
Log.d("Background url", "Background URL in progress");
}

return v0;
}

然后从响应里取数据,并在应用上显示

automne

大体逻辑分析清楚了,接下来要关注的就是上面提到的v6_1这个json字符串里到底是什么内容。
用到的函数在Utils()里

automne

可以看到,分别是Base64解码,int转char,BigInteger转Hex以及三个native层的函数。

为了方便,使用frida框架去hook对应的函数,上传frida server到root的手机设备里,
运行

setenforce 0

关闭SELinux机制,然后运行frida server,并编写hook脚本,这里直接使用参考链接里大佬的脚本。

代码的第一部分使用了android hook时绕过SSL Pinning的一种方法,实际测试时发现删除了也可以hook。相关绕过方法的链接

第二部分是hook java String类中的toString()方法,完美展示了frida万物皆可hook的强大功能。为什么hook这个函数是因为前面代码里有个将URL toString()的操作,把通过各种编码加密处理后的内容呈现出来。

第三部分则是将hook到的toString()打印出来。

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
Java.perform(function(){

// Step - 1 Bypass SSL Unpinning

var array_list = Java.use("java.util.ArrayList");
var ApiClient = Java.use('com.android.org.conscrypt.TrustManagerImpl');

ApiClient.checkTrustedRecursive.implementation = function(a1, a2, a3, a4, a5, a6) {
var k = array_list.$new();
return k;
}


// Step - 2 Hook toString

console.log("Hooking Java");

const StringBuilder = Java.use('java.lang.StringBuilder');

StringBuilder.$init.overload('java.lang.String').implementation = function (arg) {
var partial = "";
var result = this.$init(arg);
console.log('new StringBuilder("' + result + '");')
return result;
}

console.log("Hooking new StringBuilder(java.lang.String)");


// Step - 3 Monitor toString

StringBuilder.toString.implementation = function () {
var result = this.toString();
console.log('StringBuilder.toString(); => ' + result)
return result;
}

console.log("Hooking StringBuilder.toString() hooked");

}, 0);

将上述脚本保存为hook.js

在Android 8.0的Nexus 6P上运行这个应用,使用如下命令开始hook:

frida -U -f com.example.myapplication -l D:\Learning\Android\CTF\hook.js

稍等片刻,即可得到下面的内容。

automne

这段json格式的字符串其实就是Google Cloud Storage里使用服务账号进行授权的密钥文件(json格式),https://cloud.google.com/sdk/docs/authorizing#authorizing_with_a_service_account。

我是第一次听到Google Cloud Storage这个词。。

从网上了解到这个密钥文件的格式后,将上图的hook内容整理(注意\n的使用)如下并保存为key.json文件:

1
2
3
4
5
6
7
8
9
10
11
12
{
"type": "service_account",
"project_id": "bsides-sf-ctf-2019",
"private_key_id": "6dd7fc48a8b1d49edf7f03f74bc47713bec7d989",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCbNaJX7qZ2Sec4\n5W4ir+yYXJ3IwjZ8fwyw0PZSoiTb/iJqTcKk/ltjP61TrJHB5MqKm6Vz/WGw7GSm\nnd21xMFqclwuG8N7f+zhIK0XuvRBrS+cMEhw0RbHNUbcO3ZaghKffaLThzPY4x2r\nHh/N8lUYi4TB8WAGaAzCJH3pUi9rTG4+ucxO6pMNz3/ENzyOSmihR9Xb5OkMY/Aq\n7NJ7BAwH8oSWRlTllaC8Chl8wDdun6fKWgYYmmcBWXSjropu8f6MR7vMM3F0PEua\nYBz1ZpxVvYc1iiMS0WiNtAa1ZBuWhRenAiTfwOCt4rISfbgYSqCTSoK5tOWS52tM\ntuzt/OVjAgMBAAECggEAC9T230UuI25W1huHXdWTb7n/vUIw7SSyTvhfDsWVkb+5\n1+i9od5SESrVh79sDR/n4NEkt8blH5ulwJ3gPO8W34qARHORX2TNJgxbpad231rY\nekuj+hW2atFA6aEO0K+Bw+7L7twrs6j8pgLR4d1LZ2ebYz2HWHWuI06s2pCNVNyM\nSMn593YgfmzNotaoJ3dHAwKG8PuNRHh0qDqqLnu0574dXkTFkEePcedyzza007Iy\nCoNTUYJxDO7aJbTQPVyqm7mOewh1SYUdHSofcfALlC9eT2nn6+c9qRgGO3AwbC8V\ns9TmMR9+DWfHrUmPN8AKB4YKov/QGUv29svI0d8YwQKBgQDTobsDSI96xm2s+NnE\nPabZ+W76sg/1o1dPU4w4+/0u/RUT+vJoumsvwf5n7KUXVAP8npu6LYouNlHz9+zV\nThTINxyTrrA5VamFhoEpeY8OFboNVltxKj9nFvbS2jw2GLZQLapN0XIYE0axRNJs\nCSyc2LDYVVj0abjzx0CCFc0S+QKBgQC7v7kpSMJmIwl7FpJm/T9oakdvyZNzt3ZU\n+DTRmPXh/WM5c6j9vdzGKq3IlmHV/SSzVUfwQaxF8VzQlAi69fru/DStT8h7CpGd\nLEz8S0qq7ubbs7gODK/ZrwSQhv8doegZGu0ntURfaVL7AnyKGJVq2SLheVhMhJeZ\nm94mGME2OwKBgFXFSWcGRGhM/WxKGvAG0JWtGwZtnjw+rAcRZFZAApfFqIJFhXNe\ngkyDwhjadvpiaY87tP+ar1MVXteS1qCImbGfbGyKMw+5oQ/luHlXs9vQgGwhYMQX\njES6sOQ54IdIMrOCHnCVfzk0rsTvkJyKh1M2G05CIOBF7NiYG5PdRBT5AoGABEwT\nFNrReDz9DpApsanCNcWY9PoMIe3lC3TS4Kk7l3yRNNNs3sHlt7NqXtjyTE+K83/U\nMa+PHdq0YSHCQWU35RhorD7TO922D37gFDY080ychBLM96VasQTMefJdDHSUN17i\nZrJDaluixpP7/b0qTlPB9J8uYjH2tlFW+FBAu9kCgYBch8VvyBzlGay5r07joRju\ncdnVfrGAUdZRLxA5hrIaKvKFy28CaaKV/lZNI9NmdZntj6aIQ0rzUWidOQsmj2o4\n7VcArl5UNurhx4uhg2GClcUIHY5YdExHfEujBu6uMQdZjBpX87uoaggC7jwJanQC\nlApTPc3478g/mho9S5eDMw==\n-----END PRIVATE KEY-----\n",
"client_email": "weather-companion-service-acco@bsides-sf-ctf-2019.iam.gserviceaccount.com",
"client_id": "116037946827001874660",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url" : "https://www.googleapis.com/robot/v1/metadata/x509/weather-companion-service-acco%40bsides-sf-ctf-2019.iam.gserviceaccount.com"
}

但是在安装Google Cloud SDK(安装过程中需要断开shadowsocks,要求使用python2.7.9+)后使用

gcloud auth activate-service-account --key-file=key.json

却被告知invalid_grant,一度怀疑是json格式没整理好的原因,纠结蛮久的。结果后面发现是主办方把这道题的项目或者密钥删了。。

automne

为了搞清楚什么情况,我决定动手生成一个自己的服务账号密钥。

通过下面的网址进入服务账号界面:
https://console.cloud.google.com/iam-admin/serviceaccounts?_ga=2.264695759.-1360606619.1552115951

创建一个服务账号后,Google Cloud Platform的菜单栏里选择"API和服务",然后就看图操作。

automne

automne

automne

automne

automne

下面是我创建的服务账号密钥,用于测试。

1
2
3
4
5
6
7
8
9
10
11
12
{
"type": "service_account",
"project_id": "l-project-6598",
"private_key_id": "44ce83da2a92a53255bc0549c7516ec0f51532d1",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDOgpO10EsII69Y\n3VMI7a90sv+rpedkl2lIyWWwDjz9//f6qtpY7OKg3me0qR7MeN6VnP9E51A6RFVT\nt+x9GEEFIfeh4FAawOg97lcmRNcAqoDx9T4mIGQkY5Ynrz6hNc1NIVgXSSceUP4r\nHJgElOhzTE5W9lsr7D6+MOaabuqZWLVODBz8adBdwMW0386yWRwxK/hdJ3k0Vkkh\nWCsXypaMUMJiw9yLy2wP6T4Me0tA4poP2uMv7fA38YY3KQTiL5obNtqg5VMo8UGJ\nvdVF7vGMjxUZ28+ZcF3KgRyVlhMzZ8kkJhlihSzqEIgiKRbULtCZ8bABznZaJ4LQ\nUT5VapERAgMBAAECggEAYuE7AM9fXeJYT2CKJbhJTsN2kCW8CfpFu0hTnFz071nk\nzu2H5xRl30ketu5ThOxLB7BIuiFX1M5rXM6wOaWnXGHLRIM3V9gOW7kHmZdUq0j4\nvOIEhBT2XkYg25eZkW9Fgiz5FQ094dI+IT5ru6XhN3PN/u2u8fUIheC8Si3ZvtpR\nsbQCH8mJdzWMRKbHNpvxI9pvv5jNqs297MyNqgaGmM2UfAnaZogWOWklnagm2oda\nUTzN6SHreOJA/58PQ+zjh8E7sHhDuvq2xcaYvlJXEIZpoAjdOqE38fb/VmUMxmm7\nTGFGxlBuuCPNLhEC/PFT0fk8YfIe00zEGG9eJOw00QKBgQD8VTyeYLKpVJStgh+M\nOS4L+s7tREOiSbmn4cPJrVz1d4iVNQIs464HXPSIP+Sd7c8FDaUwC9FrVS22Prly\nRwNDQpRx5byq1l1VzzlL08ONJilAotdoN3Dcm5+kZtWuyynnnsTBpGHb3CTlpt+t\nAs8FF05GTNHlPhiig9Ogj8IfrwKBgQDRgt0aht2/B+AAGQMPGU0y3pYOHY1J2baR\nCsKqZJ4/Bti15QhpuP9+/QlGwuo+e8iWFKd2dj+HT3Lm1JKQQ9C+tZ0gkToMgn6h\nWw6mDpVWeavfs/yP0XuEz90/0LwfyTRORF9C3IZpjniQB3UAXfvmw5FhdSuNDTa5\nnqm/JwvLPwKBgCB/9fvMJpbKrw+d9+Q+pSCj4C0NIszvQ/tMh8QdESEIkU3ucuZH\nOO332gMGf7Kbo5NaC5GhpAp4ARg9AfOnE9OA6s+0sFUsoP7hXtbWYpR2es+5aZch\ntOalIK2zFQibYD1V5K+wNW5070eo85w8BV/5fXpugIZPNisQYZyHh5A/AoGBAMbm\noBYw95AGX+h31mmdMacbngWklKAJ6sLkHk852uonC9ITr9r+4MCkZQwiu9O6HIIu\n9ZUHSeShzonKQaJoX2LOdG+YsC10Ldft5IhNzAUe2cc0zo+S5tr9pCSTfCYJFHlB\ne9a0GX9Y3KiQC8nfb+hyvjBx1njAlLXAJZLt9MptAoGBAIlG4ehIBWD2xxQ9Mdu4\nyEM621WHb32SHTBa3CSrOs1egUjh6QysYF3nHV8GbLBTW2CSnkVA1lrmz+pU8lYZ\n1vyr0PG8FUz68H/FMbE5MDdTLHwytZh2cr9bmZwJIzn+6KcY4d8NyJRMWCTUmGNI\nniT6f27B8msLzwYNU96EbhGY\n-----END PRIVATE KEY-----\n",
"client_email": "l-project@l-project-6598.iam.gserviceaccount.com",
"client_id": "112915449445549367190",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/l-project%40l-project-6598.iam.gserviceaccount.com"
}

使用gcloud auth activate-service-account --key-file=xxx.json是可以访问的。

automne

尝试使用gsutil创建存储分区(https://cloud.google.com/storage/docs/quickstart-gsutil),

发现创建不了,好像是需要注册结算账号,而且注册需要visa卡,我哪有这个。。

最后测试一下删除服务账号密钥,再用gcloud auth命令认证

automne

删除后再认证的结果果然和上面的是一样的

automne

所以应该是题目环境不存在了,最后无法获取flag。

最后截一下参考链接里大佬的图,看一下连上之后的基本操作。

automne

总结完这道题之后,对Google Cloud Storage有了新的认识。

CATALOG