看雪·安恒 2020 KCTF 春季赛 | 十三题设计思路及解析

微信扫一扫,分享到朋友圈

看雪·安恒 2020 KCTF 春季赛 | 十三题设计思路及解析


第十三题《白云苍狗》

共有

1615


人围观,最终仅有

1

支战队成功破解。


在比赛过程中,金左手战队再次惊艳全场,5月16日深夜,
金左手

战队的


ccfer

拿到
《白云苍狗》的一血,成功登上攻击方排行榜的榜首。

春季赛到这里就告一段落啦,感谢每一支战队的参与,你们的存在让整个赛场更闪亮。

为了保证赛事的公平公正,春季赛的排名将由专业裁判进行审核,正式结果将在明天公布,请大家耐心等待,持续关注后续消息。如果您对比赛结果有任何异议,尽请提出。

接下来让我们一起看一下第十三题的设计思路和详细解析吧。

赛题点评

本题点评由看雪密码学专家

readyu

提供:

最后一题,
金左手
战队的

ccfer

大牛,拿下一血,以画龙点睛之笔收官了比赛。随便还发现了题目flag的hex字符大小写多解。

这一题的“36次”蛋炒饭,每一轮加密算法都修改了参数。算法分三个步骤:用户态34次,js虚拟机1次,kernel调用1次。js虚拟机,kernel里对应的算法参数是修改过的。

第二步骤里,作者用了一个不太常见的duktape的js 虚拟机,加密代码写成js脚本,被编译为1个32681字节的opcode来执行。程序里编译得很干净,没有明显的特征信息,这是一个很大的障碍。需要从opcode里找出32个dword子秘钥 ,和一个256字节的sbox,以及ror循环移位的4个参数。如果没有找到duktape虚拟机原型,只有这个arm程序,那就很难调试js脚本,攻击方可能就止步在这里了。

攻击方修改并编译duk虚拟机,加载opcode,把vm handler的关键指令(异或,移位)打印log出来观察,抽丝剥茧,成功获取密钥参数,攻克了此题。

出题团队简介

本题出题战队


 飘向北方




团队简介:

毕业于广西大学,现居住于深圳,自带冷场天赋,专业拆台30年,常年承接各种相亲冷场,聚会拆台业务,有意向者可站内信联系。

设计思路


一、类型

智能设备CTF


二、Flag

18542cf63f2508f9632e6f8a49e0c298


三、题目设计

题目针对ARM智能设备进行模拟。

本题只有一种核心算法,SM4算法。

算法的计算过程是:

对输入的序列号进行36次SM4解密,判断最后解密出来的结果是否等于0x57656c636f6d6520746f204b616e5875。

36次变种SM4分3种类型:1~34次SM4为直接运算,第35次运行在js虚拟机中,第36次依赖Linux crypto API调用内核的加密算法。

如36次解密后解密结果等于0x57656c636f6d6520746f204b616e5875,

则提示”you got it”, 并进入登录界面。

否则提示继续输入flag。

SM4 的变化方式为修改算法的S盒参数、FK参数、CK参数。

破解方法为跟踪并恢复算法相关参数。也可以跟出SM4算法的round key,直接解密。

题目未加壳。


四、解题思路

比赛提供images.tar.gz 文件,解压可以得到如下文件:

├── rootfs.ext2

├── versatile-pb.dtb

└── zImage

参赛者可以在 linux 系统下运行如下命令运行镜像文件:

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

该命令在 Ubuntu 18.04.4 ,qemu 2.11 测试通过,不建议使用Windows版本的qemu 运行。


五、题目主题词

糖牛的“黯然销魂蛋炒饭”

香江,珍宝画舫,食神争霸总决赛现场,衣香鬓影,名流云集。

周星星与糖牛正在激烈地争夺食神的宝座,他们最后都选择了在“黯然销魂蛋炒饭”这道菜上一决高下!

糖牛手捧着刚刚完成的“黯然销魂蛋炒饭”,忍不住傲然道:周星星,当年在灌江口,我跟你学厨。

第一年,你让我砍柴,我砍了整整一年的柴;

第二年,你让我挑水,我挑了整整一年的水;

第三年,你让我烧火,我烧了整整一年的火;

第四年,你让我扇风,我扇了整整一年的风;

直到第五年,你才肯教给我这道“黯然销魂蛋炒饭”。

这十年来,我每日苦练这道“黯然销魂蛋炒饭”,未敢有一日松懈。

今天,我完成了这道“黯然销魂蛋炒饭”的三十六重变化,每一种变化都能让食客获得一种独特的美食体验。如今,我看你怎么赢我!哈哈哈哈!哈哈哈哈!

谁料,周星星却仰天大笑道:糖牛,十五年前你跟我学厨,我就看出你脑后有反骨。

