初音ミクの消失

rctf_babyre1

字数统计: 2k阅读时长: 10 min
2019/05/21 Share

RCTF_babyre1

rctf_babyre1

思路

以下是与输入相关的指令

main

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
unsigned __int64 *in; // rdx
int v4; // ecx
unsigned int v5; // eax
unsigned __int64 v6; // rdx
int v7; // eax
int *v8; // rax
char *v9; // rbx
__int64 i; // rax
int v12; // [rsp+4h] [rbp-124h]
void *ptr; // [rsp+8h] [rbp-120h]
unsigned __int64 str; // [rsp+10h] [rbp-118h]
unsigned __int64 v15; // [rsp+118h] [rbp-10h]

v15 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
ptr = 0LL;
v12 = 0;
memset(&str, 0, 0x100uLL);
__printf_chk(1LL, "Input right flag you can got 'Bingo!' :");
__isoc99_scanf("%31s", &str); // 输入为16位
in = &str;
do
{
v4 = *(_DWORD *)in;
in = (unsigned __int64 *)((char *)in + 4);
v5 = ~v4 & (v4 - 0x1010101) & 0x80808080;
}
while ( !v5 );
if ( !((unsigned __int16)~(_WORD)v4 & (unsigned __int16)(v4 - 0x101) & 0x8080) )
v5 >>= 16;
if ( !((unsigned __int16)~(_WORD)v4 & (unsigned __int16)(v4 - 0x101) & 0x8080) )
in = (unsigned __int64 *)((char *)in + 2);
v6 = (char *)in - __CFADD__((_BYTE)v5, (_BYTE)v5) - 3 - (char *)&str;
if ( v6 > 0x10 )
{
puts("input is too long!");
}
else if ( v6 == 16 )
{
v7 = change_into_number((unsigned __int64)&str, 16, &ptr);// 这个程序就是把输入的内容(十六进制)转换成了程序可读的数字
if ( v7
&& (v8 = xtea_keychanged(ptr, v7, (__int64)&unk_55883B7B0010, 16, &v12), (v9 = (char *)v8) != 0LL)
&& v12 > 0
&& (unsigned __int16)crc16((__int64)v8, v12) == 0x69E2 )
{
for ( i = 0LL; v12 > (signed int)i; ++i )
v9[i] ^= 0x17u;
puts(v9);
if ( ptr )
free(ptr);
free(v9);
}
else
{
puts("input flag is wrong!");
}
}
else
{
puts("input is too short!");
}
return 0LL;
}

可以看到输入后的字符串进行了一大堆看不懂的操作,但是能推测出来输入的长度为16位

用ida调试可知,change_into_number这个函数把输入的字符串进行了转换

完成了16进制字符串 -> 16进制整数的过程

xtea_keychanged中的一个子函数

