April_Note

发布时间 2023-05-01 19:20:37作者: Tree_24

tea,xtea,xxtea

在逆向过程中,常常会遇到tea加密,本文将系统地总结一下tea,xtea,xxtea

tea

简介

TEA加密算法是一种分组密码算法,其明文密文块为64比特,密钥长度为128比特。TEA算法利用不断增加的Delta(黄金分割率)值作为变化,使得每轮的加密是不同,该加密算法的迭代次数可以改变,建议的迭代次数为32轮。
值得一提的是Delta值一般为0x9E3779B9(-0x61C88647),这是判定TEA加密的特征之一

代码实现

#include <stdint.h>

void encrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0, i;           /* set up */
    uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i < 32; i++) {                       /* basic cycle start */
        sum += delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);  
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

void decrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;  /* set up */
    uint32_t delta=0x9e3779b9;                     /* a key schedule constant */
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
    for (i=0; i<32; i++) {                         /* basic cycle start */
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;                                   
    }                                              /* end cycle */
    v[0]=v0; v[1]=v1;
}

示例

#include <stdio.h>
#include <stdint.h>

void encrypt (uint32_t* v, uint32_t* k) {
    uint32_t sum = 0; 
    uint32_t v0 = v[0], v1 = v[1];
    uint32_t delta = 0x9e3779b9;
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];

    for (int i=0; i<32; i++) {
        sum += delta;
        v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
    }

    v[0]=v0; 
    v[1]=v1;
}

void decrypt (uint32_t* v, uint32_t* k) {
    uint32_t v0 = v[0], v1 = v[1];
    uint32_t delta = 0x9e3779b9;
    uint32_t sum = delta * 32;
    uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];

    for (int i=0; i<32; i++) {
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
        sum -= delta;
    }

    v[0]=v0; 
    v[1]=v1;
}

int main(){
    uint32_t v[2] = {0x3e8947cb,0xcc944639};
    uint32_t k[4]= {0x1122,0x2233,0x3344,0x4455};
    printf("Data is : %x %x\n", v[0], v[1]);
    encrypt(v, k);
    printf("Encrypted data is : %x %x\n", v[0], v[1]);
    decrypt(v, k);
    printf("Decrypted data is : %x %x\n", v[0], v[1]);
    return 0;
}
/*
Data is : 3e8947cb cc944639
Encrypted data is : 35ef37b1 464adcc9
Decrypted data is : 3e8947cb cc944639
*/

xtea

简介

XTEA是TEA的扩展,与TEA相比,XTEA使用更长的密钥和更多的迭代轮数,从而提高了安全性。XTEA使用128位密钥和64位块大小,而TEA使用64位密钥和64位块大小。

代码实现

#include <stdint.h>

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
    for (i=0; i < num_rounds; i++) {
        v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
        sum += delta;
        v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
    }
    v[0]=v0; v[1]=v1;
}

void decrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
    for (i=0; i < num_rounds; i++) {
        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
        sum -= delta;
        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
    }
    v[0]=v0; v[1]=v1;
}

示例

#include <stdio.h>
#include <stdint.h>

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
    for (i=0; i < num_rounds; i++) {
        v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
        sum += delta;
        v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
    }
    v[0]=v0; v[1]=v1;
}

void decrypt(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
    for (i=0; i < num_rounds; i++) {
        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
        sum -= delta;
        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
    }
    v[0]=v0; v[1]=v1;
}

int main(){
    uint32_t v[3]={0x61657478,0x5f73695f,0x0};
    uint32_t v1[2]={0x79736165,0x0};
    uint32_t const k[4]={0X95C4C,0X871D,0X1A7B7,0X12C7C7};
    unsigned int r=32;//num_rounds建议取值为32
    // v为要加密的数据是两个32位无符号整数
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
    printf("Data is:%s%s\n",(char*)v,(char*)v1);
    encrypt(r, v, k);
    encrypt(r, v1, k);
    printf("Encrypted data is:%u %u %u\n",v[0],v[1],v1[0]);
    decrypt(r, v, k);
    decrypt(r, v1, k);
    printf("Decrypted data is:%s%s\n",(char*)v,(char*)v1);
    return 0;
}
/*
Data is:xtea_is_easy
Encrypted data is:543258208 1827651953 369487673
Decrypted data is:xtea_is_easy
*/

