参考文章:
【BUUCTF逆向 [2019红帽杯]xx】_nb_What_DG的博客-CSDN博客
re学习笔记(26)BUUCTF-re-[2019红帽杯]xx_Forgo7ten的博客-CSDN博客
还有B站 水番正文
IDA64位载入
shift+F12查看字符串
交叉引用找到关键代码
使用findcrypt插件找到加密算法为tea系列,进函数内部查看知道为xxtea
// 代码审计(正向开发经验)
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 flaglen2; // rbx
__int64 flaglen1; // rax
__int128 *malloc5; // rax
__int64 data; // r11
__int128 *malloc7; // r14
int v8; // edi
__int128 *malloc9; // rsi
char tmp; // r10
int v11; // edx
__int64 v12; // r8
unsigned __int64 datalen; // rcx
__int64 v14; // rcx
unsigned __int64 v15; // rax
unsigned __int64 i; // rax
__int64 encodeflag; // rax
size_t encodeflaglen; // rsi
_BYTE *enflag; // rbx
_BYTE *v20; // r9
int I; // r11d
char *data1; // r8
__int64 n; // rcx
char v24; // al
__int64 v25; // r9
__int64 v26; // rdx
__int64 v27; // rax
size_t Size; // [rsp+20h] [rbp-48h] BYREF
__int128 v30; // [rsp+28h] [rbp-40h] BYREF
int v31; // [rsp+38h] [rbp-30h]
int v32; // [rsp+3Ch] [rbp-2Ch]
int flag[4]; // [rsp+40h] [rbp-28h] BYREF
int v34; // [rsp+50h] [rbp-18h]
*(_OWORD *)flag = 0i64;
v34 = 0;
sub_1400018C0(std::cin, argv, flag); // 在C++中,std其实就是standard标准的意思。
// 例如std::cin就是标准输入,std::cout就是标准输出的意思。
flaglen2 = -1i64;
flaglen1 = -1i64;
do
++flaglen1;
while ( *((_BYTE *)flag + flaglen1) );
if ( flaglen1 != 19 ) // flag 为 19位
{
sub_140001620((__int64 *)std::cout, (__int64)"error\n");
_exit((int)flag);
}
malloc5 = (__int128 *)operator new(5ui64); // operator new申请内存(operator new后面将进行详细说明,这里理解为C语言中的malloc)
data = *(_QWORD *)&Code; // 将Code数据给data,
// Code为"qwertyuiopasdfghjklzxcvbnm1234567890"
malloc7 = malloc5;
v8 = 0;
malloc9 = malloc5;
do
{
tmp = *((_BYTE *)malloc9 + (char *)flag - (char *)malloc5);
v11 = 0;
*(_BYTE *)malloc9 = tmp;
v12 = 0i64;
datalen = -1i64;
do
++datalen;
while ( *(_BYTE *)(data + datalen) );
if ( datalen )
{
do
{
if ( tmp == *(_BYTE *)(data + v12) )
break;
++v11;
++v12;
}
while ( v11 < datalen );
}
v14 = -1i64;
do
++v14;
while ( *(_BYTE *)(data + v14) );
if ( v11 == v14 )
_exit(data);
malloc9 = (__int128 *)((char *)malloc9 + 1);// 下一位
}
while ( (char *)malloc9 - (char *)malloc5 < 4 );
*((_BYTE *)malloc5 + 4) = 0;
do
++flaglen2;
while ( *((_BYTE *)flag + flaglen2) );
v15 = 0i64;
v30 = *malloc7;
while ( *((_BYTE *)&v30 + v15) )
{
if ( !*((_BYTE *)&v30 + v15 + 1) )
{
++v15;
break;
}
if ( !*((_BYTE *)&v30 + v15 + 2) )
{
v15 += 2i64;
break;
}
if ( !*((_BYTE *)&v30 + v15 + 3) )
{
v15 += 3i64;
break;
}
v15 += 4i64;
if ( v15 >= 0x10 )
break;
}
for ( i = v15 + 1; i < 0x10; ++i )
*((_BYTE *)&v30 + i) = 0; // 全部赋值为0
encodeflag = (__int64)sub_140001AB0((__int64)flag, flaglen2, (unsigned __int8 *)&v30, &Size);
encodeflaglen = Size;
enflag = (_BYTE *)encodeflag;
v20 = operator new(Size); // v20开辟一块内存区域
I = 1;
*v20 = enflag[2];
data1 = v20 + 1; // v22与v20指向了同一片内存区域
v20[1] = *enflag;
v20[2] = enflag[3];
v20[3] = enflag[1];
v20[4] = enflag[6];
v20[5] = enflag[4];
v20[6] = enflag[7];
v20[7] = enflag[5];
v20[8] = enflag[10];
v20[9] = enflag[8];
v20[10] = enflag[11];
v20[11] = enflag[9];
v20[12] = enflag[14];
v20[13] = enflag[12];
v20[14] = enflag[15];
v20[15] = enflag[13];
v20[16] = enflag[18];
v20[17] = enflag[16];
v20[18] = enflag[19];
v20[19] = enflag[17];
v20[20] = enflag[22];
v20[21] = enflag[20];
v20[22] = enflag[23];
for ( v20[23] = enflag[21]; I < encodeflaglen; ++data1 )// 异或操作
{
n = 0i64;
if ( I / 3 > 0 ) // I从3开始
{
v24 = *data1;
do
{
v24 ^= v20[n++]; // v22^v20
*data1 = v24;
}
while ( n < I / 3 ); // I/3是跟前多少位...
}
++I;
}
*(_QWORD *)&v30 = 0xC0953A7C6B40BCCEui64;
v25 = v20 - (_BYTE *)&v30;
*((_QWORD *)&v30 + 1) = 0x3502F79120209BEFi64;
v26 = 0i64;
v31 = 0xC8021823;
v32 = 0xFA5656E7;
do
{
if ( *((_BYTE *)&v30 + v26) != *((_BYTE *)&v30 + v26 + v25) )
_exit(v8 * v8);
++v8;
++v26;
}
while ( v26 < 24 );
v27 = (__int64)sub_140001620((__int64 *)std::cout, (__int64)"You win!");
std::ostream::operator<<(v27, sub_1400017F0);
return 0;
}
流程为
- 先判断输入的字符串是否都在程序实现存储的数据Code中
- 然后取前四个字符作为xxtea的密钥,(不满位数右端补零)
- 然后对输入的字符串进行加密
- 之后对加密的字符串打乱顺序
- 之后异或操作
- 再与存储的数据进行比对
其中第6部存储的数据为
v30,v30+1,v31,v32在栈上存放的位置相连。小端序存放,需将其反过来写。
开始写脚本:
一.逆异或 二.正序
data0 = "CEBC406B7C3A95C0EF9B202091F70235231802C8E75656FA"#为提取出来的v29,v29+1,v30,v31
data = []
for i in range(0,len(data0),2):
data.append(int(data0[i]+data0[i+1],16))#每两位为整体,将16进制转换为10进制
print(data)
for i in range(len(data)-1,-1,-1):
for j in range(i//3):
data[i] ^= data[j]
#1.进行的异或操作
print(data)
biao = [2,0,3,1,6,4,7,5,10,8,11,9,14,12,15,13,18,16,19,17,22,20,23,21]#置换表
shuju = [1]*24
for i in range(24):
shuju[biao[i]] = data[i]#2.将其按照一定顺序置换
print(shuju)
print(len(shuju))
三.得到加密数据 密钥key xxtea解密
因为用户输入的为19位字符,也就是38位十六进制字符串,而上面生成了40位,所以要去掉最后两位,也就是去掉13。
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "encode.h"
int main()
{
int i,j,data[] = { 188, 165, 206, 64, 244, 178, 178, 231, 169, 18, 157, 18, 174, 16, 200, 91, 61, 215, 6, 29, 220, 112, 248, 220 };
for (i = 1; i <= 6; i++)//3.导出加密的数据,将其转换为十六进制并用小端序来表示
{
printf("0x");
for (j = i * 4 - 1; j > (i - 1) * 4 - 1; j--)
printf("%x", data[j]);
printf(",");
}
printf("\n0x");
int key[] = { 'f','l','a','g' };//导出密钥,将其转换为十六进制并用小端序来表示
for (i = 3; i >= 0; i--)
printf("%x", key[i]);
printf(",");
for (i = 0; i < 3; i++)//密钥为4个32位的数,1个字符4bit,4个字符为32bit,还差3个32bit的数
printf("0x0,");
}
图片展示
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do
{
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++)
{
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
}
while (--rounds);
}
else if (n < -1) /* Decoding Part */
{
n = -n;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}
int main()
{
uint32_t v[6]= {0x40cea5bc,0xe7b2b2f4,0x129d12a9,0x5bc810ae,0x1d06d73d,0xdcf870dc};
uint32_t const k[4]= {0x67616c66,0,0,0};
int n= 6; //n的绝对值表示v的长度,取正表示加密,取负表示解密
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
//printf("加密前原始数据:%u %u\n",v[0],v[1]);
//btea(v, n, k);
//printf("加密后的数据:%u %u\n",v[0],v[1]);
btea(v, -n, k);
for (int i = 0 ; i < 6;i++){
printf("%x",v[i]);
printf(" ");
}
return 0;
}
m = [0x67616c66,0x5858437b,0x646e615f,0x742b2b5f,0x7d6165]
四.最后将生成的十六进制数用小端序表示
m = [0x67616c66,0x5858437b,0x646e615f,0x742b2b5f,0x7d6165]
flag = ''
for i in m:
flag += libnum.n2s(i).decode()[::-1]
print(flag)
#flag{CXX_and_++tea}
总结:
1.如果涉及多字节了,应该考虑字节的顺序,比如这个题,就应该将处理的数据都转换为小端序2.这个题的加密步骤有多步(3步:xxtea/乱序/异或)
3.要增强python工具,提高代码能力