是以始终对你有所保留。虽然你教会了“黯然销魂蛋炒饭”的第三十六重变化,

可今天我要告诉你,我的“黯然销魂蛋炒饭”有七十二种变化。

真正的老饕,在品尝了我的“蛋炒饭”之后,你的三十六重变化每一重都会让人味同嚼蜡,你且说我如何不能赢你!!!哈哈哈哈!哈哈哈哈!

解析过程


本题解题思路由

金左手



ccfer

提供: 


一、小猪上路

展开images,bin下拿到主程序kanxue2020:

.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”

然后调试到里面会遇若干次svc调用,这是带kernel交互的啊,我感到迷茫,从没调试过这玩意。

大帅锅同学这时候把调试方法和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和encrypt与setp1里的34重蛋炒饭相似度很高啊(记住这个要点,后面step2就靠这个猜想了)


二、杀猪儆猴

decode_step1和decode_step3
算法结构相似,只是密钥和置换表不同。


我没有逆 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_step3是kernel中前面已经贴了F5的代码,按简介这个就是第36种蛋炒饭了,和decode_step1同理可解决。

剩下的最后难点就是decode_step2了:

1.在不知道是js引擎的情况下调试了若干次,晕头转向

2.在知道了是js引擎,但不知道是哪种js引擎的情况下调试了若干次,继续晕头转向

3.在知道了是duktape的js引擎的情况下调试了若干次,仍然晕头转向

这个时候貌似要靠猜想了,第35种蛋炒饭?

咱就假设它和其它已知的35种蛋炒饭一样的算法结构行不?试试看吧。

已知的蛋炒饭算法都是异或和循环移位。

找来duktape的源码,编译examplescmdline可以测试字节码,经验证结果是匹配的。

在代码里找到void duk__vm_bitwise_binary_op(…)这个函数,把异或和移位操作都打印出log。

例如xor的:

case DUK_OP_BXOR >> 2: {

i3 = i1 ^ i2;

printf("%08X ^ %08X = %08Xn",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

这样就可以把key都获取到了。

观察字节码可以看到:

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}}

这前面那个256字节的数组sbbbc经验证就是置换表。

其实如果自己实现setkey就更好搞了,把ccccckksgh和ffffffdk传给setkey就行了。


三、吃蛋炒饭喽

整理前面分析流程,写出代码没几行,大部分都是key数据和置换表。

运行得到结果:18542cf63f2508f9632e6f8a49e0c298

而且发现因为没有区分字母大小写而存在多解共512个。

dukcmd工具、字节码和keygen见附件(戳阅读原文下载)

看雪CTF(简称KCTF)是圈内知名度最高的技术竞技之一,从原CrackMe攻防大赛中发展而来,采取线上PK的方式,规则设置严格周全,题目涵盖Windows、Android、iOS、Pwn、智能设备、Web等众多领域。

看雪CTF比赛分为两个阶段:

第一阶段是防守篇,防守方根据比赛要求制作题目,根据题目被破解的时间排名,被破解时间长者胜出。

第二阶段为攻击篇,攻击第一阶段的题目,根据攻击成功的时间与题目排名,破解时间短且破解题目数多者胜。

既给了防守方足够的施展空间,也避免过度浪费攻击方的时间。从攻防两个角度看,都是个难得的竞技和学习机会。

看雪学院(kanxue.com)是一个专注于PC、移动、智能设备安全研究及逆向工程的开发者社区!成立于2000年,经历了近二十年的磨砺,见证了国内安全行业的发展,被誉为安全界的黄埔军校。它是中国最早的安全人员交流学习社区,通过近二十年的发展在行业内树立了令人尊敬的专业形象。

杭州安恒信息技术股份有限公司(简称:安恒信息)成立于2007年,科创板股票代码:688023,一直专注于网络信息安全领域,公司主营业务为网络信息安全产品的研发、生产及销售,并为客户提供专业的网络信息安全服务。公司的产品及服务涉及应用安全、大数据安全、云安全、物联网安全、工业控制安全及工业互联网安全等领域。

公司秉承“助力安全中国、助推数字经济”的企业使命,以“诚信正直、成就客户,责任至上,开放创新,以人为本,共同成长”作为企业的价值观,不断提高核心技术创新能力,致力于成为一家具有优秀企业文化和社会责任感的新时代网络信息安全产品和服务提供商。



“阅读原文

 
一起来充电吧!

微信扫一扫,分享到朋友圈

看雪·安恒 2020 KCTF 春季赛 | 十三题设计思路及解析

原创:微信小程序入口猜想?

上一篇

使用gdb调试php

下一篇

你也可能喜欢

看雪·安恒 2020 KCTF 春季赛 | 十三题设计思路及解析

长按储存图像,分享给朋友