-
-
[推荐]看雪·安恒 2020 KCTF 春季赛 | 十三题设计思路及解析
-
发表于: 2020-5-18 18:26 6626
-
在比赛过程中,金左手战队再次惊艳全场,5月16日深夜,金左手战队的ccfer拿到《猪突豨勇》的一血,成功登上攻击方排行榜的榜首。
赛题点评
本题点评由看雪密码学专家readyu提供:
最后一题, 金左手战队的ccfer大牛,拿下一血,以画龙点睛之笔收官了比赛。随便还发现了题目flag的hex字符大小写多解。
出题团队简介
团队简介:
毕业于广西大学,现居住于深圳,自带冷场天赋,专业拆台30年,常年承接各种相亲冷场,聚会拆台业务,有意向者可站内信联系。
设计思路
一、类型
二、Flag
三、题目设计
四、解题思路
├── rootfs.ext2
├── versatile-pb.dtb
└── zImage
qemu-system-arm -M versatilepb -kernel ./zImage -dtb ./versatile-pb.dtb -drive file=./rootfs.ext2,if=scsi -append "root=/dev/sda console=ttyAMA0,115200" -nographic
五、题目主题词
香江,珍宝画舫,食神争霸总决赛现场,衣香鬓影,名流云集。
周星星与糖牛正在激烈地争夺食神的宝座,他们最后都选择了在“黯然销魂蛋炒饭”这道菜上一决高下!
糖牛手捧着刚刚完成的“黯然销魂蛋炒饭”,忍不住傲然道:周星星,当年在灌江口,我跟你学厨。
第一年,你让我砍柴,我砍了整整一年的柴;
第二年,你让我挑水,我挑了整整一年的水;
第三年,你让我烧火,我烧了整整一年的火;
第四年,你让我扇风,我扇了整整一年的风;
直到第五年,你才肯教给我这道“黯然销魂蛋炒饭”。
这十年来,我每日苦练这道“黯然销魂蛋炒饭”,未敢有一日松懈。
今天,我完成了这道“黯然销魂蛋炒饭”的三十六重变化,每一种变化都能让食客获得一种独特的美食体验。如今,我看你怎么赢我!哈哈哈哈!哈哈哈哈!
谁料,周星星却仰天大笑道:糖牛,十五年前你跟我学厨,我就看出你脑后有反骨。
是以始终对你有所保留。虽然你教会了“黯然销魂蛋炒饭”的第三十六重变化,
可今天我要告诉你,我的“黯然销魂蛋炒饭”有七十二种变化。
真正的老饕,在品尝了我的“蛋炒饭”之后,你的三十六重变化每一重都会让人味同嚼蜡,你且说我如何不能赢你!!!哈哈哈哈!哈哈哈哈!
解析过程
一、小猪上路
.text:00010934 CMP R0, #0x20 //输入有效长度0x20,后面转成16字节 ... .text:00010940 LDR R12, =byte_23C28C //输入存储在全局变量 ... .text:00010A04 BL sub_1898C //decode_step1 .text:00010A08 MOV R11, SP .text:00010A0C BL sub_18598 //decode_step2 ... .text:00010AC0 MOV R0, R5 .text:00010AC4 BL sub_18718 //decode_step3 .text:00010AC8 CMP R0, #0 .text:00010ACC BNE loc_108BC .text:00010AD0 LDR R3, =aWelcomeToKanxu //"Welcome to KanXue CTF 2020" .text:00010AD4 LDR R2, [R3] .text:00010AD8 LDR R3, [R9] .text:00010ADC CMP R2, R3 //比较结果经过3步decode的结果和"Welcome to KanXue CTF 2020"前16字符比较 .text:00010AE0 BNE loc_108BC ... .text:00010B2C MOV R4, R0 //这里就是光明之巅 .text:00010B30 LDR R0, =aYouGotIt //"you got it[*]!\r" .text:00010B34 BL zz_printf
decode_step1
即“黯然销魂蛋炒饭”的前34个变化,除了代码长没啥,IDA中可F5,每个变化用不同的初始化向量重新生成key,每个变化的256字节置换表都不同的全局变量。
decode_step2
这个是比较难搞的部分,花了很多时间,是个js虚拟机,经高人看了看字节码便指点出这是个叫duktape的js引擎。
字节码在byte_2348CB,先加载字节码,然后调用函数h(hexstr(step1_result))计算返回结果。
decode_step3
首先注意到:
.text:00073324 ADD R1, PC, R1 ; "skcipher"
大帅锅同学这时候把调试方法和kernel展开文件准备好了,我学习了一下就继续分析算法了。
深入调试得到sub_C0171EE0是setkey,sub_C0171D74是decrypt。
int __fastcall zz_setkey(int a1, int a2) { int v2; // lr signed int v3; // r6 int v4; // r2 _DWORD *v5; // r1 int v6; // r6 int v7; // r4 int v8; // lr int v9; // r12 signed int v10; // r9 int v11; // r5 signed int v12; // t1 unsigned int v13; // r3 int v14; // ST00_4 int v15; // r3 int v16; // r3 int v17; // r2 int v18; // t1 int result; // r0 int v20; // [sp+4h] [bp-3Ch] int v21; // [sp+8h] [bp-38h] int v22; // [sp+Ch] [bp-34h] int v23; // [sp+10h] [bp-30h] int v24; // [sp+14h] [bp-2Ch] v2 = 0; v3 = 0x12578F07; v24 = 0; while ( 1 ) { v4 = *(unsigned __int8 *)(a2 + v2) | (*(unsigned __int8 *)(a2 + v2 + 1) << 8) | (*(unsigned __int8 *)(a2 + v2 + 2) << 16) | (*(unsigned __int8 *)(a2 + v2 + 3) << 24); *(int *)((char *)&v20 + v2) = (((unsigned int)v4 ^ __ROR4__(v4, 16)) >> 8) & 0xFFFF00FF ^ __ROR4__(v4, 8) ^ v3; v2 += 4; if ( v2 == 16 ) break; v3 = *(_DWORD *)((char *)&unk_C03A0324 + v2); } v5 = &unk_C03A0334; v6 = v20; v7 = v21; v8 = v22; v9 = v23; v10 = 0x14ECBD93; v11 = a1 - 4; while ( 1 ) { v13 = v9 ^ v7 ^ v8 ^ v10; HIBYTE(v14) = byte_C03A0224[v13 >> 24]; BYTE1(v14) = byte_C03A0224[BYTE1(v13)]; BYTE2(v14) = byte_C03A0224[BYTE2(v13)]; LOBYTE(v14) = byte_C03A0224[(unsigned __int8)v13]; v15 = __ROR4__(v14, 11) ^ __ROR4__(v14, 24) ^ v14 ^ v6; *(_DWORD *)(v11 + 4) = v15; v11 += 4; if ( v5 == (_DWORD *)&unk_C03A03B0 ) break; v6 = v7; v7 = v8; v8 = v9; v9 = v15; v12 = v5[1]; ++v5; v10 = v12; } v16 = a1 + 128; v17 = a1 + 124; do { v18 = *(_DWORD *)(v16 - 4); v16 -= 4; *(_DWORD *)(v17 + 4) = v18; v17 += 4; } while ( a1 != v16 ); result = 0; if ( v24 ) sub_C0017628(); return result; } int __fastcall zz_decrypt(_DWORD *key, _BYTE *out, _BYTE *in) { _DWORD *v3; // r6 _BYTE *v4; // r4 int *v5; // lr int v6; // r3 int v7; // r5 int v8; // r12 _DWORD *v9; // r0 int v10; // r6 int v11; // r4 int i; // lr int v13; // t1 unsigned int v14; // r3 int v15; // ST00_4 unsigned int v16; // r3 _BYTE *v17; // r4 int *v18; // lr int result; // r0 int v20; // t1 int v21; // [sp+4h] [bp-34h] int v22; // [sp+8h] [bp-30h] int v23; // [sp+Ch] [bp-2Ch] int v24; // [sp+10h] [bp-28h] int v25; // [sp+14h] [bp-24h] v3 = key + 0x30; v4 = in + 16; v25 = 0; v5 = &v21; do { v6 = (unsigned __int8)*in | ((unsigned __int8)in[1] << 8) | ((unsigned __int8)in[2] << 16) | ((unsigned __int8)in[3] << 24); in += 4; *v5 = (((unsigned int)v6 ^ __ROR4__(v6, 16)) >> 8) & 0xFFFF00FF ^ __ROR4__(v6, 8); ++v5; } while ( in != v4 ); v7 = v21; v8 = v22; v9 = key + 0x2F; v10 = (int)(v3 + 0x1F); v11 = v23; for ( i = v24; ; i = v16 ) { v13 = v9[1]; ++v9; v14 = v8 ^ v11 ^ v13 ^ i; HIBYTE(v15) = byte_C03A0224[v14 >> 24]; BYTE1(v15) = byte_C03A0224[BYTE1(v14)]; BYTE2(v15) = byte_C03A0224[BYTE2(v14)]; LOBYTE(v15) = byte_C03A0224[(unsigned __int8)v14]; v16 = __ROR4__(v15, 20) ^ __ROR4__(v15, 28) ^ v15 ^ __ROR4__(v15, 12) ^ __ROR4__(v15, 6) ^ v7; v7 = v8; if ( v9 == (_DWORD *)v10 ) break; v8 = v11; v11 = i; } v22 = v11; v23 = i; v21 = v8; v24 = v16; v17 = out + 16; v18 = &v23; while ( 1 ) { result = (unsigned __int64)v16 >> 16; out[3] = v16; out[2] = v16 << 16 >> 24; out[1] = result; *out = v16 / 0x1000000; out += 4; if ( v17 == out ) break; v20 = *v18; --v18; v16 = v20; } if ( v25 ) sub_C0017628(); return result; }
二、杀猪儆猴
我没有逆setkey部分,直接内存中dump出setkey之后的结果,传给decrypt就行了。
decode_step1里1~34种蛋炒饭,举例第一次setkey结束的情况:
.text:00018EB8 STR R3, [SP,#0xAF0+key+0x3C] //这是setkey最后一步
.text:00018EBC MOV R11, #0 //从这开始循环解密数据。
虽然34种蛋炒饭比较长,但都是相同结构,费些体力就解决了。
剩下的最后难点就是decode_step2了:
1.在不知道是js引擎的情况下调试了若干次,晕头转向
2.在知道了是js引擎,但不知道是哪种js引擎的情况下调试了若干次,继续晕头转向
3.在知道了是duktape的js引擎的情况下调试了若干次,仍然晕头转向
咱就假设它和其它已知的35种蛋炒饭一样的算法结构行不?试试看吧。
已知的蛋炒饭算法都是异或和循环移位。
找来duktape的源码,编译examples\cmdline可以测试字节码,经验证结果是匹配的。
在代码里找到void duk__vm_bitwise_binary_op(...)这个函数,把异或和移位操作都打印出log。
例如xor的:
case DUK_OP_BXOR >> 2: { i3 = i1 ^ i2; printf("%08X ^ %08X = %08X\n",i1,i2,i3); //这行是我加的 break;
重新编译,运行dukcmd -b -i op.bin
测试得到log,列举一段:
duk> h("11111119222222273333333544444443") //调用函数 ...省略 22222227 ^ 33333335 = 11111112 //从捕捉到自己的输入开始,前面估计是setkey的 44444443 ^ FCF4D1C2 = B8B09581 //参考已知蛋炒饭的算法0xFCF4D1C2这个应该是setkey后的结果key[0] 11111112 ^ B8B09581 = A9A18493 A0000000 >> 0000001C = 0000000A 09000000 >> 00000018 = 00000009 00A00000 >> 00000014 = 0000000A 00010000 >> 00000010 = 00000001 00008000 >> 0000000C = 00000008 00000400 >> 00000008 = 00000004 00000090 >> 00000004 = 00000009 00000003 >> 00000000 = 00000003 00000023 << 00000018 = 23000000 0000006F << 00000010 = 006F0000 0000001B << 00000008 = 00001B00 000000CA << 00000000 = 000000CA 236F1BCA << 00000006 = DBC6F280 236F1BCA >> 0000001A = 00000008 236F1BCA ^ DBC6F288 = F8A9E942 236F1BCA << 0000000E = C6F28000 236F1BCA >> 00000012 = 000008DB 236F1BCA << 00000014 = BCA00000 236F1BCA >> 0000000C = 000236F1 C6F288DB ^ BCA236F1 = 7A50BE2A F8A9E942 ^ 7A50BE2A = 82F95768 236F1BCA << 00000018 = CA000000 236F1BCA >> 00000008 = 00236F1B 82F95768 ^ CA236F1B = 48DA3873 11111119 ^ 48DA3873 = 59CB296A 33333335 ^ 44444443 = 77777776 //大概大这里结构开始重复,注意看后面移位的次数 59CB296A ^ 22827EBC = 7B4957D6 //参考已知蛋炒饭的算法0x22827EBC这个应该是setkey后的结果key[1] 77777776 ^ 7B4957D6 = 0C3E20A0 00000000 >> 0000001C = 00000000 0C000000 >> 00000018 = 0000000C 00300000 >> 00000014 = 00000003 000E0000 >> 00000010 = 0000000E 00002000 >> 0000000C = 00000002 00000000 >> 00000008 = 00000000 000000A0 >> 00000004 = 0000000A 00000000 >> 00000000 = 00000000 0000000D << 00000018 = 0D000000 00000007 << 00000010 = 00070000 00000052 << 00000008 = 00005200 00000071 << 00000000 = 00000071 0D075271 << 00000006 = 41D49C40 0D075271 >> 0000001A = 00000003
观察字节码可以看到:
00000300h: 5F 30 78 32 61 30 38 00 00 00 00 07 5F 30 78 32 ; _0x2a08....._0x2 00000310h: 32 62 33 00 00 00 00 01 61 00 00 00 00 0C 5A 4D ; 2b3.....a.....ZM 00000320h: 4F 70 77 71 4A 46 63 41 3D 3D 00 00 00 00 0C 64 ; OpwqJFcA==.....d 00000330h: 32 48 44 72 4D 4B 4F 77 34 4D 3D 00 00 00 00 0C ; 2HDrMKOw4M=..... 00000340h: 77 34 41 47 58 54 76 43 6C 51 3D 3D 00 00 00 00 ; w4AGXTvClQ==....
_0x2a08,_0x22b3,a这些都是变量名。
可以输出查看:
duk> a = {sbbbc:[[62,162,122,86,253,15,93,68,131,64,159,134,13,108,101,96],[207,233,88,163,199,250,83,105,248,16,147,132,59,228,201,235], [82,156,198,8,251,219,47,144,245,31,212,34,80,118,55,217],[20,90,107,221,115,252,171,149,121,45,204,189,246,180,7,184], [33,215,247,38,124,151,17,218,50,79,234,9,3,28,99,179],[71,222,65,106,4,123,164,160,169,229,137,220,177,24,94,52], [195,140,241,0,14,84,12,142,244,168,154,66,40,210,104,178],[97,26,205,148,76,32,87,141,139,78,152,186,161,63,183,206], [155,239,75,213,27,208,98,72,181,227,203,175,197,73,226,43],[57,225,176,202,196,254,36,193,126,21,130,165,37,110,2,128], [113,111,209,236,109,22,102,145,211,35,243,46,153,166,223,39],[146,100,58,85,60,190,70,133,95,120,232,136,224,214,173,54], [10,112,125,1,188,191,231,19,30,158,135,127,91,114,255,187],[119,29,167,77,238,69,170,240,150,5,157,51,41,6,61,25], [194,185,200,174,192,143,237,230,182,129,116,49,81,92,172,67],[53,11,74,103,44,18,89,48,56,249,242,138,117,42,23,216]], ccccckksgh:[1545928790,1819523012,1300536528,2040543102,927101669,2266960222,541058818,658447962, 1530502291,1640414457,2082075646,870663072,3952126004,2775248416,2211896835,3306793059, 2343141319,1441383902,4277648326,210210204,3142034540,1984503314,421625248,3717453135, 3748020838,2594357764,3689027317,1003144796,779877921,4265274753,2465069848,1588512741], ffffffdk:[1228829654,750719941,2602314668,580818561], bbb:{_func:true},ssssl:{_func:true},prefixInteger:{_func:true},ssssssbs:{_func:true},GGGGULB:{_func:true},PPPPPPULBE:{_func:true}, ssggg:{_func:true},kkkkkd:{_func:true},h16AddZero:{_func:true},cccctti:{_func:true},tttttth:{_func:true},ppppph:{_func:true},h:{_func:true}}
其实如果自己实现setkey就更好搞了,把ccccckksgh和ffffffdk传给setkey就行了。
三、吃蛋炒饭喽
运行得到结果:18542cf63f2508f9632e6f8a49e0c298
而且发现因为没有区分字母大小写而存在多解共512个。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)