xxtea

简介

XXTEA使用更复杂的运算方式,它的块大小可以是任意的,密钥也可以是任意长度的。在加密时,XXTEA会对明文进行分块,然后每个块都会进行加密,加密后的结果再进行拼接,最终形成密文。在解密时,XXTEA会对密文进行分块,然后每个块都会进行解密,解密后的结果再进行拼接,最终形成明文。

代码实现

#include <stdint.h>
#include <string.h>

#define DELTA 0x9E3779B9
#define MX (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[(p & 3) ^ e] ^ z)

void xxtea_encrypt(uint32_t *v, uint32_t len, uint32_t *k) {
    uint32_t n = len - 1;
    uint32_t y, z, sum = 0, e, p, q;
    q = 6 + 52 / len;
    while (q-- > 0) {
        sum += DELTA;
        e = sum >> 2 & 3;
        for (p = 0; p < n; p++) {
            y = v[p + 1];
            z = v[p] += MX;
        }
        y = v[0];
        z = v[n] += MX;
    }
}

void xxtea_decrypt(uint32_t *v, uint32_t len, uint32_t *k) {
    uint32_t n = len - 1;
    uint32_t y, z, sum, e, p, q;
    q = 6 + 52 / len;
    sum = q * DELTA;
    while (sum != 0) {
        e = sum >> 2 & 3;
        for (p = n; p > 0; p--) {
            z = v[p - 1];
            y = v[p] -= MX;
        }
        z = v[n];
        y = v[0] -= MX;
        sum -= DELTA;
    }
}

示例

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#define DELTA 0x9E3779B9
#define MX (z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[(p & 3) ^ e] ^ z)

void xxtea_encrypt(uint32_t *v, uint32_t len, uint32_t *k) {
    uint32_t n = len - 1;
    uint32_t y, z, sum = 0, e, p, q;
    q = 6 + 52 / len;
    while (q-- > 0) {
        sum += DELTA;
        e = sum >> 2 & 3;
        for (p = 0; p < n; p++) {
            y = v[p + 1];
            z = v[p] += MX;
        }
        y = v[0];
        z = v[n] += MX;
    }
}

void xxtea_decrypt(uint32_t *v, uint32_t len, uint32_t *k) {
    uint32_t n = len - 1;
    uint32_t y, z, sum, e, p, q;
    q = 6 + 52 / len;
    sum = q * DELTA;
    while (sum != 0) {
        e = sum >> 2 & 3;
        for (p = n; p > 0; p--) {
            z = v[p - 1];
            y = v[p] -= MX;
        }
        z = v[n];
        y = v[0] -= MX;
        sum -= DELTA;
    }
}

int main() {
    // The message to encrypt and decrypt
    char message[] = "xxtea_is_easy_too";
    // The secret key for encryption and decryption
    char key[] = "secret_key";

    // Calculate the required lengths for the data and key
    size_t message_len = strlen(message);
    size_t key_len = strlen(key);

    // Calculate the number of uint32_t values required to store the data and key
    size_t data_len = (message_len + 3) / 4;
    size_t key_data_len = (key_len + 3) / 4;

    // Allocate memory for the uint32_t arrays for the data and key
    uint32_t *data = calloc(data_len, sizeof(uint32_t));
    uint32_t *key_data = calloc(key_data_len, sizeof(uint32_t));

    // Copy the data and key into the uint32_t arrays
    memcpy(data, message, message_len);
    memcpy(key_data, key, key_len);

    // Encrypt the data using the key
    xxtea_encrypt(data, data_len, key_data);

    // Print the encrypted data
    printf("Encrypted data: ");
    for (size_t i = 0; i < message_len; i++) {
        printf("%02x", ((uint8_t*)data)[i]);
    }
    printf("\n");

    // Decrypt the data using the key
    xxtea_decrypt(data, data_len, key_data);

    // Print the decrypted data
    printf("Decrypted data: %s\n", (char*)data);

    // Free the allocated memory
    free(data);
    free(key_data);

    return 0;
}
/*
Original data:
xxtea_is_easy_too

Encrypted data:
1C178B4E 4A63ABD4 7B734D34 4B7B858C

Decrypted data:
78787465 615F6973 5F656173 795F746F
6F
可以看到,原始数据为字符串"xxtea_is_easy_too",经过XXTEA加密后得到了一组十六进制的密文,然后再将这个密文进行解密后,得到了原始数据。
需要注意的是,解密后的结果多出了一个0x6F,这是因为在加密时,为了填充满64位的数据块,加密函数对数据进行了填充。在解密时,需要将填充的字节去掉。在本例中,填充的是一个字节0x0F,因此在解密时会多出一个0x6F。
*/

