Android CTF初探

@t3ls  April 4, 2020

Android CTF初探

最近因为一些偶然的契机,做了两道安卓的入门CTF题,这里也打算记录一下。(tcl

从我目前有限的知识里,安卓题的漏洞大概就是位于Java层或者Native层,所以基本都是逆向的题目;有的结合Web的题会把漏洞设计在.so(也就是native层)和远端服务器的连接中,比如app后端的服务器存在反序列化漏洞,这时既需要逆向apk,解析请求包的结构,也得有一定的web知识进行利用,最后拿到flag

我这里涉及到的都是普通的逆向,没有其它的骚操作。

factor2base

题目:https://github.com/t3ls/pwn/raw/master/XMAN2019/factor2base/factor2base.apk

XMan入营赛的Mobile

image-20200312152916226.png

直接拖入Jeb,看MainActivity方法

可以看到加载了名为native的原生库:

image-20200312153139208.png

继续往下看,可以找到标准的输入及提交,把输入框的值赋给passtflagt,分别是longstring类型

image-20200312153608925.png

然后就是判断flagtpasst的逻辑,直接调用了原生的check方法

image-20200312153346969.png

IDA打开libnative.so,搜索对应的实现

image-20200312154116549.png

跟进到sub_730这个函数

image-20200312154629680.png

可以看到在拿到用户提交的flag以后,经过sub_880这个函数的加密,最后和eG1hbntmYWM9NmJhczM0这个字符串进行比较,然后返回结果

于是继续跟进sub_880

image-20200312155053511.png

看着比较眼熟,估计是base64,这里可以打断点调试来看

但是需要注意的一点是,这里进行base64转换的字典在调用之前被修改了,0和9对换

image-20200312155351123.png

image-20200312155407060.png

所以直接把eG1hbntmYWM9NmJhczM0进行字符对换后debase64就行

image-20200403230615520.png

Emmmm....

肯定哪里出了问题,我们看下字符表的交叉引用

image-20200403230816041.png

点进这个write的操作

image-20200403234944658.png

可以发现这个函数是init_array中调用的(又是个出题人的坑

image-20200403230945197.png

image-20200403231935301.png

*&aAbcdefghijklmn[(a1 == 0x7FFFFFFF) + 12] = 0x4D4E;

int main() {
    char *a = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    uint16_t *b = a+12;
    printf("0x%x\n", *b);
    return 0;
}

/*

./1.c: In function ‘main’:
./1.c:13:16: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
  uint16_t *b = a+12;
                ^
0x4e4d

*/

仔细看一下,就是判断a1的值,然后把0x4d4e也就是"NM"赋值到aAbcdefghijklmn[12]或者aAbcdefghijklmn[13],这里其实可以直接打个断点看,蛋疼的是我手上没有安卓调试机,用的模拟器。。

于是IDA的Android调试我只看到了直接支持arm64armv7,于是我们就直接两种都试一下把。。

第一种情况就是MN互换:

image-20200403235539186.png

嗯。。答案出来了

Sign

题目:https://github.com/t3ls/pwn/raw/master/XMAN2019/sign/task__sign_.apk

一样的,直接拖到JEB,看MainActivity

image-20200404002132206.png

加载原生库native-lib,下面就是主要逻辑

image-20200404002337017.png

先是通过getSign拿到flag1,这个函数是Java层实现的

image-20200404002637705.png

遍历包名,对签名进行SHA运算,值得注意的是SHA用的是SHA-512

image-20200404003006149.png

这个位置其实还是一样的,打断点可以直接看,实际上赛后也成功复现了,可惜当时就是刚入门,疯狂找JEBattach上去的模拟器(雷电模拟器可以),结果并没有成功,于是就用了一个傻办法:直接用解包出来的证书手动算(证书文件在original\META-INF\CERT.RSA

#!/usr/bin/env python
#coding:utf-8 
# --*-- encoding:utf-8 --*--
'''

export LANG="zh_CN.GB18030"
'''

import commands
import hashlib
import StringIO
import sys
import base64

# from utils import *

reload(sys)   
sys.setdefaultencoding('utf-8')  

#RSAFile like: /root/apk/20110705144927058251126935.apk-unpack/META-INF/WORKNET.RSA
def convertFilePath(path):
    path = path.replace('(', '\(')
    path = path.replace(' ', '\ ')
    path = path.replace(')', '\)')

    return path


def getSignSHA512(RSAFile) :
    signBase64Str = commands.getoutput('openssl pkcs7 -in '+convertFilePath(RSAFile)+' -inform DER -print_certs')
    signBase64StringIO = StringIO.StringIO()
    
    lines = signBase64Str.splitlines()
    flag = False
    for index ,line in enumerate(lines):
        if cmp('-----BEGIN CERTIFICATE-----',line) == 0:
            flag = True
            continue
        elif cmp('-----END CERTIFICATE-----',line) == 0:
            flag = False
            continue
            
        if flag == True:
            signBase64StringIO.write(line)
    print(base64.b64decode(signBase64StringIO.getvalue()))
    m = hashlib.sha512(base64.b64decode(signBase64StringIO.getvalue()))
    m.digest()
    print m.hexdigest()
    

if __name__ == '__main__': getSignSHA512(sys.argv[1])
    

拿到flag1,然后是flag2

image-20200404004950225.png

这是一个原生实现的方法

image-20200404005301766.png

就是一些简单的异或逻辑,最后一个strcmp比对字符串,所以只要写个正向的爆破脚本就行

image-20200404005349915.png

image-20200404005359103.png

我的这个脚本其实有点问题,也能凑合用下

lib2 = '[email protected]::tZpc'
lib1 = [0xA4,0xC9,0xD3, 0xC9, 0xF1,0xA6,0xB5,0xCD,0xD4,0xF6,0xDA]


def stage1():
    tmp = []
    chr1, chr2, tmp_chr1 = '', '', ''
    for j in range(11):
        for i in range(0x100):
            # print('trying:', j, hex(i))
            try:
                chr2 = chr(i)
                tmp_chr1 = chr((ord(chr2) - 0x20))
                if (ord(chr2) - 0x61) >= 0x1a:
                    tmp_chr1 = chr2
                v10 = ord(chr2) - 0x41
                chr1 = chr((ord(chr2) + 0x20))
                if v10 >= 0x1a:
                    chr1 = tmp_chr1
                if chr((lib1[j] ^ ((ord(chr1) ^ 0x28) + 66))) == lib2[j]:
                    tmp.append(chr(i))
                    print(j, 'found', chr(i))
                    break
            except ValueError:
                continue
        if i == 0xff:
            print(j, 'not found')
    return ''.join(tmp)


if __name__ == '__main__':
    print(stage1())

运行的结果:

image-20200404005746198.png

但是拼接flag1之后交上去是错的,当时就把?换成_试了一下就行了。。应该是脚本哪里写的有问题把,不过懒得改了,就这样一个题


添加新评论