signed __int64 __fastcall sub_55883B5AECE0(int *a1, signed int a2, __int64 a3)
{
__int64 v3; // rbp
signed int v4; // ebx
unsigned int v5; // ecx
int v6; // er12
int *v7; // r13
unsigned int v8; // er14
unsigned int v9; // ecx
unsigned int v10; // er15
unsigned int v11; // ebx
unsigned int v12; // er10
int *v13; // r9
int v14; // er8
unsigned int v15; // esi
int v16; // eax
unsigned __int8 v17; // dl
unsigned int v18; // eax
int *v19; // r10
unsigned int v20; // eax
char v21; // dl
unsigned int v22; // eax
signed __int64 result; // rax
int v24; // esi
unsigned int v25; // er11
int *v26; // r13
int v27; // er14
unsigned int v28; // er12
unsigned int v29; // ebx
int *v30; // r8
int v31; // esi
unsigned int v32; // er9
unsigned int v33; // er10
unsigned int v34; // eax
int v35; // ecx
unsigned __int8 v36; // dl
unsigned int v37; // eax
int v38; // er15
int *v39; // r8
unsigned int v40; // er10
int v41; // eax
bool v42; // zf
signed int v43; // [rsp+0h] [rbp-40h]
int *v44; // [rsp+0h] [rbp-40h]
unsigned int v45; // [rsp+Ch] [rbp-34h]

v3 = a3;
v4 = a2;
v5 = *a1;
v43 = a2;
if ( a2 > 1 )
{
v6 = a2 - 1;
v7 = &a1[a2 - 1];
v8 = 0;
v9 = *v7;
v10 = ((a2 - 4) & 0xFFFFFFFE) + 2;
v45 = 0x9E3779B9 * (52 / a2) - 0x4AB325AA;
do
{
v8 -= 0x61C88647;
v11 = v8 >> 2;
if ( v43 <= 3 )
{
v14 = 0;
}
else
{
v12 = *a1;
v13 = a1;
v14 = 0;
do
{
v15 = v13[1];
v13 += 2;
v16 = (v9 ^ *(_DWORD *)(v3 + 4LL * (((unsigned __int8)v14 ^ (unsigned __int8)v11) & 3))) + (v15 ^ v8);
v17 = v14 + 1;
v14 += 2;
v18 = v12 + ((((v9 >> 5) ^ 4 * v15) + ((v15 >> 3) ^ 16 * v9)) ^ v16);
v12 = *v13;
*(v13 - 2) = v18;
v9 = v15
+ (((4 * v12 ^ (v18 >> 5)) + (16 * v18 ^ (v12 >> 3))) ^ ((v12 ^ v8)
+ (v18 ^ *(_DWORD *)(v3
+ 4LL
* (((unsigned __int8)v11 ^ v17) & 3)))));
*(v13 - 1) = v9;
}
while ( v10 != v14 );
}
v19 = &a1[v14];
do
{
v20 = v19[1];
v21 = v11 ^ v14++;
++v19;
v22 = *(v19 - 1)
+ (((v9 ^ *(_DWORD *)(v3 + 4LL * (v21 & 3))) + (v20 ^ v8)) ^ ((16 * v9 ^ (v20 >> 3)) + ((v9 >> 5) ^ 4 * v20)));
*(v19 - 1) = v22;
v9 = v22;
}
while ( v6 > v14 );
v9 = *v7
+ (((v22 ^ *(_DWORD *)(v3 + 4LL * (((unsigned __int8)v6 ^ (unsigned __int8)v11) & 3))) + (*a1 ^ v8)) ^ ((4 * *a1 ^ (v22 >> 5)) + (16 * v22 ^ ((unsigned int)*a1 >> 3))));
*v7 = v9;
}
while ( v8 != v45 );
return 0LL;
}
result = 1LL;
if ( a2 < -1 )
{
v24 = -a2;
v25 = -1640531527 * (52 / v24 + 6);
if ( v25 )
{
v26 = &a1[v24 - 1];
v27 = ~v4;
v44 = &a1[~v4];
v28 = ~v4 - 2 - ((~v4 - 3) & 0xFFFFFFFE);
do
{
v29 = v25 >> 2;
if ( v27 <= 2 )
{
v31 = v27;
}
else
{
v30 = v44;
v31 = v27;
v32 = *v44;
do
{
v33 = *(v30 - 1);
v30 -= 2;
v34 = v32;
v32 = *v30;
v35 = ((v5 ^ v25) + (v33 ^ *(_DWORD *)(v3 + 4LL * (((unsigned __int8)v31 ^ (unsigned __int8)v29) & 3)))) ^ ((4 * v5 ^ (v33 >> 5)) + ((v5 >> 3) ^ 16 * v33));
v36 = v31 - 1;
v31 -= 2;
v37 = v34 - v35;
v38 = *v30;
v30[2] = v37;
v5 = v33
- (((16 * v38 ^ (v37 >> 3)) + ((v32 >> 5) ^ 4 * v37)) ^ ((v32 ^ *(_DWORD *)(v3
+ 4LL
* (((unsigned __int8)v29 ^ v36) & 3)))
+ (v25 ^ v37)));
v30[1] = v5;
}
while ( v28 != v31 );
}
v39 = &a1[v31];
do
{
v40 = *(v39 - 1);
--v39;
v5 = v39[1]
- (((v5 ^ v25) + (v40 ^ *(_DWORD *)(v3 + 4LL * (((unsigned __int8)v29 ^ (unsigned __int8)v31) & 3)))) ^ (((v5 >> 3) ^ 16 * v40) + ((v40 >> 5) ^ 4 * v5)));
v39[1] = v5;
--v31;
}
while ( v31 );
v41 = *a1
- (((((unsigned int)*v26 >> 5) ^ 4 * v5) + (16 * *v26 ^ (v5 >> 3))) ^ ((*(_DWORD *)(v3 + 4LL * (v29 & 3)) ^ *v26)
+ (v25 ^ v5)));
v42 = v25 == -1640531527;
v25 += 1640531527;
v5 = v41;
*a1 = v41;
}
while ( !v42 );
}
return 0LL;
}
return result;
}