逆向中TEA系列加密的识别

解决逆向题大部分出现TEA的场合都是 识别算法->编写对应解密程序
分析二进制文件中的算法的时候有几个识别的特征

  • 可能存在针对64bit以及128bit数字的操作(输入的msg和key) ,一般会用无符号的32位的数组表示
  • 存在先进行位移,然后异或的类似操作((z>>5^y<<2) 这类混合变换)(z>>5^y<<2)就是xxtea加密了,存在(v0 << 4)(v0 >> 5)移位就是tea和xtea加密了
  • 前面一个复杂的混合变换的结果可能会叠加到另一个值上,两者相互叠加(Feistel 结构)
  • 获取密钥的时候,会使用某一个常量值作为下标(key[(sum>>11) & 3])存在轮换的方式获得密钥 就是xtea或者xxtea了
  • 会在算法开始定义一个delta,并且这个值不断的参与算法,但是从来不会受到输入的影响(delta数值如果没有魔改就是0x9e3779b9)如果出现了0x9e3779b9这个数字一般就能确定是TEA加密系列

例题

[GDOUCTF2023]tea

查壳是64位无壳,ida64直接查看字符串
img
交叉引用一下you are right,定位到关键函数

__int64 sub_140016230()
{
  char *v0; // rdi
  __int64 i; // rcx
  char v3[32]; // [rsp+0h] [rbp-20h] BYREF
  char v4; // [rsp+20h] [rbp+0h] BYREF
  int v5; // [rsp+24h] [rbp+4h]
  int v6; // [rsp+44h] [rbp+24h]
  int v7[12]; // [rsp+68h] [rbp+48h] BYREF
  _DWORD v8[16]; // [rsp+98h] [rbp+78h] BYREF
  int v9[31]; // [rsp+D8h] [rbp+B8h] BYREF
  int j; // [rsp+154h] [rbp+134h]
  int k; // [rsp+174h] [rbp+154h]
  int l; // [rsp+194h] [rbp+174h]

  v0 = &v4;
  for ( i = 102i64; i; --i )
  {
    *(_DWORD *)v0 = -858993460;
    v0 += 4;
  }
  sub_14001137F(&unk_140023009);
  v5 = 32;
  v6 = 0;
  v7[0] = 1234;
  v7[1] = 5678;
  v7[2] = 9012;
  v7[3] = 3456;
  memset(v8, 0, 0x28ui64);
  v9[15] = 0;
  v9[23] = 0;
  sub_1400113E8();
  for ( j = 0; j < 10; ++j )
    sub_1400111FE("%x", &v8[j]);                // 读入16进制数组v8
  sub_140011339(v7);                            // v7 = [2233,4455,6677,8899]
  sub_140011145(v8, v9);                        // v8赋值给v9
  sub_1400112B7(v8, v7);                        // xtea加密v8,v7为key
  v6 = sub_140011352(v8);                       
  // 判断v8是否等于[0x1A800BDA,0xF7A6219B,0x491811D8,0xF2013328,0x156C365B,
  //0x3C6EAAD8,0x84D4BF28,0xF11A7EE7,0x3313B252,0xDD9FE279]
  if ( v6 )
  {
    sub_140011195("you are right\n");
    for ( k = 0; k < 10; ++k )
    {
      for ( l = 3; l >= 0; --l )
        sub_140011195("%c", (unsigned __int8)((unsigned int)v9[k] >> (8 * l)));// 输出flag
    }
  }
  else
  {
    sub_140011195("fault!\nYou can go online and learn the tea algorithm!");
  }
  sub_140011311(v3, &unk_14001AE90);
  return 0i64;
}

