Reverse
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
大致思路
- 程序读入16位的十六进制数,并转化为8字节
- 将数据进行xtea解密
同时这个自带加密函数,只需要把输入的参数取反,就是逆过程了
- 将解密后的数据进行crc16校验(校验码为0x1021),得到结果0x69E2
- 这个解密后的数组去异或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
babyre2
思路
alarm
程序一开头就有一个alarm函数,这会使我们在调试的时候中途退出,直接把call alarm nop掉即可
sub_55761565891A
__int64 __fastcall sub_55761565891A(void *a1)
{
unsigned int v2; // [rsp+10h] [rbp-10h]
signed int v3; // [rsp+14h] [rbp-Ch]
v2 = 0;
print_notice();
memset(a1, 0, 0x100uLL);
read(0, a1, 0xFFuLL);
v3 = strlen((const char *)a1);
if ( v3 <= 16 )
{
if ( v3 > 7 )
v2 = 1;
else
print_notice();
}
else
{
print_notice();
}
return v2;
}
sub_5576156589CC
__int64 __fastcall sub_5576156589CC(void *a1)
{
unsigned int v2; // [rsp+1Ch] [rbp-14h]
signed int i; // [rsp+20h] [rbp-10h]
signed int v4; // [rsp+24h] [rbp-Ch]
v2 = 0;
print_notice();
memset(a1, 0, 0x100uLL);
read(0, a1, 0xFFuLL);
v4 = strlen((const char *)a1);
if ( v4 <= 16 )
{
if ( v4 > 7 )
{
v2 = 1;
for ( i = 0; i < v4; ++i )
{
if ( *((_BYTE *)a1 + i) <= 9 || *((_BYTE *)a1 + i) > 99 )
{
print_notice();
return 0;
}
}
}
else
{
print_notice();
}
}
else
{
print_notice();
}
return v2;
}
简单的读入账户和密码,不赘述
不赘述就出问题了
读密码的时候你没发现一个奇怪的事情吗?
if ( ((_BYTE )a1 + i) <= 9 || ((_BYTE )a1 + i) > 99 )
密码可以不是可见字符
sub_557615658ACE
char *__fastcall sub_557615658ACE(__int64 a1, __int64 a2)
{
__int64 v2; // rdx
int i; // [rsp+10h] [rbp-40h]
__int64 v5; // [rsp+14h] [rbp-3Ch]
__int64 dest; // [rsp+20h] [rbp-30h]
__int64 v7; // [rsp+28h] [rbp-28h]
char v8; // [rsp+30h] [rbp-20h]
char v9; // [rsp+31h] [rbp-1Fh]
char v10; // [rsp+32h] [rbp-1Eh]
char v11; // [rsp+33h] [rbp-1Dh]
char v12; // [rsp+34h] [rbp-1Ch]
char v13; // [rsp+35h] [rbp-1Bh]
char v14; // [rsp+36h] [rbp-1Ah]
char v15; // [rsp+37h] [rbp-19h]
char v16; // [rsp+38h] [rbp-18h]
char v17; // [rsp+39h] [rbp-17h]
char v18; // [rsp+3Ah] [rbp-16h]
char v19; // [rsp+3Bh] [rbp-15h]
char v20; // [rsp+3Ch] [rbp-14h]
char v21; // [rsp+3Dh] [rbp-13h]
char v22; // [rsp+3Eh] [rbp-12h]
char v23; // [rsp+3Fh] [rbp-11h]
char v24; // [rsp+40h] [rbp-10h]
unsigned __int64 v25; // [rsp+48h] [rbp-8h]
v25 = __readfsqword(0x28u);
v8 = -8;
v9 = -44;
v10 = -43;
v11 = -36;
v12 = -55;
v13 = -38;
v14 = -49;
v15 = -50;
v16 = -41;
v17 = -38;
v18 = -49;
v19 = -46;
v20 = -44;
v21 = -43;
v22 = -56;
v23 = -102;
v24 = -79;
dest = 0LL;
v7 = 0LL;
v5 = (unsigned int)strlen((const char *)a1);
if ( (signed int)v5 <= 16 )
{
memcpy(&dest, (const void *)a1, (signed int)v5);// 复制内存
for ( i = 0; 16 - (signed int)v5 > i; ++i )
*((_BYTE *)&dest + i + (signed int)v5) = i + 1;// 对于没有满16位的account 自动补全16位(补全一个从0开始的递增序列)
}
else
{
v2 = *(_QWORD *)(a1 + 8);
dest = *(_QWORD *)a1;
v7 = v2;
}
return sub_55761565981E(&v8, 17, (__int64)&dest, 16, (int *)a2);
}
char *__fastcall sub_55761565981E(const void *buf, int a2, __int64 a1, int a4, int *a5)
{
int *v6; // [rsp+0h] [rbp-40h]
__int64 acc; // [rsp+8h] [rbp-38h]
signed int v8; // [rsp+24h] [rbp-1Ch]
int v9; // [rsp+28h] [rbp-18h]
char *dest; // [rsp+30h] [rbp-10h]
acc = a1;
v6 = a5;
dest = 0LL;
v8 = 0;
if ( buf )
{
if ( a2 > 0 )
{
if ( a1 )
{
if ( a4 == 16 )
{
if ( a5 )
{
*a5 = 0;
v9 = 4 * (a2 / 4 + 1);
dest = (char *)malloc(v9 + 1);
if ( dest )
{
memcpy(dest, buf, a2);
memset(&dest[a2], 4 - a2 % 4, 4 - a2 % 4);// 填充了3
dest[v9] = 0;
xtea((unsigned int *)dest, v9 / 4, acc);// 这里是执行了加密的过程,account作为密钥
*v6 = v9;
v8 = 1;
}
}
}
}
}
}
if ( !v8 )
{
if ( dest )
{
free(dest);
dest = 0LL;
}
if ( v6 )
*v6 = 0;
}
return dest;
}
简单来说就是利用xtea算法,将buf(上面的数组)加密了,密钥是账户名
sub_557615658C02
__int64 __fastcall sub_557615658C02(__int64 a1)
{
signed int v2; // [rsp+14h] [rbp-81Ch]
int v3; // [rsp+18h] [rbp-818h]
unsigned int v4; // [rsp+18h] [rbp-818h]
int i; // [rsp+1Ch] [rbp-814h]
char buf[2048]; // [rsp+20h] [rbp-810h]
char v7; // [rsp+820h] [rbp-10h]
unsigned __int64 v8; // [rsp+828h] [rbp-8h]
v8 = __readfsqword(0x28u);
memset(buf, 0, sizeof(buf));
v7 = 0;
print_notice();
read(0, buf, 0x400uLL);
v3 = strlen(buf);
if ( v3 & 1 || v3 > 1024 ) // 长度不超过1024,且必须为偶数
{
v4 = 0;
print_notice();
}
else
{
v2 = 1;
for ( i = 0; i < v3; ++i ) // 检测输入是否合规
{
if ( (buf[i] <= 47 || buf[i] > 57) && (buf[i] <= 96 || buf[i] > 102) && (buf[i] <= 64 || buf[i] > 70) )// 只准输入数字,大小写的字母a~f
{
print_notice();
v2 = 0;
break;
}
}
if ( v2 ) // 如果输入合规
v4 = sub_5576156587C6(buf, v3, (_QWORD *)a1);
else
v4 = 0;
}
return v4;
}
读入了偶数个字节的数据,并且长度不超过1024,数据只能在0~9 a~f A~F的范围
sub_557615658DD6
_BYTE *__fastcall sub_557615658DD6(const char *passwd, __int64 data, int chunk, _DWORD *a4)
{
_DWORD *v5; // [rsp+0h] [rbp-50h]
int v6; // [rsp+Ch] [rbp-44h]
signed int v7; // [rsp+2Ch] [rbp-24h]
signed int v8; // [rsp+30h] [rbp-20h]
int i; // [rsp+34h] [rbp-1Ch]
int v10; // [rsp+38h] [rbp-18h]
int v11; // [rsp+3Ch] [rbp-14h]
_BYTE *ptr; // [rsp+40h] [rbp-10h]
v6 = chunk;
v5 = a4;
v7 = 0;
ptr = 0LL;
if ( data )
{
if ( chunk > 0 )
{
if ( passwd )
{
v10 = strlen(passwd);
if ( v10 > 0 )
{
ptr = malloc(v10 + 1);
if ( ptr )
{
ptr[v10] = 0;
v8 = 1;
for ( i = 0; i < v10; ++i )
{
v11 = passwd[i] - (passwd[i] % 10 + passwd[i] / 10);// 由passwd生成了一个偏移地址
if ( v11 >= v6 )
{
v8 = 0;
break;
}
ptr[i] = *(_BYTE *)(v11 + data); // ptr[i]就等于这个偏移地址上面的数据
}
if ( v8 ) // 若正常退出
{
*v5 = v10;
v7 = 1;
}
}
}
}
}
}
if ( !v7 && ptr )
{
free(ptr);
ptr = 0LL;
}
return ptr;
}
用passwd可以算出来一个偏移,对应着data中的数据,生成了一个ptr数组
sub_557615658FB0
for ( j = 0; j < passwd_len; ++j )
*((_BYTE *)&dest + j) ^= 0xCCu; // ptr进行异或0xcc
::ptr = sub_5576156599B9(v6, v7, (__int64)&dest, 16, &dword_55761585B058);
sub_5576156599B9:
xtea((unsigned int *)dest, buf_len / -4, v7);// 这里执行解密的过程,解密encrypted,密钥为ptr_xor
公式
xtea(xtea(buf, accout, ENCRYPT) ,f(data,passwd) , DECRYPT)
f(data, passwd) == accout
passwd到data是一个很简单的映射,映射完之后要异或一个0xcc,异或结果应该刚好等于account
solve
script from dalao
https://balsn.tw/ctf_writeup/20190518-rctf2019/#babyre2
我自己还没用过pwntools。。。没想到要直接给程序传入非可见字符,所以就借用了一下国外大佬的脚本
from pwn import *
r=remote("139.180.215.222", 20000)
print r.recvuntil("account")
r.send("a"*16)
print r.recvuntil("password")
r.send("\x10"*16)
r.recvuntil("data")
r.send("010203040506070809ad0b0c0d0e0f") #ad=61^cc
r.shutdown("send")
r.interactive()