我在用〔ssf2fcitx〕把搜狗的皮肤转成fcitx的。

编译时有警告:‘int AES_set_decrypt_key(const unsigned char*, int, AES_KEY*)’ is deprecated: Since OpenSSL 3.0

所以换成了EVP_CIPHER_CTX_new...

29个.ssf文件中有2个转换出错,gcc加-g,gdb --args ssf2skin -i SUNDAY黑色炫酷.ssf,r, bt:

#0 0x00007ffff7ae00f0 in ?? () from /lib/x86_64-linux-gnu/libcrypto.so.3
#1 0x00007ffff7cf7159 in ?? () from /lib/x86_64-linux-gnu/libcrypto.so.3
#2 0x00007ffff7d3c62d in ?? () from /lib/x86_64-linux-gnu/libcrypto.so.3
#3 0x00007ffff7c0813f in EVP_EncryptUpdate () from /lib/x86_64-linux-gnu/libcrypto.so.3

回到原始版,gcc不加-g,gdb...

Program received signal SIGSEGV, Segmentation fault.
#0  0x00007ffff7ad2543 in AES_cbc_encrypt () from /lib/x86_64-linux-gnu/libcrypto.so.3

AES_cbc_encrypt(ssfbin + 8, out, static_cast<size_t>(size - 8), &key, iv, AES_DECRYPT);

王小川超过王小云啦?搜狗会造让openssl崩溃的数据啦?It's 【SUNDAY】黑色炫酷. What a black day.

ssf2fcitx用了Qt5. 把代码扒 (pa) 出来写了个极小程序,成功解密。

openssl 避坑与吐槽

① 解密后iv被修改,再次用它解密结果不对。要memcpy(iv, const_iv, sizeof(iv))重新来过。

② openssl/evp.h:const EVP_CIPHER *EVP_aes_256_cbc(void);

  这个函数很可能返回&_aes_256_cbc (全局变量)之类,不用free.

  EVP_DecryptInit_ex(ctx, MyClass());

  // 到这里类为MyClass的匿名临时变量就被析构了,还敢用指向它内部数据成员的指针?详情请看最后。

③ EVP_DecryptFinal_ex与padding,详情请看最后。

④ 要不要调用“析构函数”EVP_CIPHER_CTX_cleanup?本例不调用没事。

⑤ 函数参数设计成void*和const void*,用户就不必reinterpret_cast<const unsigned char*>buf了。应把困难留给自己。

⑥ 有人说加密和解密是一样的,本例把Decrypt换成Encrypt,结果不一样。


密文len = 2939312, % 16 = 0

Decrypt_Update: len = 2939296, % 16 = 0

DecryptFinal_ex: len = 10

2939312  - (2939296 + 10)  = 6

  • 调用了Final后解密结果和AES_cbc_encrypt不一样?给Final的应为out + total而不是out.
  • 每次Update AES_BLOCK_SIZE个字节,不crash,但结果不对,估计是也是上面的原因。
  • 一步到位fread(AES_BLOCK_SIZE * 1024); 解密; fwrite()好了,难道手贱到fseek(0, SEEK_SET)或rewind()后去改?
    • set做名词有14个意思,没有"开始"。做动词的第5个意思是:cause (sb/sth) to begin to do sth

image

嚯,还有_ex2哪?


struct MyClass {
  ~MyClass () { puts("~MyClass"); }
  void print () const { puts("xxx"); }
};

void fn (const MyClass& m) { m.print(); }

int main () {
  fn(MyClass());
  puts("---");
}

xxx
~MyClass
---

附完整代码。

#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <zlib.h>

int AES256_decrypt(void* out_,
  const void* in_, int n,
  const void* key, void* iv)
{
  const int BS = AES_BLOCK_SIZE * 1024;
  //const int BS = n;
  EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  auto cipher = EVP_aes_256_cbc();
  if (!EVP_DecryptInit_ex(ctx, cipher, NULL, (const uint8_t*)key, (uint8_t*)iv)) return 0;
  uint8_t* out = (uint8_t*)out_;
  const uint8_t* in = (const uint8_t*)in_;
  int total = 0, len;
  while (n > 0) {
    int m = n > BS ? BS : n;
    EVP_DecryptUpdate(ctx, out, &len, in, m);
    if (len != m) printf("update: %d %d %d\n", total, len, m);
    out += len; total += len;
    in += m; n -= m;
  }
  EVP_DecryptFinal_ex(ctx, out + total, &len);
  printf("final: %d\n", len);
  EVP_CIPHER_CTX_cleanup(ctx); EVP_CIPHER_CTX_free(ctx);
  return total + len;
}

