-
-
[原创]CTF2018第五题分析(qwertyaa)
-
2018-6-25 00:50 3561
-
Java层分析
首先将apk用jadx打开,简单查看可知程序中主要加载了libexecute_table.so,并且调用其中注册的lkdakjudajndn来判断密钥是否成功。
另外Java层有一个判断函数,但由这个函数返回结果设置的Fail
提示似乎会被lkdakjudajndn的返回结果覆盖,并无实际用途。
顺带一提:Java层中几乎字符串都是用Base64加密的;解密函数调用时“偷偷”加载了libexecute_table.so。
Native层分析
找到判断函数
利用 https://bbs.pediy.com/thread-216701.htm 上的程序,我伪造了一个JavaVM
来加载so,并趁机获得so加载基址和JNI_OnLoad函数地址(ida中直接显示的是错误的,应该是.init和.init_array中对JNI_OnLoad地址进行了修改所致)
#include <stdio.h> #include <string.h> #include <dlfcn.h> #include <jni.h> typedef unsigned int u4; int main() { JavaVM* vm; JNIEnv* env; jint res; JavaVMInitArgs vm_args; JavaVMOption options[1]; options[0].optionString = "-Djava.class.path=."; vm_args.version=0x00010002; vm_args.options=options; vm_args.nOptions =1; vm_args.ignoreUnrecognized=JNI_TRUE; printf("[+] dlopen libdvm.so\n"); void *handle = dlopen("/system/lib/libdvm.so", RTLD_LAZY);//RTLD_LAZY RTLD_NOW if(!handle){ printf("[-] dlopen libdvm.so failed!!\n"); return 0; } //这里我先创建一个java虚拟机。因为JNI_ONload函数参数第一个参数为JavaVM。 typedef int (*JNI_CreateJavaVM_Type)(JavaVM**, JNIEnv**, void*); JNI_CreateJavaVM_Type JNI_CreateJavaVM_Func = (JNI_CreateJavaVM_Type)dlsym(handle, "JNI_CreateJavaVM"); if(!JNI_CreateJavaVM_Func){ printf("[-] dlsym failed\n"); return 0; } res=JNI_CreateJavaVM_Func(&vm,&env,&vm_args); void* si=dlopen("/data/local/tmp/libexecute_table.so",RTLD_LAZY); if(si == NULL){ printf("[-] dlopen err!\n"); return 0; } typedef jint (*FUN)(JavaVM* vm,void* res); FUN func_onload=(FUN)dlsym(si,"JNI_OnLoad"); u4 load=(u4)(dlsym(si,"JNI_OnLoad")); load&=~0xfff; while(*(u4*)load!=0x464C457F)load-=0x1000; printf("%x %x\n",func_onload,load); if(func_onload==NULL) return 0; func_onload(vm,NULL); return 0; }
我们找到正确JNI_OnLoad地址后在IDA中将其第一个参数的类型定义为JavaVM*
,找到其中GetEnv
的调用,将其第二个参数的类型定义为JNIEnv*
,顺路找到了调用RegisterNatives
的地址。(可能需要右键“Force call type”来显示全所有参数)
然后将上述程序调试运行,运行到RegisterNatives
处为止(注意由于这里FindClass
返回值为空,需要手工过掉一个跳转),成功得到lkdakjudajndn函数的注册地址为libexecute_table.so的基址+0xac98。
libexecute_table.so的基址+0xac98处的代码并不能直接F5,猜测可能是运行时需要动态修改所致。
分析判断函数
获取真实内容
直接将IDA挂载进正在运行的app,成功了。这说明该app并没用采用fork自身后ptrace自身这样变态的反调试。不过接下来一旦运行,似乎很快就会崩溃,所以我们放着程序不要运行。
按Ctrl+S在内存中找到位于libexecute_table.so的基址+0xac98的lkdakjudajndn函数,这里可以成功F5。
命名各个函数
里面一些乱七八糟的函数其实是标准库中的字符串函数,利用标准库函数内的英文提示、部分函数的导出符号和对函数行为的简单分析(同时比对我自己用g++生成的程序的IDA反汇编结果),我们可以命名大部分函数,如下:
int __fastcall check(JNIEnv *a1, int a2, void *a3) { //省略定义 v3 = (*a1)->GetStringUTFChars(a1, a3, 0); strcon(&v24, (int)v3, (int)&v89); strcon(&v33, (int)"A782E192B81NICAIsan38Qz", (int)&v89); sub_90C8524C((int)&v68, 24); sub_90C8F504(&v69, 1); sub_90C84D44(&v34, &v70); v4 = 0x912; if ( !((int (__fastcall *)(char *, char *))operator_equal)(&v33, &v34) ) v4 = 1; if ( ((int (__fastcall *)(char *, char *))operator_notequal)(&v34, &v33) ) { sub_90C8524C((int)&v89, 24); sub_90C8F504(&v90, 33687); sub_90C84D44(&v86, &v91); v4 = ((int (__fastcall *)(char *, const char *))operator_equal_)(&v86, "0d87a"); strdel(&v86); sub_90C84BAC(&v89); } if ( v4 == 1 ) v4 = 0x90CC9254; strdel(&v34); sub_90C84BAC(&v68); strdel(&v33); if ( (unsigned int)(*(_DWORD *)(v24 - 12) - 10) <= 10 ) { cpx(&v26, &v24); strcon(&failed, (int)&empty, (int)&v23); ((void (__fastcall *)(int *, int *, signed int, _DWORD, int *, int *))substr)( &others, &v26, 1, *(_DWORD *)(v26 - 12), &v26, &failed); ((void (__fastcall *)(char *, int *, _DWORD, signed int))substr)(&fist, &v26, 0, 1); merge(&v58, &others, (int)&fist); cp(&v26, &v58); strdel(&v58); strdel(&fist); strdel(&others); for ( i = 0; i < *(_DWORD *)(v26 - 12); ++i ) sub_90C905E8(&failed, *(_BYTE *)(*(unsigned __int8 *)(v26 + i) - 0x6F38103E)); cp(&v26, &failed); assign((int)&failed, (const char *)&empty); strcon(&v44, (int)"BQ366EYdQs3716UCANDOIT666", (int)&v89); sub_90C8524C((int)&v80, 24); sub_90C8F504(&v81, 0x7265); sub_90C84D44(&v45, &v82); ((void (__fastcall *)(int *, int *, _DWORD, signed int))substr)(&v49, &v44, 0, 10); cp(&v44, &v49); strdel(&v49); if ( ((int (__fastcall *)(int *, char *))operator_equal)(&v44, &v45) ) v6 = 50; else v6 = 1; strdel(&v45); sub_90C84BAC(&v80); strdel(&v44); for ( j = 0; j < *(_DWORD *)(v26 - 12); ++j ) sub_90C905E8(&failed, ((*(_BYTE *)(v26 + j) & 0xF) << 4 * v6) | (*(_BYTE *)(v26 + j) >> 4)); cp(&v26, &failed); assign((int)&failed, (const char *)&empty); strcon(&v42, (int)"A782E192B81NICAIsan38Qz", (int)&v89); sub_90C8524C((int)&v77, 24); sub_90C8F504(&v78, 3373080); sub_90C84D44(&v43, &v79); v8 = 3373029; if ( !((int (__fastcall *)(char *, char *))operator_equal)(&v42, &v43) ) v8 = 3373080; if ( ((int (__fastcall *)(char *, char *))operator_notequal)(&v43, &v42) ) { sub_90C8524C((int)&v89, 24); sub_90C8F504(&v90, 33687); sub_90C84D44(&v86, &v91); v8 = ((int (__fastcall *)(char *, const char *))operator_equal_)(&v86, "0d87a"); strdel(&v86); sub_90C84BAC(&v89); if ( v8 ) v8 = -1865641388; } strdel(&v43); sub_90C84BAC(&v77); strdel(&v42); v9 = 0; do { sub_90C905E8(&failed, *(_BYTE *)(v26 + v8 + v9) ^ xorbyte1); sub_90C905E8(&failed, xorbyte2 ^ *(_BYTE *)(v26 + v9 + 1)); sub_90C905E8(&failed, xorbyte3 ^ *(_BYTE *)(v26 + v9 + 2)); sub_90C905E8(&failed, *(_BYTE *)(v26 + v9 + 3) ^ xorbyte4); v9 += 4; } while ( v9 != 16 ); cp(&v26, &failed); assign((int)&failed, (const char *)&empty); strcon(&v40, (int)"BQ366EYdQs3716UCANDOIT666", (int)&v89); sub_90C8524C((int)&v74, 24); sub_90C8F504(&v75, 0xAE86); sub_90C84D44(&v41, &v76); ((void (__fastcall *)(int *, int *, _DWORD, signed int))substr)(&v48, &v40, 0, 10); cp(&v40, &v48); strdel(&v48); if ( ((int (__fastcall *)(int *, char *))operator_equal)(&v40, &v41) ) v10 = 50; else v10 = 1; strdel(&v41); sub_90C84BAC(&v74); v11 = 0; strdel(&v40); while ( 1 ) { v13 = *(_DWORD *)(v26 - 12); if ( v11 >= v13 >> 1 ) break; v12 = *(_BYTE *)(v26 + 2 * v11); sub_90C905E8(&failed, *(_BYTE *)(v26 + v10 + 2 * v11)); sub_90C905E8(&failed, v12); ++v11; } if ( v13 & 1 ) { ((void (__fastcall *)(char *, int *, unsigned int, signed int))substr)(&v55, &v26, v13 - 1, 1); cp2(&failed, &v55); strdel(&v55); } cp(&v26, &failed); assign((int)&failed, (const char *)&empty); while ( v8 < *(_DWORD *)(v26 - 12) ) sub_90C905E8(&failed, *(_BYTE *)(*(unsigned __int8 *)(v26 + v8++) - 1865945150)); cp(&v26, &failed); assign((int)&failed, (const char *)&empty); strcon(&v38, (int)"BQ366EYdQs3716UCANDOIT666", (int)&v89); sub_90C8524C((int)&v71, 24); sub_90C8F504(&v72, 0x937872A); sub_90C84D44(&v39, &v73); ((void (__fastcall *)(int *, int *, _DWORD, signed int))substr)(&v47, &v38, 0, 10); cp(&v38, &v47); strdel(&v47); if ( ((int (__fastcall *)(int *, char *))operator_equal)(&v38, &v39) ) v14 = 50; else v14 = 1; strdel(&v39); sub_90C84BAC(&v71); strdel(&v38); ((void (__fastcall *)(int *, int *, signed int, _DWORD))substr)(&v53, &v26, v14, *(_DWORD *)(v26 - 12)); ((void (__fastcall *)(char *, int *, _DWORD, signed int))substr)(&v52, &v26, 0, v14); merge(&v54, &v53, (int)&v52); cp(&failed, &v54); strdel(&v54); strdel(&v52); strdel(&v53); cpx(&v50, &failed); strcon(&v35, (int)"A3Cw6Gb0OZWPU52s", (int)&v89); strcon(&v36, (int)&empty, (int)&v89); strcon(&v37, (int)&empty, (int)&v89); v15 = 0; while ( v15 < *(_DWORD *)(v50 - 12) ) { v16 = *(_DWORD *)&v35; sub_90C901A8(&v50); sub_90C905E8(&v36, *(_BYTE *)(v16 + ((unsigned int)*(unsigned __int8 *)(v50 + v15) >> 4))); v17 = *(_DWORD *)&v35; sub_90C901A8(&v50); v18 = *(unsigned __int8 *)(v17 + (*(_BYTE *)(v50 + v15) & 0xF)); v46 = 0x90CB2E30; cpxx(&v46, (char *)(*((_DWORD *)v37 - 3) + 1)); ((void (__fastcall *)(int *, signed int, int))appender)(&v46, 1, v18); cper(&v46, &v37); cp((int *)&v37, &v46); ++v15; strdel(&v46); } merge(&v51, &v36, (int)&v37); strdel(&v37); strdel(&v36); strdel(&v35); cp(&failed, &v51); strdel(&v51); strdel(&v50); strdel(&v26); strcon(&v28, (int)"3d8ahnb1", (int)&v80); strcon(&v59, (int)"UYetrq736UMayFindMe233", (int)&v89); sub_90C8524C((int)&v83, 24); ((void (__fastcall *)(char *, _BYTE **))unk_90C860A4)(&v84, &v28); sub_90C84D44(&v60, &v85); sub_90C901A8((int *)&v28); v19 = (unsigned __int8)*v28; if ( ((int (__fastcall *)(char *, char *))operator_equal)(&v59, &v60) ) { strcon(&v89, (int)"DABD786ABH", (int)&v86); if ( v19 == 1 ) { v20 = "8a7d9Vduya"; } else if ( v19 >= 1 ) { if ( v19 == 2 ) v20 = "73812huvVQ"; else v20 = "daj87YBDASYBvy"; } else { v20 = "UDHA47DBsd"; } assign((int)&v28, v20); cp2(&v28, &v89); strdel(&v89); } if ( ((int (__fastcall *)(char *, char *))operator_notequal)(&v59, &v60) ) { assign((int)&v28, (const char *)&empty); strcon(&v89, (int)"DU8NABvA", (int)&v86); ((void (__fastcall *)(char *, char *, _DWORD, signed int))substr)(&v62, &v89, 0, 1); cp2(&v28, &v62); strdel(&v62); if ( operator_notequal_(&v28, &empty) ) assign((int)&v28, (const char *)&empty); ((void (__fastcall *)(char *, char *, signed int, signed int))substr)(&v61, &v89, 1, 2); cp2(&v28, &v61); strdel(&v61); if ( operator_notequal_(&v28, &empty) ) assign((int)&v28, (const char *)&empty); strdel(&v89); } cpx(&v29, (int *)&v28); strdel(&v60); sub_90C84BAC(&v83); strdel(&v59); ((void (__fastcall *)(char *, char *, signed int))operator_plus)(&v30, &v29, '3'); ((void (__fastcall *)(char *, char *, signed int))operator_plus)(&v31, &v30, 'w'); ((void (__fastcall *)(char *, char *, signed int))operator_plus)(&v32, &v31, 'w'); ((void (__fastcall *)(int *, char *, signed int))operator_plus)(&v27, &v32, '3'); strdel(&v32); strdel(&v31); strdel(&v30); strdel(&v29); strdel(&v28); if ( operator_notequal_(&v27, &empty) ) { append((int)&v27, (const char *)&str_U5); sub_90C905E8(&v27, '3'); sub_90C905E8(&v27, 'w'); } if ( *(_DWORD *)(v27 - 12) ) { append((int)&v27, "OAWG"); sub_90C905E8(&v27, '3'); sub_90C905E8(&v27, '3'); sub_90C905E8(&v27, '3'); } sub_90C905E8(&v27, 'w'); sub_90C905E8(&v27, 'w'); append((int)&v27, "PZ56GGw0PO02OUW"); if ( ((int (__fastcall *)(int *, int *))operator_equal)(&failed, &v27) && *(_DWORD *)(v24 - 12) == 16 ) { strcon(&v63, (int)"BQ366EYdQs3716UCANDOIT666", (int)&v89); sub_90C8524C((int)&v89, 24); sub_90C8F504(&v90, 0); sub_90C84D44(&v64, &v91); ((void (__fastcall *)(int *, int *, _DWORD, signed int))substr)(&v65, &v63, 0, 10); cp(&v63, &v65); strdel(&v65); if ( ((int (__fastcall *)(int *, char *))operator_equal)(&v63, &v64) ) v4 = 50; else v4 = 1;//ok strdel(&v64); sub_90C84BAC(&v89); v21 = &v63; } else { strcon(&v66, (int)"A782E192B81NICAIsan38Qz", (int)&v89); sub_90C8524C((int)&v86, 24); sub_90C8F504(&v87, 1); sub_90C84D44(&v67, &v88); v4 = 2322; if ( !((int (__fastcall *)(char *, char *))operator_equal)(&v66, &v67) ) v4 = 1; if ( ((int (__fastcall *)(char *, char *))operator_notequal)(&v67, &v66) ) { sub_90C8524C((int)&v89, 24); sub_90C8F504(&v90, 33687); sub_90C84D44(&v83, &v91); v4 = ((int (__fastcall *)(char *, const char *))operator_equal_)(&v83, "0d87a"); strdel(&v83); sub_90C84BAC(&v89); } if ( v4 == 1 ) v4 = -1865641388; strdel(&v67); sub_90C84BAC(&v86); v21 = (int *)&v66; } strdel(v21); strdel(&v27); strdel(&failed); } strdel(&v24); return v4; }
肉眼识别花指令
这里面有少量的花指令。
以
if ( ((int (__fastcall *)(int *, char *))operator_equal)(&v63, &v64) ) v4 = 50; else v4 = 1;
结尾的指令用于产生一个常量1。
而以
if ( v4 == 1 ) v4 = -1865641388;
结尾的指令用于产生一个常量0。
还有一些产生了一个字符串,然后if(串非空)将串置空;
这样的花指令。
分析流程
将根据输入的字符串从前往后看和根据返回值从后往前看两种方式相结合,阅读几遍程序,可发现程序做了如下操作:
1.第一位放到最后
--每位按表置换
2.高低位交换
3.四位一组异或
4.奇偶位交换
--每位按表置换
5.第一位放到最后
6.高四位内容(x>>4)替换为"A3Cw6Gb0OZWPU52s"[x>>4]产生A;低四位同理产生B(反序)
7.判断"3ww3 U5 3w OAWG 333 ww PZ56GGw0PO02OUW"(去掉空格)==A+B
这里比较容易遗漏的是两处“--每位按表置换”,代码类似于:
for ( i = 0; i < *(_DWORD *)(v26 - 12); ++i ) sub_90C905E8(&failed, *(_BYTE *)(*(unsigned __int8 *)(v26 + i) - 0x6F38103E));
乍一看令人懵逼,其实: -0x6F38103E == 0x90C7EFC2 (DWORD下),而后者是so加载基址附近的一处内容。
这样,我们就可以把在0x90C7EFC2处的置换表直接从IDA中复制出来。
题目的含义
我们把0x90C7EFC2减去so加载基址,新开一个IDA,直接打开的so后跳转到所得地址,可以发现这一处表被分析成了arm指令,也就是说,题意就是可执行的表(Executable table)。
获取flag
我们可编写程序执行上述流程的逆过程来得到flag,我的程序如下:
#include <cstdio> #include <iostream> #include <string> #include <cstring> using namespace std; unsigned char ans[20]; void checkin(string a,string b){ const char* x=b.c_str(); for(int i=0;i<16;i++){ int ptr=strchr(x,a[i])-x; if(ptr<0||ptr>=16)puts("err"); ans[i]|=ptr<<4; } for(int i=16;i<32;i++){ int ptr=strchr(x,a[i])-x; if(ptr<0||ptr>=16)puts("err"); ans[31-i]|=(ptr)&0xf; } } void revc(string b){ const char* x=b.c_str(); string u="",v=""; for(int i=0;i<16;i++){ u+=x[ans[i]>>4]; v+=x[ans[i]&0xf]; } puts((u+v).c_str()); } void revone(){ char ansb[20]; ansb[0]=ans[15]; memcpy(ansb+1,ans,15); memcpy(ans,ansb,16); } void binout(){ printf("> "); for(int i=0;i<16;i++)printf("%02x ",ans[i]); for(int i=0;i<16;i++)printf("%c",ans[i]>32?ans[i]:'_'); printf(" <\n"); } unsigned char table[]={ 0x40, 0x50, 0x78, 0x7A, 0x29, 0x88, 0xF7, 6, 0x21, 9, 0xF3, 0x5C, 0x95 , 0xAE, 0x66, 0x12, 0x8F, 0x85, 0xC8, 0x5A, 0xBF, 0x33, 0x3D, 0x86, 0x90 , 0x8C, 0xED, 0xD5, 0x8B, 0xA4, 0xC5, 0xC7, 0xEA, 0xF6, 0x79, 0x1E, 0x3C , 0xBA, 0x97, 0x4E, 0x38, 0x60, 8, 0xDD, 0xFA, 0xB3, 0xDE, 0x77, 0x81 , 0x41, 0x19, 0xF4, 0x52, 0x6B, 0xFF, 0xD8, 0x2A, 0xC2, 0xBC, 0xB9, 0xE7 , 0x91, 0xE9, 0x54, 0x82, 0xAD, 0x7E, 0x11, 0x35, 0x93, 0xB0, 0xA1, 0x18 , 0xC4, 0x53, 0xA, 0x74, 0x2F, 0xE2, 0x17, 0x98, 0xC, 0x70, 0x92, 0x47 , 0x64, 0x16, 0xFE, 0x75, 0x83, 0x37, 0x8D, 7, 0x72, 0x25, 4, 0xB7, 0xC9 , 0xCE, 0xE, 0x9E, 0xEB, 0xCF, 0xB1, 0xDB, 0x71, 0x56, 0xAF, 0x39, 0xF0 , 0xBB, 0xBD, 0x46, 0x32, 0xE6, 0x9F, 0x4F, 0x1B, 0x4D, 0x68, 0xF2, 0x4B , 0x2E, 0xCB, 0x20, 0xD2, 0xB, 0xA5, 0xEE, 0xE1, 0xA9, 0x2B, 0x84, 0x14 , 0x67, 0x63, 0x6F, 0x3E, 0x7F, 0xFD, 0xB6, 0xFC, 0x55, 0x7C, 0x5F, 0xF8 , 0x4C, 0x65, 0x2C, 0x30, 0xEF, 0x48, 0xD7, 0xD, 0xF, 0x1A, 0x5E, 0xC0 , 0x3A, 0x57, 0x6A, 0x31, 0, 0xF1, 0x59, 0x10, 0xB8, 0x9A, 0x43, 0x73 , 0xA3, 0x6E, 0x26, 0x1D, 0x13, 0x15, 0x89, 0x5D, 0xDA, 0x61, 0xD1, 0x6C , 0xD3, 0xE0, 0xD9, 0x1F, 0xD4, 0x49, 0xEC, 0xE3, 0xD0, 0x34, 0x36, 0xC6 , 0x24, 0xE4, 0xF5, 0xAA, 0x9B, 0xB2, 0x4A, 0xDF, 0xAC, 0x96, 0xDC, 0xE8 , 0xA0, 0xF9, 0xC1, 0x9C, 0xCA, 0x9D, 0x27, 0xC3, 0xBE, 0x87, 0x28, 0xCC , 0x99, 0xE5, 0x45, 0x58, 0x94, 0x23, 0x22, 0xFB, 2, 1, 3, 0x8A, 0x7B , 0xB5, 0x1C, 0xA7, 0x44, 0xCD, 0xA2, 0x51, 0x8E, 0x3F, 0x42, 0xD6, 0x69 , 0xAB, 0x62, 0x3B, 0x7D, 0xA6, 5, 0x2D, 0xA8, 0x80, 0x6D, 0xB4, 0x76 , 0x5B}; int main(){ memset(ans,0,sizeof ans); string x="3ww3U53wOAWG333wwPZ56GGw0PO02OUW"; string p="A3Cw6Gb0OZWPU52s"; checkin(x,p); revc(p); binout(); revone(); binout(); for(int i=0;i<16;i++){ unsigned char q=ans[i],p=0; while(table[p]!=q)p++; ans[i]=p; } binout(); for(int i=0;i<16;i+=2){ swap(ans[i],ans[i+1]); } binout(); for(int i=0;i<16;i+=4){ ans[i]^=0x83; ans[i+1]^=0xae; ans[i+2]^=0x33; ans[i+3]^=0x23; } binout(); for(int i=0;i<16;i++){ ans[i]=(ans[i]>>4)|((ans[i]&0xf)<<4); } binout(); for(int i=0;i<16;i++){ unsigned char q=ans[i],p=0; while(table[p]!=q)p++; ans[i]=p; } binout(); revone(); binout(); return 0; }
输出最后一行的C0ngRa7U1AtIoN2U
即为所求flag。
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
赞赏
|
|
---|---|
|
哈哈,看你在群里发的,就知道你看懂题目意思了
|