由0x9E3779B9可知,这是一个xtea加密(解密)算法

然后加解密所使用的常数有所改变,是内存中的16字节的数组

xtea(v8, -(v10 >> 2), constant)

这个 -(v10 >> 2)明显是一个负数,而xtea函数为负时,就是解密的过程

在调试时xtea_keychanged这个函数执行完后就直接报wrong

经高人指点,这个是xtea解密失败,程序判断这个密码解不出来,就直接报错了

xtea_keychanged

int *__fastcall xtea_keychanged(void *src, int const_8, __int64 a3, int a4, int *a5)
{
int *v5; // rbp
int v6; // er12
__int64 constant; // r14
int *string; // rbx
int *v9; // rax
int v10; // esi
signed int v11; // eax
int v13; // er12

v5 = a5;
if ( !src || (v6 = const_8, const_8 <= 0) || (constant = a3) == 0 || a4 != 16 )
{
if ( !a5 )
goto LABEL_14;
goto LABEL_13;
}
string = 0LL;
if ( a5 )
{
*a5 = 0;
if ( !(const_8 & 3) )
{
v9 = (int *)malloc(const_8 + 1);
string = v9;
if ( v9 )
{
memcpy(v9, src, const_8); // v9 存储了输入的16进制数
v10 = const_8 + 3;
if ( v6 >= 0 )
v10 = v6;
xtea(string, -(v10 >> 2), constant); // xtea对输入的字符串进行解密
v11 = *((unsigned __int8 *)string + v6 - 1);// 取第八个字符
*((_BYTE *)string + v6) = 0;
*v5 = v6;
if ( v6 > v11 && v11 <= 4 )
{
v13 = v6 - v11; // 8 - v11
*v5 = v13;
*((_BYTE *)string + v13) = 0; // 把某一位置零了
return string;
}
free(string);
}
}
LABEL_13:
*v5 = 0;
LABEL_14:
string = 0LL;
}
return string;
}
*((_BYTE *)string + v13) = 0;         // 把某一位置零了

这句话很关键,把某一位截断了,而这一位只能是倒数第二位

crc16

