攻防世界 mobile 新手区 分类难度排序题解集

image.png

SORT

  • mobile新手

    • xctf-mobile新手 easy-dex NativeDex ”动态壳“ 5
    • xctf-mobile新手 easyjava 源码分析 3
    • xctf-mobile新手 app2 JNI service reciver intent 2
    • xctf-mobile新手 app3 ab文件 sqlite+sqlcipher 2
    • xctf-mobile新手 easy-so JNI 2
    • xctf-mobile新手 easyjni JNI 2
    • xctf-mobile新手 easy-apk 源码分析 1
    • xctf-mobile新手 app1 AndroidManifest.xml 1
    • xctf-mobile新手 Ph0en1x-100 java层调试 1
    • xctf-mobile新手 RememberOther 脑洞 1
    • xctf-mobile新手 JNI动态注册修复注册表 1
    • xctf-mobile新手 你是谁 脑洞题 1

xctf-mobile新手 你是谁

step1:寻找主逻辑

  1. 根据Manifest

    1. 逻辑活动在xyz.konso下面的Splash和Main
    2. 主活动是是Splash,这个活动在创建2.5秒后会通过intent切换到Main
  2. Main函数

    1. iflytek是讯飞语言识别模块
    2. 函数调用了多个该模块的监听事件比如SynthesizerListener并在监听结果(语音识别结果处比对)
  3. 提示字符定位,主要

    1. 使用jadx搜索中文提示:你根本不懂什么是爱
    2. 找到主逻辑在:xyz.konso.background中的getsna(String flag)
    3. 语音识别后传getsna进行匹配

step2以此分析,传入getsna前的识别处理和getsna函数

exp

因为单纯的就是冒泡排序,不可逆结合前面是语音识别因此是unicode码

cipher = [20667, 25105, 26159, 36924]
for each in cipher:
    c = chr(each)
    print(c, end='') # 傻我是逼

重排一下就是我是傻逼

因此正确的输入值就是[25105, 26159, 20667, 36924]

加上提示是:You get the sorted flag:20667 25105 26159 36924

因此flag就是:flag{25105 26159 20667 36924}

xctf-mobile新手 黑客精神

分析JNI通过字符串交叉并不能找到,代码内容,动态注册JNI函数

根据字符串找到交叉引用java层使用的函数符号,此处是动态链接符号表

对应的:

  • n1就是initSN
  • n2就是saveSN
  • n3就是work
.data:00005004 off_5004        DCD aInitsn             ; DATA XREF: JNI_OnLoad+46↑o
.data:00005004                                         ; JNI_OnLoad+4A↑o ...
.data:00005004                                         ; "initSN"
.data:00005008                 DCD aV                  ; "()V"
.data:0000500C                 DCD n1+1
.data:00005010                 DCD aSavesn             ; "saveSN"
.data:00005014                 DCD aLjavaLangStrin     ; "(Ljava/lang/String;)V"
.data:00005018                 DCD n2+1
.data:0000501C                 DCD aWork               ; "work"
.data:00005020                 DCD aV                  ; "()V"
.data:00005024                 DCD n3+1
.data:00005024 ; .data         ends

主要验证逻辑

  • saveSN

    • 加密后,保存输入值到reg.dat文件中
  • initSN

    • 结合java层逻辑,启动程序会检查程序是否已注册
    • 验证逻辑是reg.dat文件值是否为:EoPAoY62@ElRD
    • 如果是就会调用jni_work:jni_work作用就是提示flag是输入值

因此只要求逆saveSN中的加密算法即可

regfs = j_fopen("/sdcard/reg.dat", "w+");
if ( !regfs )
    return j___android_log_print(3, "com.gdufs.xman", byte_2E5A);
