攻防世界 mobile 新手区 分类难度排序题解集
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:寻找主逻辑
根据Manifest
- 逻辑活动在xyz.konso下面的Splash和Main
- 主活动是是Splash,这个活动在创建2.5秒后会通过intent切换到Main
Main函数
- iflytek是讯飞语言识别模块
- 函数调用了多个该模块的监听事件比如SynthesizerListener并在监听结果(语音识别结果处比对)
提示字符定位,主要
- 使用jadx搜索中文提示:你根本不懂什么是爱
- 找到主逻辑在:xyz.konso.background中的getsna(String flag)
- 语音识别后传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:"ek
fz@q2^x/t^fn0mF^6/^rbqanqntfg^E
hq|"`
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
3 comments
怎么收藏这篇文章?
怎么收藏这篇文章?
想想你的文章写的特别好