2025 中等职业教育大赛_网络安全赛项部分解析(2)

张开发
2026/4/19 17:47:18 15 分钟阅读

分享文章

2025 中等职业教育大赛_网络安全赛项部分解析(2)
提示文章写完后目录可以自动生成如何生成可参考右边的帮助文档前言记一次Android逆向一。 先分析题目第二题要找到逆向so中加密函数的密钥其实此处的so 指的是一个文件.so 是安卓的底层库文件里面是 C 语言写的核心代码相当于函数库二 分析so文件将test.apk 改为 test.zip 的压缩包形式 找到/lib/armeabi-v7a/目录下的文件就是so文件比赛给的工具是cutter(大佬有不同理解望指正)一言难尽JNI_onload()函数是 SO 库被加载时自动执行的 “入口函数”它的唯一作用注册 Native 方法让 Java 能调用 C/C 代码。所以先分析一下JNI_onload()函数未找到和flag相关的逻辑接下来逐个分析可疑函数找到 all()函数为什么找到了all函数呢其实all函数是判断手机环境得到flag的一个函数 不是加密函数根据题目要求应该优先找加密函数 类似以encrytp encode 这种命名的函数 主要是题目有点误导思路而且cutter 反编译的效果不太好代码是混淆的看不出逻辑// WARNING:[r2ghidra]Var arg_ch is stack pointer based,whichis not supportedfordecompilation. // WARNING:[r2ghidra]Var arg_240h is stack pointer based,whichis not supportedfordecompilation. // WARNING:[r2ghidra]Var arg_14h is stack pointer based,whichis not supportedfordecompilation. // WARNING:[r2ghidra]Var arg_8h is stack pointer based,whichis not supportedfordecompilation. void all(void){undefined2 uVar1;uint32_t uVar2;int32_t iVar3;undefined4 uVar4;undefined4 uVar5;undefined4 extraout_r1;undefined4 uVar6;undefined4 placeholder_1;undefined4 extraout_r1_00;int32_t *piVar7;undefined auStack376[4];undefined auStack372[4];undefined4 uStack368;undefined4 uStack364;undefined4 uStack360;undefined4 uStack352;undefined4 uStack348;undefined4 uStack280;undefined4 uStack276;undefined4 uStack272;undefined4 uStack264;undefined4 uStack260;uint8_t auStack192[16];uint8_t auStack176[16];unkbyte7 Stack160;undefined uStack153;unkbyte7 Stack152;unkbyte7 Stack144;undefined uStack137;unkbyte7 Stack136;uint16_t auStack124[16];uint32_t auStack92[8];int32_t iStack60;undefined *puStack40;puStack40stack0xfffffff8;piVar7*(int32_t **)(*(int32_t *)0x14c4 0x135e);iStack60*piVar7;__system_property_get(*(int32_t *)0x14c8 0x136c, auStack92);__system_property_get(*(int32_t *)0x14cc 0x1376, auStack124);uVar2*(uint32_t *)0x14b8;if(auStack92[0]*(uint32_t *)0x14b8){uVar2(uint32_t)auStack124[0];}if((auStack92[0]!*(uint32_t*)0x14b8||uVar2!0x39)(iVar3fcn.000009b4(*(int32_t*)0x14d00x13ac),uVar5*(undefined4*)0x14c0,uVar6*(undefined4*)0x14bc,iVar3!1)){uVar1CONCAT11(*(undefined *)(*(int32_t *)0x14d4 0x13c7), *(undefined *)(*(int32_t *)0x14d4 0x13c7));uVar4CONCAT22(uVar1, uVar1);Stack136(unkbyte7)(CONCAT44(uVar4, uVar4)8);Stack144(unkbyte7)*(undefined8 *)(*(int32_t *)0x14d4 0x13c0);uStack137(undefined)((uint64_t)*(undefined8*)(*(int32_t*)0x14d40x13c0)0x38);uVar1CONCAT11(*(undefined*)(*(int32_t*)0x14d80x13e5),*(undefined*)(*(int32_t*)0x14d80x13e5));uVar4CONCAT22(uVar1, uVar1);Stack152(unkbyte7)(CONCAT44(uVar4, uVar4)8);uStack368(undefined4)*(undefined8 *)0x14a8;uStack364(undefined4)*(undefined8 *)0x14b0;uStack264*(undefined4 *)0x14bc;Stack160(unkbyte7)*(undefined8 *)(*(int32_t *)0x14d8 0x13de);uStack153(undefined)((uint64_t)*(undefined8 *)(*(int32_t *)0x14d8 0x13de)0x38);uStack260*(undefined4 *)0x14c0;uStack280uStack368;uStack276uStack364;uStack272uStack364;uVar4__strlen_chk(Stack144, 0xf);fcn.00000984(uStack280,Stack144, uVar4);fcn.000009cc(uStack280, auStack176);uStack352uVar6;uStack348uVar5;uStack360uStack364;uVar5__strlen_chk(Stack160, 0xf);fcn.00000984(uStack368,Stack160);fcn.000009cc(uStack368, auStack192);fopen(*(int32_t *)0x14dc 0x1454, *(int32_t *)0x14e0 0x1456);iVar30;uVar6extraout_r1;while(iVar3!8){fcn.000014e8(auStack372, uVar6, uVar5,(uint16_t)auStack176[iVar3]);fcn.000014e8(auStack376, placeholder_1, uVar5,(uint16_t)auStack192[iVar3]);iVar3iVar3 1;uVar6extraout_r1_00;}}if(*piVar7iStack60){return;}// WARNING: Subroutine does notreturn__stack_chk_fail();}对此函数进行分析发现调用了__strlen_chk 函数 之后又发现CONCAT11 CONCAT22 cutter自动生成的 字节拼接语法strlen_chk 就是用来求字符串长度 CONCAT j就是拼接字节这都是对字符串进行调用的函数 由此推断此函数和flag有关uVar4__strlen_chk(Stack144, 0xf);就是求 Stack144 这个字符串的长度最多 15 个字符stack这个字符串极有可能就是被加密的数据Stack144(unkbyte7)*(undefined8 *)(*(int32_t *)0x14d4 0x13c0);通过这一行代码可以看出 Stack144的值是怎么来的先将0x14d4转指针 再解引用取值 再加上 0x13c0的值 然后再*(undefined8 *)读取8个字节 然后 (unkbyte7)取一个字节的值先找到0x14d4这个地址的值此处被转换为指针类型了由此可以看出 0x14d4这个地址的值为 0x000020f0 然后加上 0x13c0 的值得到结果值为0x34b0 现在找这个地址的值成功找到两个关键字符串第一个bili_2233_3322 就是 Stack144的值(指针指向的)了现在继续往下分析fcn.00000984(uStack280,Stack144, uVar4);紧接着 Stack144 以及 uVar4 被 fcn.00000984 函数当作两个参数被调用推测fcn.00000984函数就是加密函数现在分析fcn.00000984函数void fcn.00000984(void){// WARNING: Could not recover jumptable at 0x0000098c. Too many branches // WARNING: Treating indirect jump as call(*_reloc.EU)();return;}跳转函数跳转到EU了接着分析// WARNING: Restarted to delay deadcode eliminationforspace: stack // WARNING:[r2ghidra]Var arg_ch is stack pointer based,whichis not supportedfordecompilation. int32_t EU(int16_t arg1, int16_t arg2, int16_t arg3){uint32_t *puVar1;uint32_t uVar2;int32_t iVar3;uint32_t uVar4;int32_t iVar5;uint32_t uVar6;uint32_t uVar7;uint32_t uVar8;uint32_t uVar9;int32_t iVar10;int32_t unaff_r7;int32_t in_stack_00000000;uint32_t *in_stack_0000000c;uVar6SEXT24(arg3);iVar5(int32_t)arg2;puVar1(uint32_t *)(int32_t)arg1;uVar7*puVar1;uVar4puVar1[1];uVar8uVar7 uVar6 *8;*puVar1uVar8;uVar2(uVar70x17)0x1a;uVar90x40 - uVar2;if(uVar8uVar7){uVar4uVar4 1;}if(uVar8uVar7){puVar1[1]uVar4;}puVar1[1]uVar4 (uVar60x1d);if(uVar6uVar9){uVar90;}else{__aeabi_memcpy(uVar2 (int32_t)(puVar1 6), iVar5, uVar9);fcn.00000954(puVar1 2, puVar1 6);while(uVar9 0x40uVar6){fcn.00000954(puVar1 2, iVar5 uVar9);uVar9uVar9 0x40;}uVar20;}iVar3uVar2 (int32_t)puVar1;iVar5iVar5 uVar9;uVar2in_stack_00000000 - iVar5;while(uVar4uVar2, uVar4!0){iVar10iVar5 (uVar41);uVar7*(uint32_t *)(unaff_r7 iVar10 *8);uVar2uVar41;if((uVar7|(uVar70x40000000)1) unaff_r7 iVar10 *8*in_stack_0000000c){iVar5iVar10 1;uVar2uVar4 ~(uVar41);}}*(int32_t *)(iVar3 0x18)iVar5;*(uint32_t *)(iVar3 0x1c)uVar6 - uVar9;*(undefined **)(iVar3 0x20)stack0xfffffff8;returniVar3 0x24;}其实EU就是 标准 MD5 哈希算法的 Update 函数while(uVar9 0x40uVar6){fcn.00000954(...);uVar9uVar9 0x40;// 每次 64}每次必须处理 64 字节数据 只有 MD5 / SHA1 会这样写MD5 最后要补长度单位是 bit不是 byte所以代码一定会 ×8 代码如下uVar8uVar7 uVar6 *8;所以由此判断加密函数为md5 但是哈希算法其实是没有密钥的没有密钥题目要找的加密函数的密钥其实就是没有的但是这道题还是有答案的就是将Stack144 指向的字符串 进行md5加密至于为什么没话说此为flag b1815a96bcbe342afd061ff6f818ec4e三 第三题怎么做第三题就是直接找flag了 但是按照比赛给的工具cutter 反编译出来的代码实在是不规范 分析不出来逻辑只能用ida pro 了void __fastcall all(JNIEnv *env){bool v1;// zf size_t v2;// r0 size_t v3;// r0 unsigned int v4;// r1 const char *v5;// r2 int i;// r4 unsigned int v7;// r1 const char *v8;// r2 char v9[4];//[sp0h][bp-170h]BYREF char dest[4];//[sp4h][bp-16Ch]BYREF EC v11;//[sp8h][bp-168h]BYREF EC v12;//[sp60h][bp-110h]BYREF unsigned __int8 v13[16];//[spB8h][bp-B8h]BYREF unsigned __int8 digest[16];//[spC8h][bp-A8h]BYREF char v15[16];//[spD8h][bp-98h]BYREF char v16[20];//[spE8h][bp-88h]BYREF _WORD v17[16];//[spFCh][bp-74h]BYREF _DWORD v18[8];//[sp11Ch][bp-54h]BYREF _system_property_get(ro.product.cpu.abi, v18);// 读取 CPU 架构arm64-v8a/armeabi-v7a 等存入 v18 _system_property_get(ro.build.version.release, v17);// 读取安卓系统版本号如13/14/15存入 v17 v1v18[0]3553400;// // 检查CPU架构的特定值(3553400对应x86字符串的ASCII值)if(v18[0]3553400)v1v17[0]57;//57转16 进制 0x39 转字符9if(!v1j_f_e(/data/2233)!1)必须满足CPU 是 x86架构 系统版本为9则 v1true j_f_e()函数就是判断文件或者路径是否存在是则返回1 不是则返回0 所以还要满足/data/2233这个路径不存在 则执行下面的逻辑{strcpy(v16,bili_2233_3322);//把字符串 bili_2233_3322 放进 v16 *(_QWORD *)v12.count0;//初始化 MD5 哈希上下文 *(_QWORD *)v12.state0xEFCDAB8967452301LL;//初始化 v12.state[2]-1732584194;//初始化 strcpy(v15,bili_3322_2233);把字符串 bili_3322_2233 放进 v15 v12.state[3]271733878;v2_strlen_chk(v16, 0xFu);j_EU(v12,(unsigned __int8 *)v16, v2);// 对bili_2233_3322计算 MD5得到16字节 j_EF(v12, digest);//结果存在 digest *(_QWORD *)v11.count0;*(_QWORD *)v11.state0xEFCDAB8967452301LL;v11.state[2]-1732584194;v11.state[3]271733878;v3_strlen_chk(v15, 0xFu);j_EU(v11,(unsigned __int8 *)v15, v3);//对bili_3322_2233计算 MD5 得到16字节 j_EF(v11, v13);//结果存在 v13 fopen(/data/2233,a);创建 /data/2233 文件 模式a追加/读写for(i0;i!8;i){sprintf(dest, v4, v5, digest[i]);// 把两个 MD5 值拼成字符串 sprintf(v9, v7, v8, v13[i]);//此处的for循环时先 print(digest[i])也就是先打印digestv16加密后的数据的一个字节 然后再print(v13[i])也就是v15 经过md5加密后的一个字节 循环只有8次 正好16字节32位 对应MD5值 和题目要求}}}kali算出来两个字符串的MD5值python脚本算出flag的值 按照题目的格式答案为 b13981f4-5ae996d4-bc04be5b-34662a78四 预期解其实这道题预期解的话其实要用模拟器Android Studio 模拟一个安卓9 x86的手机 然后安装运行这个程序但是没有可用的系统镜像 CPU 也不支持 VT-x其实给了一个但是运行不了因为cpu的原因总结有什么地方讲的不对的希望大佬纠正

更多文章