[2020][KCTF] 第十三题 猪突豨勇 wp
2020-05-17 22:11:20 Author: bbs.pediy.com(查看原文) 阅读量:523 收藏

一 小猪上路
展开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的源码,编译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

这样就可以把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见附件

[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!

最后于 8小时前 被ccfer编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-259564.htm
如有侵权请联系:admin#unsafe.sh