本帖最后由 HNHuangJingYU 于 2021-9-30 18:24 编辑
因为刚接触ctf的题很多题目也是比较新颖的这里记录下我的解题思路,个人理解,大神理解下>.<说下自己这题的解题思路吧,这题倒是学到了不少东西。(C++基础重要啊!!)嗯,把 黑客精神.apk 拉进JEB,我们先按照App执行代码流程分析思路:
1. 这里给App类下的的m赋值为0,并加载了so,然后就是调用了so层initSN()函数再打印了m的值
1
2. 来到MainActivity进来就是对App类的m进行了判断(划重点),最后点击按钮监听,因为刚才初始化的时候m为0所以果断进入doRegister()接着就来到了RegActivity,这里就干了一件事把输入的字符串传入so层saveSN(arg)3. 打开IDA分析so,进入JNI_OnLoad,发现什么也没有
2
4. 就是打印了一些logcat,然后回到IDA VIEW-A试图看看.data(ctrl+s)里面果然发现了函数,这里我重命名了n1就是initSN,n2就是saveSN,n3就是work
3
5. 按照我刚才整理的执行流程,先进入initSN函数(个人喜欢在代码旁边注释思路),总结出来的思路就是这个函数判断是否有/sdcard/reg.dat这个文件有的话setValue(env,文件流指针),给App类的m字段设置为1,如果找不到文件的话那么fopen函数返回值就为0,这里就对应了刚才JEB分析对App.m字段的判断
[C++] 纯文本查看 复制代码 void __fastcall initSN(_JNIEnv *env)
{
_JNIEnv *env_1; // r6
FILE *stream; // r0
FILE *stream_1; // r4
_JNIEnv *env_2; // r0
int stream_2; // r1
int stream_len; // r7
void *v7; // r5
_JNIEnv *env_3; // r0
int v9; // r1
env_1 = env;
stream = fopen("/sdcard/reg.dat", "r+"); // 打开一个用于更新的文件,可读取也可写入。该文件必须存在。返回file指针,失败返回NULL
stream_1 = stream;
if ( !stream ) // 操作失败,将m设值为0
{
env_2 = env_1;
stream_2 = stream_1;
LABEL_5:
setValue(env_2, stream_2); // 此时file为0
return;
}
fseek(stream, 0, 2); // 以SEEK_END(2)为基准,偏移0(指针偏移量)个字节的位置
stream_len = ftell(stream_1); // 得到file指针的字节长度
v7 = malloc(stream_len + 1); // 开辟内存空间返回指向头的指针
if ( !v7 ) // 操作失败,将m设值为0
{
fclose(stream_1);
env_2 = env_1;
stream_2 = 0;
goto LABEL_5;
}
fseek(stream_1, 0, 0);
fread(v7, stream_len, 1u, stream_1); // 读取打开的文件。
*(v7 + stream_len) = 0;
if ( !strcmp(v7, "EoPAoY62@ElRD") )
{
env_3 = env_1;
v9 = 1; // 字符匹配成功 v9 = 1
}
else
{
env_3 = env_1;
v9 = 0; // 失败 v9 = 0
}
setValue(env_3, v9);
j_fclose(stream_1);
} SetValue()函数
[C++] 纯文本查看 复制代码 void __fastcall setValue(_JNIEnv *env, int file)
{
int file_1; // r7
_JNIEnv *env_1; // r4
jclass myApp_class; // r0
void *myApp_class_1; // r5
struct _jfieldID *myApp_class$m; // r0
file_1 = file;
env_1 = env;
myApp_class = env->functions->FindClass(&env->functions, "com/gdufs/xman/MyApp");
myApp_class_1 = myApp_class;
myApp_class$m = env_1->functions->GetStaticFieldID(&env_1->functions, myApp_class, "m", "I");// 返回类的动态域的域ID。由其名字(m)和签名(I)指定。
env_1->functions->SetStaticIntField(&env_1->functions, myApp_class_1, myApp_class$m, file_1);// 此函数设置对象的静态字段的值。
} 6. 再来到saveSN函数,就是这里我被我手机坑了,因为我没有给App存储的权限导致我每次注册完后文件一直没有写进去,然后这个函数就一直走不下去,我还一直以为自己的思路错了(认为注册成功才会有reg.dat文件>.<),大家注意一下把
[C++] 纯文本查看 复制代码 int __fastcall saveSN(int a1, int a2, int a3)
{
_DWORD *v3; // r6
int inputStr; // r9
FILE *stream; // r7
int *v7; // r4
const char *v8; // r3
int v9; // r0
int v10; // r1
_WORD *v11; // r5
_DWORD *v12; // r0
int v13; // r4
int v14; // r3
signed int v15; // r6
const char *string; // r9
const char *v17; // r5
signed int string_lenght; // r10
char v19; // r2
char v20; // r3
int v21; // [sp+0h] [bp-38h]
int v22; // [sp+14h] [bp-24h]
char v23; // [sp+18h] [bp-20h]
v3 = a1;
inputStr = a3;
stream = fopen("/sdcard/reg.dat", "w+"); // 创建一个用于读写的空文件。返回一个 FILE 指针
if ( stream )
{
v7 = &v21;
v8 = "W3_arE_whO_we_ARE";
do
{
v9 = *v8; // v9 = W
v8 += 8; // v8 = hO_we_ARE
v10 = *(v8 - 1); // v10 = w
*v7 = v9; // v7[0] = W
v7[1] = v10;
v11 = v7 + 2;
v7 += 2;
}
while ( v8 != "E" );
v12 = v3;
v13 = 2016;
*v11 = *v8;
v14 = *v3;
v15 = 0;
string = (*(v14 + 676))(v12, inputStr, 0); //这里就是我输入的字符串
v17 = string;
string_lenght = strlen(string);
while ( v15 < string_lenght ) //循环我输入字符串的长度次数
{
if ( v15 % 3 == 1 ) //当索引位置为1、4、7、10的时候v19为w
{
v13 = (v13 + 5) % 16;
v19 = *(&v23 + v13 - 23);
}
else if ( v15 % 3 == 2 ) //当索引位置为2、5、8、11的时候v19为_
{
v13 = (v13 + 7) % 15;
v19 = *(&v23 + v13 - 22);
}
else //当索引位置为其他那么就是3、6、9、12的时候v19为a(这里是我配合IDA动态得出来的结果)
{
v13 = (v13 + 3) % 13;
v19 = *(&v23 + v13 - 21);
}
v20 = *v17; //取v17也就是我们输入的字符串的每一次字符(下面v17会自增)
++v15;
*(++v17 - 1) = v20 ^ v19; //因为这里是异或所以我们输入的长度和加密的长度是一样的
}
fputs(string, stream); // 把加密后字符串写入到指定的流 stream 中
}
else if ( v22 == _stack_chk_guard )
{
return j___android_log_print(3, "com.gdufs.xman", &dword_2DCA);
}
return j_fclose(stream);
} 7. 嗯,经过这个函数后不管验证对否程序都退出,再次打开程序的时候就又进入了initSN函数,如果给了App权限的话那么你应该可以在你的文件目录下找到/sdcard/reg.dat这个文件那么就进入这个流程了,如图: 这里很简单就是对文件里面的字符串进行比较是否为EoPAoY62@ElRD如果是则v9=1,之后调用setValue函数后那么App类下的m字段的值就为1,就会显示已经注册。
4
8.正向流程分析完了,那么逆向就比较好完成了,因为我们注册码比较的字符串EoPAoY62@ElRD加密后长度是不变的,所以按照上面总结的索引处输入的字符一个个异或w_aw_aw_aw_aw就对了脚本如图:得到flag =201608Am!2333
5
9.输入正确的201608Am!2333注册码后,再次打开App弹出提示要采用xman{}格式,然后在这里调用了so层的work()进入发现没有干什么,就是给我们赋值了弹tost的那句提示格式,最后提交flag “xman{201608Am!2333}”
|