strcpy(key, "W3_arE_whO_we_ARE");
input_data = (*a1)->GetStringUTFChars(a1, a3, 0);
input_data_th = input_data;
len = j_strlen(input_data);
from_2016 = 2016;
while ( 1 )
{
    th = input_data_th - input_data;
    if ( input_data_th - input_data >= len )
        break;
    if ( th % 3 == 1 )
    {
        from_2016 = (from_2016 + 5) % 16;
        v11 = key[from_2016 + 1];
    }
    else if ( th % 3 == 2 )
    {
        from_2016 = (from_2016 + 7) % 15;
        v11 = key[from_2016 + 2];
    }
    else
    {
        from_2016 = (from_2016 + 3) % 13;
        v11 = key[from_2016 + 3];
    }
    *input_data_th++ ^= v11;
}
j_fputs(input_data, regfs);
return j_fclose(regfs);

expEoPAoY62@ElRD求逆

  • work()中的正确提示:.rodata:00002EFB aFlagXman DCB "输入即是flag,格式为xman{……}!",0
  • 但是我的实机测试并没有什么提示,可能是没写入权限?
s = list(map(ord, list("EoPAoY62@ElRD")))
key = list(map(ord, list("W3_arE_whO_we_ARE")))
from_2016 = 2016
flag = ''
for i in range(len(s)):
    xor = 0
    if i%3==1:
        from_2016 = (from_2016 + 5) % 16
        xor = key[from_2016 + 1]
    elif i%3==2:
        from_2016 = (from_2016 + 7) % 15
        xor = key[from_2016 + 2]
    else:
        from_2016 = (from_2016 + 3) % 13
        xor = key[from_2016 + 3]
    flag+=chr(s[i] ^ xor)
print("xman{}") # xman{201608Am!2333}

xctf-mobile新手 easy-dex

分析

https://www.52pojie.cn/thread-1105062-1-1.html

根据提示是让我们找dex文件,apk本身是一个nativeActivity,主函数在android_main中

com.a.sample.findmydex 两个activity:

  • android.app.NativeActivity
  • com.a.sample.findmydex.MainActivity

分析对应的so文件发现核心逻辑

  • qmemcpy
  • uncompress
  • open
  • write
  • remove
data_len = 0x100000;
uncompressed_data = (Bytef *)malloc(0x100000u);
source_len_eq = source_len;
source = (char *)malloc((size_t)source_len);
qmemcpy(source, &unk_7004, (size_t)source_len_eq);// movs source,unk_7004
//...
v20 = v6;
if ( uncompress(uncompressed_data, &data_len, (const Bytef *)source, (uLong)source_len) )
    _android_log_print(5, "FindMyDex", "Dangerous operation detected.");
fs = open(filename, 577, 511);
if ( !fs )
    _android_log_print(5, "FindMyDex", "Something wrong with the permission.");
write(fs, uncompressed_data, data_len);
close(fs);
free(uncompressed_data);
free(source);
if ( access(name, 0) && mkdir(name, 0x1FFu) )
    _android_log_print(5, "FindMyDex", "Something wrong with the permission..");
sub_2368(a1);
remove(filename);
_android_log_print(4, "FindMyDex", "Congratulations!! You made it!");
sub_2250(a1);
v10 = 0x80000000;
v6 = v20;

向上分析发现source在解压前发生了变换

v10 = shake_count;
if ( (unsigned int)(shake_count - 1) <= 0x58 )// v14=100
{
    v10 = shake_count;
    v15 = shake_count / 10;
    if ( shake_count % 10 == 9 )
    {
        v16 = source_len;
        v17 = (int)source_len / 10;
        v18 = (v15 + 1) * ((int)source_len / 10);
        if ( (int)source_len / 10 * v15 < v18 )
        {
            v19 = &source[v17 * v15];
            do
            {
                --v17;
                *v19++ ^= shake_count;
            }
            while ( v17 );
        }
        if ( shake_count == 89 )
        {
            while ( v18 < (int)v16 )
                source[v18++] ^= 0x59u;
        }
        v10 = shake_count + 1;
    }
}

可以先dump这段数据然后模拟其变换得到真正的dex(要zlib解压)

从pe文件dump出来qemecpy的值

'''
idapython 3.8
ida pro7.5
'''
# coding:utf8
import idaapi
data = idaapi.get_bytes(0x007004, 0x03CA10)
filepath = "C:\\Users\\15426\\Desktop\\1A_Files\\大学\\学科内\\计算机\\REVERSE\\0A_CTF\\__idapython_script\\dump.out"
with open(filepath, 'wb') as fp:
    fp.write(data)