简单分析如上,重点看一下xtea加密函数

__int64 __fastcall sub_140011900(__int64 a1, __int64 a2)
{
  __int64 result; // rax
  int v3; // [rsp+44h] [rbp+24h]
  int i; // [rsp+64h] [rbp+44h]
  unsigned int v5; // [rsp+84h] [rbp+64h]
  unsigned int v6; // [rsp+C4h] [rbp+A4h]

  result = sub_14001137F(&unk_140023009);
  for ( i = 0; i <= 8; ++i )
  {
    v5 = 0;
    v6 = 256256256 * i;
    v3 = i + 1;
    do
    {
      ++v5;
      *(_DWORD *)(a1 + 4i64 * i) += v6 ^ (*(_DWORD *)(a1 + 4i64 * v3)
                                        + ((*(_DWORD *)(a1 + 4i64 * v3) >> 5) ^ (16 * *(_DWORD *)(a1 + 4i64 * v3)))) ^ (v6 + *(_DWORD *)(a2 + 4i64 * (v6 & 3)));
      *(_DWORD *)(a1 + 4i64 * v3) += (v6 + *(_DWORD *)(a2 + 4i64 * ((v6 >> 11) & 3))) ^ (*(_DWORD *)(a1 + 4i64 * i)
                                                                                       + ((*(_DWORD *)(a1 + 4i64 * i) >> 5) ^ (16 * *(_DWORD *)(a1 + 4i64 * i))));
      v6 += 256256256;
    }
    while ( v5 <= 0x20 );
    result = (unsigned int)(i + 1);
  }
  return result;
}

32次迭代的变体xtea加密,可以看出来delta值被魔改了,是v6=256256256

exp

def xtea_decrypt(data,key):
  for j in range(8,-1,-1):
    i = 0
    delta = 256256256
    sum = delta * (32 + j)
    n = j + 1
    while i <= 32:
        i += 1
        data[n] = (data[n] - (((key[(sum >> 11) & 3]) + sum) ^ (((data[j] << 4) ^ (data[j] >> 5)) + data[j]))) & 0xffffffff
        data[j] = (data[j] - (((key[sum & 3] + sum) ^ ((data[n] << 4) ^ (data[n] >> 5)) + data[n]) ^ sum)) & 0xffffffff
        sum -= delta
  return data

v8 = [0x1A800BDA,0xF7A6219B,0x491811D8,0xF2013328,0x156C365B,
      0x3C6EAAD8,0x84D4BF28,0xF11A7EE7,0x3313B252,0xDD9FE279]
key = [2233,4455,6677,8899]

flag = xtea_decrypt(v8,key)

for i in range(10):
    for j in range(3,-1,-1):
        print(chr((flag[i] >> (j * 8)) & 0xFF), end='')

有个地方要特别注意的就是读入的v8是16进制无符号32位整数
如果不进行& 0xffffffff,运行结果是乱码
这耗费了我好多时间检查,问题出在C和Python对无符号整数的处理方式不同。在C中,当无符号整数溢出时,它会回绕到0。但是,在Python中,当整数溢出时,它们会自动升级为长整数。所以需要通过& 0xffffffff来保证解密出来是无符号32位整数。
最终运行结果HZCTF{hzCtf_94_re666fingcry5641qq}
根据题目要求flagNSSCTF{hzCtf_94_re666fingcry5641qq}


Python中PIL库中的Image类

逆向中可能会遇到有关图像的问题,总结了一些Python的PIL库中的Image类的一些函数,通过这些函数可以对数据进行一系列操从而得到我们想要的图片

