Back

Splendid_Minecraft 解析及脚本

ACTF2020新生赛

[ACTF新生赛2020]Splendid_MineCraft

解密字节码

反编译源码:

 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
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *v3; // eax
  char *v4; // eax
  char v6; // [esp+0h] [ebp-68h]
  int i; // [esp+14h] [ebp-54h]
  char *v8; // [esp+18h] [ebp-50h]
  char v9; // [esp+20h] [ebp-48h]
  char flag[25]; // [esp+24h] [ebp-44h] BYREF
  char v11[25]; // [esp+25h] [ebp-43h] BYREF
  char v12; // [esp+3Dh] [ebp-2Bh]
  int v13; // [esp+44h] [ebp-24h]
  __int16 v14; // [esp+48h] [ebp-20h]
  char v15[4]; // [esp+4Ch] [ebp-1Ch]
  __int16 v16; // [esp+50h] [ebp-18h]
  int v17; // [esp+54h] [ebp-14h] BYREF
  __int16 v18; // [esp+58h] [ebp-10h]
  int v19; // [esp+5Ch] [ebp-Ch]
  __int16 v20; // [esp+60h] [ebp-8h]

  sub_401020("%s\n", (char)aWelcomeToActfS);
  sub_401050("%s", (char)flag);
  if ( &flag[strlen(flag) + 1] - v11 == 26 && !strncmp(flag, "ACTF{", 5u) && v12 == '}' )
  {
    v12 = 0;
    v3 = strtok(flag, "_");
    v17 = *(_DWORD *)(v3 + 5);
    v18 = *(_WORD *)(v3 + 9);
    v19 = *(_DWORD *)(v3 + 5);
    v20 = *(_WORD *)(v3 + 9);
    v4 = strtok(0, "_");
    v13 = *(_DWORD *)v4;
    v14 = *((_WORD *)v4 + 2);
    v8 = strtok(0, "_");
    *(_DWORD *)v15 = *(_DWORD *)v8;
    v16 = *((_WORD *)v8 + 2);
    dword_403354 = (int)&unk_4051D8;
    if ( ((int (__cdecl *)(int *))unk_4051D8)(&v17) )
    {
      v9 = BYTE2(v19) ^ HIBYTE(v20) ^ v19 ^ HIBYTE(v19) ^ BYTE1(v19) ^ v20;
      for ( i = 256; i < 496; ++i )
        byte_405018[i] ^= v9;
      __asm { jmp     eax }
    }
  }
  sub_401020("Wrong\n", v6);
  return 0;
}

条件判断:

1
&flag[strlen(flag) + 1] - v11 == 26 && !strncmp(flag, "ACTF{", 5u) && v12 == '}'

第一个判断是输入的flag的最后一个字符的地址-v11的起始地址是否等于26,应该就是需要flag的长度为26:

1
cyclic 26 -> flag{aaaabaaacaaadaaaeaaa}

image-20211212181827825

后面两个判断就是判断flag的格式为:ACTF{xxxx}