const uint8_t key[] = {
  0x52, 0x36, 0x46, 0x1A, 0xD3, 0x85, 0x03, 0x66,
  0x90, 0x45, 0x16, 0x28, 0x79, 0x03, 0x36, 0x23,
  0xDD, 0xBE, 0x6F, 0x03, 0xFF, 0x04, 0xE3, 0xCA,
  0xD5, 0x7F, 0xFC, 0xA3, 0x50, 0xE4, 0x9E, 0xD9
};
uint8_t iv[] = {
  0xE0, 0x7A, 0xAD, 0x35, 0xE0, 0x90, 0xAA, 0x03,
  0x8A, 0x51, 0xFD, 0x05, 0xDF, 0x8C, 0x5D, 0x0F
};
char  x[8 * 1024 * 1024];
int   y[2 * 1024 * 1024];

struct QDataStream {
  uint8_t* p; int pos;
  QDataStream (void* p_) { p = (uint8_t*)p_; pos = 0; }
  QDataStream& operator >> (int &n) { n = *((int*)(p + pos)); pos += 4; return *this; }
  void seek (int pos_) { pos = pos_; }
  void utf162mbs (char* mbs, int n) {
    wchar_t wcs[256]; wcs[n] = 0;
    for (int i = 0; i < n; i++) { wcs[i] = *((uint16_t*)(p + pos)); pos += 2; }
    wcstombs(mbs, wcs, n + 1); // 其实全是英文名
  }
  operator uint8_t* () { return p + pos; }
};

int main (int argc, char* argv[]) {
  setlocale(LC_ALL, "");

//  FILE* fp = fopen("SUNDAY黑色炫酷.ssf", "rb");
  FILE* fp = fopen("一二.ssf", "rb");
  if (!fp) return 1;
  fseek(fp, 0, SEEK_END); int size = ftell(fp);
  rewind(fp); fread(x, 1, size, fp); fclose(fp);

  int n = AES256_decrypt(y, x + 8, size - 8, key, iv);
  printf("%d decryped\n", n);

  size = *y; *y = __builtin_bswap32(size);
  uLongf _ = size;
  uncompress((uint8_t*)x, &_, (const uint8_t*)y + 4, n - 4);
  printf("%d uncompressed\n", size);

  QDataStream ds(x);
  ds >> size >> n; n /= 4; printf("%d files\n", n);
  int ofs[256];
  for (int i = 0; i < n; i++) ds >> ofs[i];
  for (int i = 0; i < n; i++) {
    ds.seek(ofs[i]);
    int  len; ds >> len;
    char name[256], path[256];
    ds.utf162mbs(name, len / 2);
    ds >> len; printf(" %s %d\n", name, len);
    sprintf(path, "out/%s", name);
    if (FILE* fp = fopen(path, "w")) fwrite(ds, 1, len, fp), fclose(fp);
  }
}
View Code

① 黑色炫酷,BS = AES_BLOCK_SIZE * 1024; update: 0 16368 16384 final: 10

② 黑色炫酷,BS = n; update: 0 2939296 2939312 final: 10

③ 一二,BS = AES_BLOCK_SIZE * 1024; update: 0 16368 16384 final: 2

④ 一二,BS = n; update: 0 496144 496160 final: 2

以上4种情况,程序均无错退出。看图软件说①②的skin2_2.png CRC error,别的文件和③④里的所有png,OK.


出错的Qt程序,刚进main,调openssl解密char*,还没来得及往QByteArray里写呢,所以我怀疑是.so打起了家族战争。

解密后有CRC error,我怀疑加密程序不对,比如没有调Final.

posted on 2025-11-03 23:59  华容道专家  阅读(5)  评论(0)    收藏  举报