然后编写脚本求逆这个解压前的变换过程

import zlib

with open("dump.out", "rb") as f:
    source_len = 0x03CA10
    source = list(f.read())
    for shake_count in range(0x1, 0x59+1):
        v15 = shake_count // 10
        if shake_count % 10 == 9:
            v17 = source_len // 10
            v18 = (v15 + 1) * (source_len // 10)
            if ((source_len // 10) * v15) < v18:
                v19 = v17*v15
                while True:
                    v17 -= 1
                    source[v19] ^= shake_count
                    v19 += 1
                    if not(v17):
                        break
            if shake_count == 89:
                while v18 < source_len:
                    source[v18] ^= 0x59
                    v18+=1

res = bytes(source)
with open('easydex.dex', 'wb') as f:
    f.write(zlib.decompress(res))

分析easydex,对于的两个字符串标很重要,通过androidkiller的工程搜索可以发现找到:

resouces.arsc xml文件解析后的目录

  • res/values/public.xml::id表-》name
  • res/values/strings.xml:name-》id对应的内容

在strings.xml文件中可以发现two fish的线索,考虑two fish加密

比对部分如下,就是通过a函数加密(第二个参数是密钥),然后和这个数组进行比对

  • 0x7F060023:`I have a male fish and a female fish.
public void onClick(View arg5) {
    if(Arrays.equals(
        MainActivity.a(this.a.getText().toString(), this.c.getString(0x7F060023))
        ,
        new byte[]{-120, 77, -14, -38, 17, 5, -42, 44, 0xE0, 109, 85, 0x1F, 24, -91, 0x90, -83, 0x40, -83, 0x80, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 0x75, 29, -44, 6, 0x70, -4, 81, 84, 9, 22, -51, 0x5F, -34, 12, 0x2F, 77}
    )) {
        Toast.makeText(this.b, this.c.getString(0x7F060025), 1).show();
        return;
    }

    Toast.makeText(this.b, this.c.getString(0x7F060022), 1).show();
}

exp

因为twofish的结果是base64,因此先转换成base64代码再解密

import base64
key = "I have a male fish and a female fish."
m = [-120, 77, -14, -38, 17, 5, -42, 44, 0xE0, 109, 85, 0x1F, 24, -91, 0x90, -83, 0x40, -83, 0x80, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 0x75, 29, -44, 6, 0x70, -4, 81, 84, 9, 22, -51, 0x5F, -34, 12, 0x2F, 77]
data = []
for i in m:
    data.append(i&0xFF)
print(base64.b64encode(bytes(data))) # b'iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N'

在线解密:http://tool.chacuo.net/crypttwofish

  • 密码:I have a male fish and a female fish.
  • 密文:iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N

得到:qwb{TH3y_Io<e_EACh_OTh3r_FOrEUER}

xctf-mobile新手 RememberOther

JEB自动分析出字符串地址对应的字符串:// string:successed "md5:b3241668ecbeb19921fdac5ac1aafa69"

付费查询了md5结果是:YOU_KNOW_

结合doc文档写作者不懂安卓,那意思就是,解题者知道安卓所以flag就是:YOU_KNOW_ANDROID

xctf-mobile新手 Ph0en1x-100

java层很好理解就是满足this.getFlag() = this.encrypt(sInput)就可以了

public void onGoClick(View arg5) {
    String sInput = this.etFlag.getText().toString();
    if(this.getSecret(this.getFlag()).equals(this.getSecret(this.encrypt(sInput)))) {
        Toast.makeText(this, "Success", 1).show();
        return;
    }

    Toast.makeText(this, "Failed", 1).show();
}

涉及到的两个函数都是native函数,而且getflag是固定的返回,考虑动态调试

分析encrypt:单纯把每个传入的值编码减一

jstring __cdecl Java_com_ph0en1x_android_1crackme_MainActivity_encrypt(JNIEnv *a1, int a2, int a3)
{
    size_t v3; // esi
    const char *s; // edi

    v3 = 0;
    for ( s = (*a1)->GetStringUTFChars(a1, a3, 0); v3 < strlen(s); --s[v3++] )
        ;
    return (*a1)->NewStringUTF(a1, s);
}

exp

JEB动态调试出getflag的返回值:string@4586:"ekfz@q2^x/t^fn0mF^6/^rbqanqntfg^Ehq|"`

s = "ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|"
flag = ''
for c in s:
    cc = chr(ord(c)+1)
    flag+=cc
print(flag) # flag{Ar3_y0u_go1nG_70_scarborough_Fair}

xctf-mobile新手 easyjava

调用链(基于JEB的结果)

  • MainActivity.a
  • MainActivity.b

    • new b
    • new a
    • a.a(b.a(arg))
    • b.b()

new b(2) a(3)

new b(2)

//b.a = swap( b.c[:2],b.c[2:] )

static {
    b.a = new ArrayList();
    b.b = "abcdefghijklmnopqrstuvwxyz";
    b.d = (int)0;
}

public b(Integer arg9) {
    this.c = new Integer[]{((int)8), ((int)25), ((int)17), ((int)23), ((int)7), ((int)22), ((int)1), ((int)16), ((int)6), ((int)9), ((int)21), ((int)0), ((int)15), ((int)5), ((int)10), ((int)18), ((int)2), ((int)24), ((int)4), ((int)11), ((int)3), ((int)14), ((int)19), ((int)12), ((int)20), ((int)13)};
    int v0;
    for(v0 = (int)arg9; v0 < this.c.length; ++v0) {
        b.a.add(this.c[v0]);
    }

    int v0_1;
    for(v0_1 = 0; v0_1 < ((int)arg9); ++v0_1) {
        b.a.add(this.c[v0_1]);
    }
}

new a(3)

//a.a = swap( a.c[:3],a.c[3:] )
static {
    a.a = new ArrayList();
    a.b = "abcdefghijklmnopqrstuvwxyz";
    a.d = (int)0;
}

public a(Integer arg8) {
    this.c = new Integer[]{((int)7), ((int)14), ((int)16), ((int)21), ((int)4), ((int)24), ((int)25), ((int)20), ((int)5), ((int)15), ((int)9), ((int)17), ((int)6), ((int)13), ((int)3), ((int)18), ((int)12), ((int)10), ((int)19), ((int)0), ((int)22), ((int)2), ((int)11), ((int)23), ((int)1), ((int)8)};
    int v0;
    for(v0 = (int)arg8; v0 < this.c.length; ++v0) {
        a.a.add(this.c[v0]);
    }

    int v0_1;
    for(v0_1 = 0; v0_1 < ((int)arg8); ++v0_1) {
        a.a.add(this.c[v0_1]);
    }
}

a.a(b.a(one_char))

b.a(one_char)

static {
    b.a = new ArrayList();
    b.b = "abcdefghijklmnopqrstuvwxyz";
    b.d = (int)0;
}
/*
    b.a是初始化生成的序列
    b.b = "abcdefghijklmnopqrstuvwxyz"
    找到满足 res = b.a.find(b.b.find(one_char))
    
    return res
*/
public Integer a(String arg5) {
    int v0 = 0;
    Integer v1 = (int)0;
    if(b.b.contains(arg5.toLowerCase())) {
        while(v0 < b.a.size() - 1) {
            //cmp  = b_b.find(arg5)
            //v1   = b_a.find(cmp)
            if(b.a.get(v0) == ((int)b.b.indexOf(arg5))) {
                v1 = (int)v0;
            }

            ++v0;
        }
    }
    else {
        v1 = arg5.contains(" ") ? ((int)-10) : ((int)-1);
    }
    
    //把b.a第一个元素删除,复制第二个元素到末尾
    //每次从b.b中删除第一个元素
    /*
        reverse:
        执行一共12次,生成12个对应b_a b_b序列,然后逆使用
    */
    b.a();
    return v1;
}

public Integer b() {
    return b.d;
}

a.a(one_char)

static {
    a.a = new ArrayList();
    a.b = "abcdefghijklmnopqrstuvwxyz";
    a.d = (int)0;
}

public char a(Integer arg5) {
    int v0 = 0;
    Integer v1 = (int)0;
    if(((int)arg5) == -10) {
        //调用的第二十五次(不过不会掉哟那么多次所以没用) 
        //移除初始化的a.a码表中的第一位,然后吧下一位保存到结尾
        a.a(); 
        return " ".charAt(0);
    }
    
    while(v0 < a.a.size() - 1) {
        //find_index_in_a_a_equal_to_arg5
           //v1   = a_a.find(arg5)
        if(a.a.get(v0) == arg5) {
            v1 = (int)v0;
        }
        ++v0;
    }

    a.a();
    //a_b[v1]
    return a.b.charAt(v1.intValue());
}

b.a()

经过测试jd-gui的逻辑正确,两者皆是把第一位放到末尾的操作

// jd-gui 的结果
public static void a() {
    int i = ((Integer)a.get(0)).intValue();
    a.remove(0);
    a.add(Integer.valueOf(i));
    char c = b.charAt(0);
    b += "" + c;
    b = b.substring(1, 27);
    Integer integer = d;
    d = Integer.valueOf(d.intValue() + 1);
}
//JEB的结果
public static void a() {
    b.a.remove(0);
    b.a.add(Integer.valueOf(((Integer)b.a.get(0)).intValue()));
    b.b = b.b + "" + b.b.charAt(0);
    b.b = b.b.substring(1, 27);
    b.d = (int)(((int)b.d) + 1);
}

exp

a.a b.a的初始化

# coding:utf8

############
# gen1
print('[+] gen1 init b_a a_a')
s = '((int)8), ((int)25), ((int)17), ((int)23), ((int)7), ((int)22), ((int)1), ((int)16), ((int)6), ((int)9), ((int)21), ((int)0), ((int)15), ((int)5), ((int)10), ((int)18), ((int)2), ((int)24), ((int)4), ((int)11), ((int)3), ((int)14), ((int)19), ((int)12), ((int)20), ((int)13)};'
s = s.split('((int)')[1:]
lis = []
for x in s:
    lis.append(int(x[:x.rfind(')')]))
b_a = []
b_a.extend(lis[2:])
b_a.extend(lis[:2])

s = '((int)7), ((int)14), ((int)16), ((int)21), ((int)4), ((int)24), ((int)25), ((int)20), ((int)5), ((int)15), ((int)9), ((int)17), ((int)6), ((int)13), ((int)3), ((int)18), ((int)12), ((int)10), ((int)19), ((int)0), ((int)22), ((int)2), ((int)11), ((int)23), ((int)1), ((int)8)}'
s = s.split('((int)')[1:]
lis = []
for x in s:
    lis.append(int(x[:x.rfind(')')]))
a_a = []
a_a.extend(lis[3:])
a_a.extend(lis[:3])
print('b_a', b_a)
print('a_a', a_a)

############
### gen2 a
print('[+] gen2 reverse a.a(arg)')
a_b = 'abcdefghijklmnopqrstuvwxyz'
cipher = 'wigwrkaugala'
medium = []
for c in cipher:
    v1 = a_b.find(c)
    arg5 = a_a[v1]
    medium.append(arg5)

############
### gen3 b
print('[+] gen3 get b.b(arg) reversing table')
b_b = []
b_b.append('abcdefghijklmnopqrstuvwxyz')
tmp = b_a.copy()
b_a = []
b_a.append(tmp)
for i in range(1, 12):
    tmp = b_a[i-1].copy()
    tmp = tmp[1:] + [tmp[0]]
    b_a.append(tmp)

    # print(b_b[i-1])
    tmp = b_b[i-1]#.copy()[1:]
    tmp = tmp + tmp[0]
    tmp = tmp[1:27]
    b_b.append(tmp)

############
### gen4 b
print('[+] gen4 reversing b.b() and get flag')
flag = ''
for i in range(12): # list(range(12))[::-1]:
    v1 = medium[i]
    cmp = b_a[i][v1]
    arg5 = b_b[i][cmp]

    flag+=arg5
print("flag{%s}"%flag) # flag{venividivkcr}

xctf-mobile新手 easyjni

核心部分分析如下(传入时有一个java层的base64变表)

char_input = (*a1)->GetStringUTFChars(a1, a3, 0);
  if ( strlen(char_input) == 32 )
  {
    for ( i = 0; i != 16; ++i )
    {
      check_box_iplus = &check_box[i];
      check_box[i] = char_input[i + 16];
      v8 = char_input[i];
      check_box_iplus[16] = v8;                 // i from 0 to 15:
                                                // check_box[i] = char_input[i+16]
                                                // check_box[i+16] = char_input[i]
                                                // equal to: check_box[:16],check_box[16:32] = char_input[16:32],char_input[:16]
    }
    (*a1)->ReleaseStringUTFChars(a1, (jstring)a3, char_input);
    ii = 0;
    do
    {
      v10 = ii < 30;
      v13 = check_box[ii];                      // ii from 0 to 30 and ii%2==0:
                                                // equal to: swap(check_box[ii], check_box[ii+1])
      check_box[ii] = check_box[ii + 1];
      check_box[ii + 1] = v13;
      ii += 2;
    }
    while ( v10 );
    result = memcmp(check_box, "MbT3sQgX039i3g==AQOoMQFPskB1Bsc7", 0x20u) == 0;
  }

exp

# -*- coding: utf-8
import sys

def debase64tc(cryp, proc_alpha):
    assert type(cryp)==str
    assert type(proc_alpha)==str

    import base64
    base_alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
    res = ''
    for i in cryp:
        res += base_alpha[proc_alpha.find(i)]
    dec = base64.b64decode(res)
    return dec

# if sys.__main__ == 'main':
#     debase64tc(sys.argv[1], sys.argv[2])

cipher = 'MbT3sQgX039i3g==AQOoMQFPskB1Bsc7'
cipher = list(cipher)
for i in range(len(cipher)):
    if i%2==0:
        cipher[i],cipher[i+1] = cipher[i+1],cipher[i]
cipher[:16],cipher[16:32] = cipher[16:32],cipher[:16]

proc_alpha = 'i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN='
cipher = ''.join(cipher)
flag = debase64tc(cipher, proc_alpha)
print(flag) # b'flag{just_ANot#er_@p3}'

xctf-mobile新手 easy-apk

base64变表

exp

# -*- coding: utf-8
import base64

# cryp是密文,可修改
# base_alpha是原表,不可修改
# proc_alpha是变表,可修改
cryp = '5rFf7E2K6rqN7Hpiyush7E6S5fJg6rsi5NBf6NGT5rs='
base_alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
proc_alpha = ['v', 'w', 'x', 'r', 's', 't', 'u', 'o', 'p', 'q', '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'y', 'z', '0', '1', '2', 'P', 'Q', 'R', 'S', 'T', 'K', 'L', 'M', 'N', 'O', 'Z', 'a', 'b', 'c', 'd', 'U', 'V', 'W', 'X', 'Y', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', '8', '9', '+', '/', '=']
def findIndex(input):
    for i in range(len(proc_alpha)):
        if proc_alpha[i]==input:
            return i
res = ''
for i in cryp:
    if findIndex(i)==None:
        raise Exception("can not find %c"%i)
    res += base_alpha[findIndex(i)]
# base64正常码表decode
dec = base64.b64decode(res)
print('flag{%s}'%dec.decode())

xctf-mobile新手 app3

ab文件,使用github上的Android backup extractor来解压缩分析其中的base.apk

调用逻辑链条:

  • MainActivity: onCreate()
  • MainActivity: a()
  • a:

    • str2 = a( "Stranger", "123456")

      • paramString1[:4] + paramString2[:4]
    • str3 = b( str2, "123456")

      • //经过分析就是md5(paramString1)
    • key = a( str2+str3 )[:7]

      • 经过分析就是sha1(paramString+ "yaphetshan")
    • Demo.db: getWritableDatabase(key)
private void a() {
    SQLiteDatabase.loadLibs((Context)this);
    this.b = new a((Context)this, "Demo.db", null, 1);
    ContentValues contentValues = new ContentValues();
    contentValues.put("name", "Stranger");
    contentValues.put("password", Integer.valueOf(123456));
    a a1 = new a();
    String str2 = a1.a(contentValues.getAsString("name"), contentValues.getAsString("password"));
    String str3 = a1.b(str2, contentValues.getAsString("password"));
    String str1 = a1.a(str2 + str3);
    this.a = this.b.getWritableDatabase(str1.substring(0, 7));
    this.a.insert("TencentMicrMsg", null, contentValues);
  }
package com.example.yaphetshan.tencentwelcome.a;

public class a {
  private String a = "yaphetshan";
  
  public String a(String paramString) {
    new b();
    return b.b(paramString + this.a);  //经过分析就是sha1(paramString+ "yaphetshan")
  }
  
  public String a(String paramString1, String paramString2) {
    paramString1 = paramString1.substring(0, 4);
    paramString2 = paramString2.substring(0, 4);
    return paramString1 + paramString2; //paramString1[:4] + paramString2[:4]
  }
  
  public String b(String paramString1, String paramString2) {
    new b();
    return b.a(paramString1); //经过分析就是md5(paramString1)
  }
}

exp

import hashlib as hl
user = "Stranger"
pawd = "123456"

str2 = user[:4] + pawd[:4]
str2 = str2.encode(encoding='utf8')
str3 = hl.md5(str2)
str3 = str3.hexdigest().encode(encoding='utf8')
key  = hl.sha1(str2+str3+b"yaphetshan").hexdigest()[:7]
print(key) # ae56f99

然后用开源项目DB Browser for SQLite中的DB Browser for SQLCipher打开输入密钥在Encryto.db数据库文件中找到BASE64字符串

import base64
flag = base64.b64decode(b'VGN0ZntIM2xsMF9Eb19ZMHVfTG92M19UZW5jM250IX0=')
print(flag) # 'Tctf{H3ll0_Do_Y0u_Lov3_Tenc3nt!}'

xctf-mobile新手 app2

核心逻辑,调用 JNI的doRawData加密

super.onCreate(paramBundle);
setContentView(2130903041);
Intent intent = getIntent();
String str2 = intent.getStringExtra("ili");
String str1 = intent.getStringExtra("lil");
if (Encryto.doRawData(this, str2 + str1).equals("VEIzd/V2UPYNdn/bxH3Xig==")) {

分析doRawData

  • 加密方式:j_AES_128_ECB_PKCS5Padding_Encrypt
  • 加密密钥:thisisatestkey==
strcpy(key, "thisisatestkey==");
cipher = (*a1)->GetStringUTFChars(a1, a4, 0);
v7 = (const char *)j_AES_128_ECB_PKCS5Padding_Encrypt(cipher, key);
(*a1)->ReleaseStringUTFChars(a1, (jstring)a4, cipher);
result = (*a1)->NewStringUTF(a1, v7);

在SecondActivity也就是点击登入后跳转的页面中找到比对

if (Encryto.doRawData(this, str2 + str1).equals("VEIzd/V2UPYNdn/bxH3Xig==")) {
    intent.setAction("android.test.action.MoniterInstallService");
    intent.setClass((Context)this, MoniterInstallService.class);
    intent.putExtra("company", "tencent");
    intent.putExtra("name", "hacker");
    intent.putExtra("age", 18);
    startActivity(intent);
    startService(intent);
}

解密结果:aimagetencent,并非flag

在FileDataActivity中找到另一处比对,但是此处的activity始终未在程序逻辑中被调用。代码含义即使用JNI中的解码函数解码这个字符串,并且设置为页面内容。

package com.tencent.testvuln;

import android.os.Bundle;
import android.widget.TextView;
import com.tencent.testvuln.c.Encryto;

public class FileDataActivity extends a {
  private TextView c;
  
  protected void onCreate(Bundle paramBundle) {
    super.onCreate(paramBundle);
    setContentView(2130903042);
    this.c = (TextView)findViewById(2131165184);
    this.c.setText(Encryto.decode(this, "9YuQ2dk8CSaCe7DTAmaqAA=="));
  }
}

使用同样的加密规则进行解密得到:Cas3_0f_A_CAK3

即为解密,同时也可以使用JEB通过修改MainActivity中的代码使得点击登入和跳转到这个activity使得直接显示答案。

xctf-mobile新手 app1

主逻辑

public void onClick(View param1View) {
            try {
              String str1 = MainActivity.this.text.getText().toString();
              PackageInfo packageInfo = MainActivity.this.getPackageManager().getPackageInfo("com.example.yaphetshan.tencentgreat", 16384);
              String str2 = packageInfo.versionName;
              int i = packageInfo.versionCode;
              for (byte b = 0; b < str1.length() && b < str2.length(); b++) {
                if (str1.charAt(b) != (str2.charAt(b) ^ i)) {
                  Toast.makeText((Context)MainActivity.this, ", 1).show();
                  return;
                } 
              } 
              if (str1.length() == str2.length()) {
                Toast.makeText((Context)MainActivity.this, ", 1).show();
                return;
              } 
            } catch (android.content.pm.PackageManager.NameNotFoundException nameNotFoundException) {}
            Toast.makeText((Context)MainActivity.this, ", 1).show();
          }
        });

AndroidManifest.xml没有定义,考虑常量定义,在这个包域下的BuildConfig文件中找到相关定义

package com.example.yaphetshan.tencentgreat;

public final class BuildConfig {
  public static final String APPLICATION_ID = "com.example.yaphetshan.tencentgreat";
  
  public static final String BUILD_TYPE = "debug";
  
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  
  public static final String FLAVOR = "";
  
  public static final int VERSION_CODE = 15;
  
  public static final String VERSION_NAME = "X<cP[?PHNB<P?aj";
}

exp

version_name = 'X<cP[?PHNB<P?aj'
version_code = 15

for x in version_name:
    print(chr(ord(x)^version_code), end='') # W3l_T0_GAM3_0ne

xctf-mobile新手 easy-so

在so文件中找到核心逻辑

memcpy(equal_input_buff, inputs, v6);
  if ( strlen(equal_input_buff) >= 2 )
  {
    i = 0LL;
    do
    {                                           // swap(equal_input_buff[i], equal_input_buff[i+16])
                                                // i++
      tmp_input = equal_input_buff[i];
      equal_input_buff[i] = equal_input_buff[i + 16];
      equal_input_buff[i++ + 16] = tmp_input;
    }
    while ( strlen(equal_input_buff) >> 1 > i );// i<len/2
  }
  input_0 = *equal_input_buff;
  if ( *equal_input_buff )
  {                                             // swap(equal_input_buff0], equal_input_buff[1])
    *equal_input_buff = equal_input_buff[1];
    equal_input_buff[1] = input_0;
    if ( strlen(equal_input_buff) >= 3 )
    {
      ii = 2LL;
      do
      {                                         // swap(equal_input_buff[ii], equal_input_buff[ii+1])
                                                // i+=2
        v16 = equal_input_buff[ii];
        equal_input_buff[ii] = equal_input_buff[ii + 1];
        equal_input_buff[ii + 1] = v16;
        ii += 2LL;
      }
      while ( strlen(equal_input_buff) > ii );
    }
  }
  return strcmp(equal_input_buff, "f72c5a36569418a20907b55be5bf95ad") == 0;

exp

s = 'f72c5a36569418a20907b55be5bf95ad'
buf = list(s)
def swap(i, j):
    tmp = buf[i]
    buf[i] = buf[j]
    buf[j] = tmp
for i in range(len(buf)):
    if i%2==0:
        swap(i, i+1)
for i in range(len(buf)//2):
    swap(i, i+16)
print(''.join(buf)) # 90705bb55efb59da7fc2a5636549812a
Last modification:October 12, 2021
如果觉得我的文章对你有用,请随意赞赏。咖啡(12RMB)进度+100%,一块巧克力(1RMB)进度+6%。
(赞赏请备注你的名称哦!后台记录中来自理工小菜狗)