strtok,用_将字符串分隔开,v3取的是最左边的一段字符,ACTF{xxxx,注意v17是从第六个字符开始,取4个字符的长度,然后v18是从第10个字符开始,取两个字符,这样其实都可以推断出来整个flag的格式为ACTF{xxxxxx_xxxxxx_xxxxxx}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
v3 = strtok(flag, "_");
v17 = *(_DWORD *)(v3 + 5);
v18 = *(_WORD *)(v3 + 9);
v19 = *(_DWORD *)(v3 + 5);
v20 = *(_WORD *)(v3 + 9);
v4 = strtok(0, "_");
v13 = *(_DWORD *)v4;
v14 = *((_WORD *)v4 + 2);
v8 = strtok(0, "_");
*(_DWORD *)v15 = *(_DWORD *)v8;
v16 = *((_WORD *)v8 + 2);

往下存在一个unk_4051D8

image-20211212183722900

发现把unk_4051D8当成函数执行:((int (__cdecl *)(int *))unk_4051D8)(&v17),说明上面的一大段数据应该是字节码,然后将v17当成参数传入该函数中,重新运行,输入ACTF{aaaaaa_bbbbbb_cccccc},用x32dbg动态跟:

第一个循环:

image-20211212215926952

1
2
3
4
esi = 0xE351DD
for edi in range(0, 0x151):
	tmp = byte ptr [esi + edi + 0x1F]
	[esi+edi+0x1F] = tmp ^ 0x72

异或结果:

image-20211212230654068

看到EIP,发现就是通过循环解密出后面需要执行的字节码:

image-20211212230935974

第一部分解密

解密之后的push ebp才是真正的函数开始,该函数先是往内存写值,一个是3@1b;b,一个是Welcome

image-20211212231406269

往下对刚刚写入的字符串的进行循环异或,

image-20211212231713546

1
2
3
4
5
a = '3@1b;b'
b = 'elcome'
res = ''
for i in range(0, len(a)):
    res += chr(a[i] ^ b[i] + 0x23)

6次循环后得到:yOu0y*

image-20211212233654525

最后出循环,test eax, eax,说明eax是作为函数执行结果,

image-20211212234011240

重新运行执行输入:

ACTF{abcdef_hijklm_opqrst}

处理完第一个strtok之后会得到一个字符串为:abcdefRvabcdef,中间的Rv是固定的

image-20211213232914763

三次strtok处理完之后会得到这样的结果:

image-20211213233135397

然后来到字节码部分,注意传进来的字符串为:abcdefRvabcdef

image-20211213233228219

进来之后先对字节码进行解密,解谜之后从0x9F515C开始才是函数处理的开始,注意传入的参数由EDX指向:

image-20211213233405286

随后生成3@1b;bwelcome,对这两个值进行异或再加上0x23,然后跟传入的字符串进行对比,不难发现预期值应该是两个相等的,往下走完循环拿完处理后的结果为yOu0y*

image-20211213233907366

重新运行:

image-20211213234107801

此时相等了,跳转不实现:

image-20211213234225491

将edx作为计数器来存储相等的个数,最后对比是否全部相等,将返回值置为1:

image-20211213234415857

第二部分解密

往下来到第二个部分的验证:

image-20211214101314551

1
2
res = 'yOu0y*'
key = res[0] ^ res[1] ^ res[3] ^ res[0] ^ res[5] ^ res[2] = 

image-20211215110249558

异或之后的值作为key,然后对:405018开始的值进行异或,跟第一个函数一样,通过这个key对字节码进行解密,然后再执行,这里的内容就是第二部分字符串的验证:

image-20211215085046486

image-20211215085805863

开局几个循环啥事都没干就是将输入的第二部分字符串进行memcpy,到这个位置才真正对字符串进行处理:

image-20211215091013268

往下的mov很重要,EAX此时的值为0x705018,也就是该函数的字节码首地址,这里异或的结果作为数组下标,将字节码以byte的形式存储到EBX:

image-20211215091206850

往下从0x705018+0x166取值与上面通过偏移拿到的值进行比较:

image-20211215091509928

edi作为计数器,回到异或之前可以看到ecx的值来自于edi:

image-20211215092843105

image-20211215092045179

image-20211215111022353

得到异或逻辑为:

1
2
3
4
5
res2 = [0x30,4,4,3,0x30,0x63]
flag2 = 'xxxxxx'
byte_405018 = [...]
for i in range(0, len(flag2)):
	cmp byte_405018[flag2[i] ^ (i+0x83)],  ord(str2[i])

exp:

1
2
3
4
5
res2 = [0x30,4,4,3,0x30,0x63]
flag2 = ''
byte_405018 = [...]
for i in range(0, len(res2)):
  flag2 += chr((0x83+i)^ byte_405018.index(res2[i]))

byte_405018,用Ghidra的字节复制复制下来:

1
[ 0xf6, 0xa3, 0x5b, 0x9d, 0xe0, 0x95, 0x98, 0x68, 0x8c, 0x65, 0xbb, 0x76, 0x89, 0xd4, 0x09, 0xfd, 0xf3, 0x5c, 0x3c, 0x4c, 0x36, 0x8e, 0x4d, 0xc4, 0x80, 0x44, 0xd6, 0xa9, 0x01, 0x32, 0x77, 0x29, 0x90, 0xbc, 0xc0, 0xa8, 0xd8, 0xf9, 0xe1, 0x1d, 0xe4, 0x67, 0x7d, 0x2a, 0x2c, 0x59, 0x9e, 0x3d, 0x7a, 0x34, 0x11, 0x43, 0x74, 0xd1, 0x62, 0x60, 0x02, 0x4b, 0xae, 0x99, 0x57, 0xc6, 0x73, 0xb0, 0x33, 0x18, 0x2b, 0xfe, 0xb9, 0x85, 0xb6, 0xd9, 0xde, 0x7b, 0xcf, 0x4f, 0xb3, 0xd5, 0x08, 0x7c, 0x0a, 0x71, 0x12, 0x06, 0x37, 0xff, 0x7f, 0xb7, 0x46, 0x42, 0x25, 0xc9, 0xd0, 0x50, 0x52, 0xce, 0xbd, 0x6c, 0xe5, 0x6f, 0xa5, 0x15, 0xed, 0x64, 0xf0, 0x23, 0x35, 0xe7, 0x0c, 0x61, 0xa4, 0xd7, 0x51, 0x75, 0x9a, 0xf2, 0x1e, 0xeb, 0x58, 0xf1, 0x94, 0xc3, 0x2f, 0x56, 0xf7, 0xe6, 0x86, 0x47, 0xfb, 0x83, 0x5e, 0xcc, 0x21, 0x4a, 0x24, 0x07, 0x1c, 0x8a, 0x5a, 0x17, 0x1b, 0xda, 0xec, 0x38, 0x0e, 0x7e, 0xb4, 0x48, 0x88, 0xf4, 0xb8, 0x27, 0x91, 0x00, 0x13, 0x97, 0xbe, 0x53, 0xc2, 0xe8, 0xea, 0x1a, 0xe9, 0x2d, 0x14, 0x0b, 0xbf, 0xb5, 0x40, 0x79, 0xd2, 0x3e, 0x19, 0x5d, 0xf8, 0x69, 0x39, 0x5f, 0xdb, 0xfa, 0xb2, 0x8b, 0x6e, 0xa2, 0xdf, 0x16, 0xe2, 0x63, 0xb1, 0x20, 0xcb, 0xba, 0xee, 0x8d, 0xaa, 0xc8, 0xc7, 0xc5, 0x05, 0x66, 0x6d, 0x3a, 0x45, 0x72, 0x0d, 0xca, 0x84, 0x4e, 0xf5, 0x31, 0x6b, 0x92, 0xdc, 0xdd, 0x9c, 0x3f, 0x55, 0x96, 0xa1, 0x9f, 0xcd, 0x9b, 0xe3, 0xa0, 0xa7, 0xfc, 0xc1, 0x78, 0x10, 0x2e, 0x82, 0x8f, 0x30, 0x54, 0x04, 0xac, 0x41, 0x93, 0xd3, 0x3b, 0xef, 0x03, 0x81, 0x70, 0xa6, 0x1f, 0x22, 0x26, 0x28, 0x6a, 0xab, 0x87, 0xad, 0x49, 0x0f, 0xaf ]

image-20211215103806688

得到flag2:

image-20211215113140388

第三部分解密

第三部分字符串发现就是直接进行strncmp:

image-20211215113450451

可以得到第三部分的字符串为:5mcsM<,最终flag:

ACTF{yOu0y*_knowo3_5mcsM<}

Exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
flag_prefix = 'ACTF{'
flag_suffix = '}'
flag3 = '5mcsM<'

a = '3@1b;b'
b = 'elcome'
flag1 = ''
for i in range(0, len(a)):    
	flag1 += chr((ord(a[i]) ^ ord(b[i])) + 0x23)

res2 = [0x30,4,4,3,0x30,0x63]
flag2 = ''
byte_405018 = [ 0xf6, 0xa3, 0x5b, 0x9d, 0xe0, 0x95, 0x98, 0x68, 0x8c, 0x65, 0xbb, 0x76, 0x89, 0xd4, 0x09, 0xfd, 0xf3, 0x5c, 0x3c, 0x4c, 0x36, 0x8e, 0x4d, 0xc4, 0x80, 0x44, 0xd6, 0xa9, 0x01, 0x32, 0x77, 0x29, 0x90, 0xbc, 0xc0, 0xa8, 0xd8, 0xf9, 0xe1, 0x1d, 0xe4, 0x67, 0x7d, 0x2a, 0x2c, 0x59, 0x9e, 0x3d, 0x7a, 0x34, 0x11, 0x43, 0x74, 0xd1, 0x62, 0x60, 0x02, 0x4b, 0xae, 0x99, 0x57, 0xc6, 0x73, 0xb0, 0x33, 0x18, 0x2b, 0xfe, 0xb9, 0x85, 0xb6, 0xd9, 0xde, 0x7b, 0xcf, 0x4f, 0xb3, 0xd5, 0x08, 0x7c, 0x0a, 0x71, 0x12, 0x06, 0x37, 0xff, 0x7f, 0xb7, 0x46, 0x42, 0x25, 0xc9, 0xd0, 0x50, 0x52, 0xce, 0xbd, 0x6c, 0xe5, 0x6f, 0xa5, 0x15, 0xed, 0x64, 0xf0, 0x23, 0x35, 0xe7, 0x0c, 0x61, 0xa4, 0xd7, 0x51, 0x75, 0x9a, 0xf2, 0x1e, 0xeb, 0x58, 0xf1, 0x94, 0xc3, 0x2f, 0x56, 0xf7, 0xe6, 0x86, 0x47, 0xfb, 0x83, 0x5e, 0xcc, 0x21, 0x4a, 0x24, 0x07, 0x1c, 0x8a, 0x5a, 0x17, 0x1b, 0xda, 0xec, 0x38, 0x0e, 0x7e, 0xb4, 0x48, 0x88, 0xf4, 0xb8, 0x27, 0x91, 0x00, 0x13, 0x97, 0xbe, 0x53, 0xc2, 0xe8, 0xea, 0x1a, 0xe9, 0x2d, 0x14, 0x0b, 0xbf, 0xb5, 0x40, 0x79, 0xd2, 0x3e, 0x19, 0x5d, 0xf8, 0x69, 0x39, 0x5f, 0xdb, 0xfa, 0xb2, 0x8b, 0x6e, 0xa2, 0xdf, 0x16, 0xe2, 0x63, 0xb1, 0x20, 0xcb, 0xba, 0xee, 0x8d, 0xaa, 0xc8, 0xc7, 0xc5, 0x05, 0x66, 0x6d, 0x3a, 0x45, 0x72, 0x0d, 0xca, 0x84, 0x4e, 0xf5, 0x31, 0x6b, 0x92, 0xdc, 0xdd, 0x9c, 0x3f, 0x55, 0x96, 0xa1, 0x9f, 0xcd, 0x9b, 0xe3, 0xa0, 0xa7, 0xfc, 0xc1, 0x78, 0x10, 0x2e, 0x82, 0x8f, 0x30, 0x54, 0x04, 0xac, 0x41, 0x93, 0xd3, 0x3b, 0xef, 0x03, 0x81, 0x70, 0xa6, 0x1f, 0x22, 0x26, 0x28, 0x6a, 0xab, 0x87, 0xad, 0x49, 0x0f, 0xaf ]
for i in range(0, len(res2)):
	flag2 += chr((0x83+i)^ byte_405018.index(res2[i]))


flag = flag_prefix + flag1 + '_' + flag2 + '_' + flag3 + flag_suffix
print(flag)

image-20211215115036210

YuSec Github Blog
Built with Hugo
Theme Stack designed by Jimmy