-
-
[推荐]看雪·安恒 2020 KCTF 春季赛 | 十三题设计思路及解析
-
发表于: 2020-5-18 18:26 6769
-


在比赛过程中,金左手战队再次惊艳全场,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
五、题目主题词
香江,珍宝画舫,食神争霸总决赛现场,衣香鬓影,名流云集。
周星星与糖牛正在激烈地争夺食神的宝座,他们最后都选择了在“黯然销魂蛋炒饭”这道菜上一决高下!
糖牛手捧着刚刚完成的“黯然销魂蛋炒饭”,忍不住傲然道:周星星,当年在灌江口,我跟你学厨。
第一年,你让我砍柴,我砍了整整一年的柴;
第二年,你让我挑水,我挑了整整一年的水;
第三年,你让我烧火,我烧了整整一年的火;
第四年,你让我扇风,我扇了整整一年的风;
直到第五年,你才肯教给我这道“黯然销魂蛋炒饭”。
这十年来,我每日苦练这道“黯然销魂蛋炒饭”,未敢有一日松懈。
今天,我完成了这道“黯然销魂蛋炒饭”的三十六重变化,每一种变化都能让食客获得一种独特的美食体验。如今,我看你怎么赢我!哈哈哈哈!哈哈哈哈!
谁料,周星星却仰天大笑道:糖牛,十五年前你跟我学厨,我就看出你脑后有反骨。
是以始终对你有所保留。虽然你教会了“黯然销魂蛋炒饭”的第三十六重变化,
可今天我要告诉你,我的“黯然销魂蛋炒饭”有七十二种变化。
真正的老饕,在品尝了我的“蛋炒饭”之后,你的三十六重变化每一重都会让人味同嚼蜡,你且说我如何不能赢你!!!哈哈哈哈!哈哈哈哈!

解析过程

一、小猪上路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | .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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | 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的:
1 2 3 4 | case DUK_OP_BXOR >> 2 : { i3 = i1 ^ i2; printf( "%08X ^ %08X = %08X\n" ,i1,i2,i3); //这行是我加的 break ; |
重新编译,运行dukcmd -b -i op.bin
测试得到log,列举一段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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 |
观察字节码可以看到:
1 2 3 4 5 | 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这些都是变量名。
可以输出查看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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个。
[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!