函数 参数 作用 使用举例
new mode, size, color 新建图像 Image.new('RGB', (200, 100), 'red')
open image_name 加载图像 img = Image.open('cat.jpg')
save file_name, file_format 保存图像 img.save('picture.jpg')
crop (left, upper, right, lower) 裁剪图片 crop_img = img.crop((420, 150, 860 , 560))
paste im, (x, y) 粘贴图片方法 img.paste(crop_img, (0, 0))
resize (width, height) 调整图片大小 resized_img = img.resize((width, height))
thumbnail (width, height) 制作缩略图,等比例缩放图片大小 img.thumbnail((w//3, h//3))
rotate angle 围绕其中心逆时针旋转给定的度数。返回此图像的副本。 rotate_img = img.rotate(30)
show 显示此图像。此方法主要用于调试目的。 img.show()
copy 复制图像。返回一个新的Image对象。 copy_img = img.copy()
crop (left, upper, right, lower) 矩形元组参数。返回一个新的Image对象。对原图没有影响。 裁剪图片。 crop_img = img.crop((420, 150, 860 , 560))
transpose method,通过FLIP_LEFT_RIGHT和FLIP_TOP_BOTTOM可以分别实现水平竖直镜像翻转 创建并返回此图像的转置副本。 img.transpose(Image.FLIP_LEFT_RIGHT)
filter filter 对此图像应用图像增强滤镜。返回一个新的Image对象。 img.filter(ImageFilter.BLUR)
convert mode 转换图像模式。返回一个新的Image对象。 img.convert('L')
getpixel xy 返回给定位置的像素值。 img.getpixel((0,0))
putpixel xy, value 修改给定位置的像素值。 img.putpixel((0,0), value)
point lut, mode 将查找表应用于图像。返回一个新的Image对象。 img.point(lut)
split 将图像拆分为单独的波段。返回一个元组,其中包含单独的波段。 r, g, b = img.split()
merge mode, bands 将单独的波段合并为单个多波段图像。返回一个新的Image对象。 img = Image.merge('RGB', (r, g, b))
load 分配存储并加载图像数据。返回一个像素访问对象。 px = img.load()

例题

[CTFShow愚人杯]easy_re

32位无壳,拖进IDA看看

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // ebx
  int v4; // ecx
  unsigned int v5; // edi
  unsigned int v6; // kr00_4
  int v8; // [esp-4h] [ebp-414h]
  int v9; // [esp+10h] [ebp-400h]
  int v10; // [esp+18h] [ebp-3F8h] BYREF
  int v11; // [esp+1Ch] [ebp-3F4h] BYREF
  char v12[1004]; // [esp+20h] [ebp-3F0h] BYREF

  sub_401460();
  sub_401700(v12);
  sub_401460();
  std::istream::operator>>(std::cin, &v10);
  std::istream::operator>>(std::cin, &v11);
  v3 = v10 % 299;
  v4 = v11 % 299;
  v5 = 0;
  v9 = v11 % 299;
  v6 = strlen(v12);
  if ( v6 )
  {
    do
    {
      v8 = dword_403AA0[300 * v3 + v4] ^ v12[v5];
      v3 = (v8 + v3) % 299;
      v9 = (v8 + v9) % 300;
      std::ostream::operator<<(std::cout, v8);
      sub_401460();
      v4 = v9;
      ++v5;
    }
    while ( v5 < v6 );
  }
  sub_401460();
  return 0;
}

只看得出来是个300*300的一个数组
记住403AA0地址是从28A0到5A6E3
尝试用x32dbg看看
img
也就是说用两个300以内数字去加密一段话
可以得到后面一大串的数字,尝试直接枚举出这两个key
利用flag的base64值是'ZmxhZ'来判断

from base64 import *
from pwn import u32 

def decrypt(k1,k2,cipher):
    tk1,tk2 = k1,k2
    m = []
    for v8 in cipher[::-1]:
        k1 = (k1-v8)%299
        k2 = (k2-v8)%300
        m.append(d_3aa0[300*k1+k2] ^ v8)
    base64_text = bytes(m[::-1])
    if base64_text[:5]==b'ZmxhZ':
        flag=b64decode(base64_text).decode('utf-8')
        print(base64_text,tk1,tk2,'\n',flag)

flag='' 
data = open('re1.exe', 'rb').read()[0x28a0: 0x5a6e0]
d_3aa0 = [u32(data[i:i+4]) for i in range(0, len(data), 4)]
a = [90,171,198,235,229,43,246,92,198,203,233,228,6,128,215,68,201,4,220,214,169,245,208,199,112,170,119,251,244,58,237,4,70,231,200,45,186,137,247,225,243,13,145,139,190,146,194,242,253,56,239,5,41,225,105,51,247,79,170,231,88,64,224,138,222,220,229,88,43,117,236,189,228,205,150,65,26,205,232,141,116,149,185,89,212,251,16,215,205,17,238,22,245,77,220,198,224,248,223,209,205,167,223,210,165,247,190,3,5,246,243,228,181,33,42,207,174,138,244,118,192,22,219,60,80,229,144,219,133,211,221,229,190,58,151,240,183,207,221,60,77,217,220,74,105,220,221,165,85,174,43,183,188,190,252,255,130,137,189,201,239,181,150,143,214,203,26,211,103,222,105,87,214,179,83,185,104,206,229,172,221,117,163,57,106,200,46,165,193,135,243,166,168,209,144,52,210,12,58,10,103,5,211,55,172,76,88,250,136,245,167,139,241,26,92,97,139,241,137,27,53,211,251,191,240,173,14,231,241,242,255,122,144,97,234,36,175,155,253,35,156,229,19,166,191,140,195,218,130,35,200,178,245,41,162,243,214,222,87,83,195,144,55,159,208,241,193,233,204,228,196,105,84,58,220,226,1,47,248,138,177,124,236,53,210,79,250,106,27,244,251,203,210,103,213,218,183,4,40,28,12,175,52,224,203,89,176,174,175,233,43,20,103,152,201,4,148,76,241,103,135,139,136,246,80,184,255,194,149,239,206,207,246,166,20,63,202,199,177,214,60,99,74,211,219,94,247,193,40,212,197,175,30,244,41,24,113,27,249,213,225,55,188,193,165,220,174,252,105,154,74,126,174,255,110,169,103,44,246,255,98,251,211,87,171,62,67,250,69,149,18,77,159,137,168,231,187,97,174,115,243,44,128,151,90,246,83,11,138,67,184,22,53,228,230,252,76,112,20,136,131,90,233,248,67,207,61,212,113,62,239,203,201,66,83,179,16,209,253,63,206,208,101,150,196,145,101,220,22,79,241,69,237,219,97,87,20,22,240,244,218,7,237,42,14,8,38,115,141,102,206,191,142,55,196,200,142,98,16,129,53,52,50,197,53,219,2,66,152,192,245,243,69,26,132,240,164,90,246,200,53,89,221,119,139,76,47,132,53,47,249,26,53,141,113,69,76,152,121,193,53,176,97,135,205,206,237,108,251,38,216,108,12,220,209,194,26,243,217,231,36,117,235,106,205,43,254,75,209,141,239,200,5,183,219,166,113,9,16,154,116,144,238,208,245,136,173,16,103,107,114,17,208,181,196,98,212,133,211,252]

for k1 in range(299):
    for k2 in range(300):
        decrypt(k1,k2,a)

运行结果
img
提示研究一下加密矩阵,尝试把d_3aa0中的元素输出来发现很像RGB值,尝试转化成图片。最后输出
img
flagctfshow{d244daeb-7182-4c98-bec6-0c99329ab71f}

exp

from base64 import *
from pwn import u32 

def decrypt(k1,k2, cipher):
    tk1,tk2 = k1,k2
    m = []
    for v8 in cipher[::-1]:
        k1 = (k1-v8)%299
        k2 = (k2-v8)%300
        m.append(d_3aa0[300*k1+k2] ^ v8)
    base64_text = bytes(m[::-1])
    if base64_text[:5]==b'ZmxhZ':
        flag=b64decode(base64_text).decode('utf-8')
        print(base64_text,tk1,tk2,'\n',flag)

flag='' 
data = open('re1.exe', 'rb').read()[0x28a0: 0x5a6e0]
d_3aa0 = [u32(data[i:i+4]) for i in range(0, len(data), 4)]
a = [90,171,198,235,229,43,246,92,198,203,233,228,6,128,215,68,201,4,220,214,169,245,208,199,112,170,119,251,244,58,237,4,70,231,200,45,186,137,247,225,243,13,145,139,190,146,194,242,253,56,239,5,41,225,105,51,247,79,170,231,88,64,224,138,222,220,229,88,43,117,236,189,228,205,150,65,26,205,232,141,116,149,185,89,212,251,16,215,205,17,238,22,245,77,220,198,224,248,223,209,205,167,223,210,165,247,190,3,5,246,243,228,181,33,42,207,174,138,244,118,192,22,219,60,80,229,144,219,133,211,221,229,190,58,151,240,183,207,221,60,77,217,220,74,105,220,221,165,85,174,43,183,188,190,252,255,130,137,189,201,239,181,150,143,214,203,26,211,103,222,105,87,214,179,83,185,104,206,229,172,221,117,163,57,106,200,46,165,193,135,243,166,168,209,144,52,210,12,58,10,103,5,211,55,172,76,88,250,136,245,167,139,241,26,92,97,139,241,137,27,53,211,251,191,240,173,14,231,241,242,255,122,144,97,234,36,175,155,253,35,156,229,19,166,191,140,195,218,130,35,200,178,245,41,162,243,214,222,87,83,195,144,55,159,208,241,193,233,204,228,196,105,84,58,220,226,1,47,248,138,177,124,236,53,210,79,250,106,27,244,251,203,210,103,213,218,183,4,40,28,12,175,52,224,203,89,176,174,175,233,43,20,103,152,201,4,148,76,241,103,135,139,136,246,80,184,255,194,149,239,206,207,246,166,20,63,202,199,177,214,60,99,74,211,219,94,247,193,40,212,197,175,30,244,41,24,113,27,249,213,225,55,188,193,165,220,174,252,105,154,74,126,174,255,110,169,103,44,246,255,98,251,211,87,171,62,67,250,69,149,18,77,159,137,168,231,187,97,174,115,243,44,128,151,90,246,83,11,138,67,184,22,53,228,230,252,76,112,20,136,131,90,233,248,67,207,61,212,113,62,239,203,201,66,83,179,16,209,253,63,206,208,101,150,196,145,101,220,22,79,241,69,237,219,97,87,20,22,240,244,218,7,237,42,14,8,38,115,141,102,206,191,142,55,196,200,142,98,16,129,53,52,50,197,53,219,2,66,152,192,245,243,69,26,132,240,164,90,246,200,53,89,221,119,139,76,47,132,53,47,249,26,53,141,113,69,76,152,121,193,53,176,97,135,205,206,237,108,251,38,216,108,12,220,209,194,26,243,217,231,36,117,235,106,205,43,254,75,209,141,239,200,5,183,219,166,113,9,16,154,116,144,238,208,245,136,173,16,103,107,114,17,208,181,196,98,212,133,211,252]

for k1 in range(299):
    for k2 in range(300):
        decrypt(k1,k2,a)

from PIL import Image

img = Image.new('RGB', (300, 300))
pixels = img.load()

for i in range(299):
    for j in range(300):
        pixels[i, j] = d_3aa0[i * 300 + j]
img = img.transpose(Image.FLIP_LEFT_RIGHT).rotate(90)
img.save('a.png')
img.show()

其他

np.fromstringreshape都是NumPy中常用的数组操作函数。

np.fromstring

np.fromstring函数用于从字符串或字节序列中创建NumPy数组。它将输入字符串或字节序列解析为一组数字,并返回一个NumPy数组。该函数的参数包括:要解析的字符串或字节序列、用于解析数字的数据类型和用于分隔数字的分隔符。
例如,下面的代码使用np.fromstring函数从一个字符串中创建一个NumPy数组:

import numpy as np

s = "1 2 3 4 5"
a = np.fromstring(s, dtype=int, sep=' ')
print(a)

输出:

[1 2 3 4 5]

reshape

reshape函数用于改变数组的形状。它将一个NumPy数组重新排列为一个具有不同行数和列数的新数组。该函数的参数包括:要重排的数组、一个包含新形状的元组或整数列表。

例如,下面的代码创建一个二维数组,然后使用reshape函数将其重新排列为一个具有不同行数和列数的新数组:

import numpy as np

a = np.array([[1, 2], [3, 4], [5, 6], [7, 8]])
b = a.reshape((2, 4))
print(b)

输出:

[[1 2 3 4]
 [5 6 7 8]]

reshape函数可以用于改变数组的维度和形状,但必须保证原始数组中元素的总数与新数组中元素的总数相同。因此,如果重排数组的形状会改变元素的数量,将会引发错误。

参考