__int64 __fastcall sub_55883B5AF3D0(__int64 a1, int a2)
{
__int64 v2; // r12
__int64 v3; // rbp
__int16 v4; // ax
__int16 v5; // ax
__int16 v6; // dx
__int16 v7; // ax
__int16 v8; // dx
__int16 v9; // ax
__int16 v10; // dx
__int16 v11; // ax
unsigned __int8 v13; // [rsp+5h] [rbp-23h]
unsigned __int16 v14; // [rsp+6h] [rbp-22h]
unsigned __int64 v15; // [rsp+8h] [rbp-20h]

v15 = __readfsqword(0x28u);
v13 = 0;
v14 = 0;
if ( a2 )
{
v2 = a1;
v3 = a1 + (unsigned int)(a2 - 1) + 1;
do
{
while ( 1 )
{
v13 = *(_BYTE *)(++v2 - 1);
sub_55883B5AF270(&v13, &v13);
v4 = v14 ^ (v13 << 8);
if ( ((v14 ^ (v13 << 8)) & 0x8000u) != 0 )
v5 = 2 * v4 ^ 0x1021;
else
v5 = 2 * v4;
v6 = 2 * v5 ^ 0x1021;
if ( v5 >= 0 )
v6 = 2 * v5;
v7 = 2 * v6 ^ 0x1021;
if ( v6 >= 0 )
v7 = 2 * v6;
v8 = 2 * v7 ^ 0x1021;
if ( v7 >= 0 )
v8 = 2 * v7;
v9 = 2 * v8 ^ 0x1021;
if ( v8 >= 0 )
v9 = 2 * v8;
v10 = 2 * v9 ^ 0x1021;
if ( v9 >= 0 )
v10 = 2 * v9;
v11 = 2 * v10 ^ 0x1021;
if ( v10 >= 0 )
v11 = 2 * v10;
if ( v11 < 0 )
break;
v14 = 2 * v11;
if ( v2 == v3 )
goto LABEL_19;
}
v14 = 2 * v11 ^ 0x1021;
}
while ( v2 != v3 );
}
LABEL_19:
sub_55883B5AF2D0(&v14, &v14);
return v14;
}

也是通过0x1021可知,这是一个crc16校验,校验码为0x1021

if ( v2 == v3 )
goto LABEL_19;

由这句话可知,v2会从起始字节一直遍历到v3处,那么a2就要构造得刚好把这些值覆盖,可知a2=6,所以解密后的那个数组的最后一个字节就是2

调试tips:

有效的加密字符串 4290cd6dc6ae54cc

解密后得0123456789abcd02

大致思路

  1. 程序读入16位的十六进制数,并转化为8字节
  2. 将数据进行xtea解密

同时这个自带加密函数,只需要把输入的参数取反,就是逆过程了

  1. 将解密后的数据进行crc16校验(校验码为0x1021),得到结果0x69E2
  2. 这个解密后的数组去异或0x17能得到”Bingo!” 字符串

solve

def crc16(data: bytes, poly=0x1021):
'''
CRC-16-CCITT Algorithm
'''
data = bytearray(data)
crc = 0xFFFF
for b in data:
cur_byte = 0xFF & b
for _ in range(0, 8):
if (crc & 0x0001) ^ (cur_byte & 0x0001):
crc = (crc >> 1) ^ poly
else:
crc >>= 1
cur_byte >>= 1
crc = (~crc & 0xFFFF)
crc = (crc << 8) | ((crc >> 8) & 0xFF)

return crc & 0xFFFF


enc = "Bingo!"
dec = []

for i in enc:
dec.append(hex(ord(i) ^ 0x17))
dec.append(hex(6))
dec.append(hex(2))

print(dec)

557e797078360602

然后再拿这个字符串去加密一遍,就得到flag了

rctf{2a2e71aab6168fb6}

[11] Accepting connection from 192.168.133.1...
Input right flag you can got 'Bingo!' :2a2e71aab6168fb6

Bingo!

后续:据说倒数第二字节是个padding

有空再填坑 https://blog.csdn.net/shift_wwx/article/details/84256774

原文作者:mrh929

原文链接:https://mrh1s.top/posts/122a7c75/

发表日期:May 21st 2019, 12:49:39 am

更新日期:May 21st 2019, 8:51:31 pm

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. RCTF_babyre1
    1. 1.1. 思路
      1. 1.1.1. main
      2. 1.1.2. xtea_keychanged中的一个子函数
      3. 1.1.3. xtea_keychanged
      4. 1.1.4. crc16
      5. 1.1.5. 大致思路
    2. 1.2. solve