官方Writeup,好详细的Writeup,点赞😍
题目描述
比赛太累了?来弹奏一首钢琴曲来放松一下!!
注:附件运行环境需要 Android 11 以上,架构仅支持 arm64
Too tired from the competition? Come play a piano piece to relax a bit.
Note: The attachment environment needs to be Android 11+, and only supports arm64
幸好有真机了,不然这类题目碰都碰不了。
题目是一个模拟钢琴的app
当时是根本找不到关键逻辑,只能得到Fake Flag和Frida Hook到的假Flag,甚至我都找不到检测Frida的代码是在哪里的位置。
Java层代码比较简单,就是绘制琴键、加载琴音资源、获取当前触摸位置,判断属于哪个琴键每次按下都会将琴键的序号传给native的check函数进行判断,如果返回true就执行getflag的逻辑,然后打印出flag。
Writeup这里提到了getkeysoundnames()这个Native函数,不过当时我并没有怎么关注这个函数
当时就是Frida Hook check()和getflag()函数,会被检测。然后这里就去分析调用的So文件了。这里只能看到程序加载了一个libD3piano.so。
JNI_OnLoad可以看到注册的check函数
发现这里使用了GMP大数库,琴键总共12个,这里的逻辑就是把琴键的index拼接成一个12进制字符串然后作为大数值,进行加密,然后在转换成16进制字符串,再与目标字符串进行比较,所以这里的sub_29B44就是加密函数了。
有点像RSA加密算法,再进sub_29980函数看
这里就确定是RSA了,for循环里面,生成随机数和一个未知变量qword_63AF8相乘累加到v6,再将v6作为GMP随机数种子,生成RSA加密的p和q。交叉引用这个变量,发现在getkeysoundnames()函数里面对其进行了赋值,这里是12轮for循环,猜测是琴键的索引值,下面的红框应该是根据索引取值
但索引是按顺序的还是乱序,我们并不清楚,这里在Linux复现一下
#include <stdio.h>
#include <stdlib.h>
#include <gmp.h>
void sub_29940(mpz_t out_prime, gmp_randstate_t rand_state, int bits) {
mpz_urandomb(out_prime, rand_state, bits);
mpz_nextprime(out_prime, out_prime);
}
int main() {
srand(0x221221); // 固定种子
// 模拟 qword_63AF8 表(12 项)
unsigned int v6 = 0;
for (int i = 0; i <= 11; ++i) {
v6 += i* rand();
}
// 初始化 GMP 随机状态
gmp_randstate_t state;
gmp_randinit_mt(state);
gmp_randseed_ui(state, v6);
// 生成素数 p 和 q
mpz_t p, q;
mpz_init(p);
mpz_init(q);
sub_29940(p, state, 512);
sub_29940(q, state, 512);
// 打印为十六进制
printf("Prime p (hex):\n");
mpz_out_str(stdout, 16, p);
printf("\n\nPrime q (hex):\n");
mpz_out_str(stdout, 16, q);
printf("\n");
// 清理
mpz_clear(p);
mpz_clear(q);
gmp_randclear(state);
return 0;
}
/*
Prime p (hex):
cd88775691357147eea5dc584718edab9ca314cdd52a8c1cf847dbbb8371798f15e9bdca2bfaa4595d47eecae21bea38691a26e1c707867b5ea2f6f2f03bf4d
Prime q (hex):
565c0138487b57e4b76d0924163f67facb17a77f83e354cc3c8432879dab4611c2442cdd73f71c9e6cb4e56a7c45a403148e6d558f986ec6505882ae095c34d3
*/
这里可以解密出字符串,那就是没问题的,这里动态调试不知道能不能得到p、q,没有尝试
from Crypto.Util.number import long_to_bytes
p = 0xcd88775691357147eea5dc584718edab9ca314cdd52a8c1cf847dbbb8371798f15e9bdca2bfaa4595d47eecae21bea38691a26e1c707867b5ea2f6f2f03bf4c+1
q = 0x565c0138487b57e4b76d0924163f67facb17a77f83e354cc3c8432879dab4611c2442cdd73f71c9e6cb4e56a7c45a403148e6d558f986ec6505882ae095c34d2+1
d = 0x2e4f4254c529f4ef40d28a6595e60bb28b1d11ab13328000fb63cf77836a423259af8a8881d73e1868cd66bbd78920f38033f9f1e0e406b82f0aee50fdbe2567e0ac329e80e9abbb7075f4711157844e9fcb666200b1c4ef05b3cc66278221c9e6bd7250caf2be2a3793cf1f2dd43918d6ead0ee851eccf43e48750b6fab2f9
e = 65537
c=0xc901acacbb426c9c447acda82513965ccc3faf6c9dc58d24ed34b62c7fb1548f9ad06b9355c7d20704cfdfdfc89a3f893801e31719564683fdc7de26d807ed27f898edb3efd51b6e8e2a192d6a0929554342adfed541cd8399da0fbacfeaa5b608b887fd74f4f0e31f9bb5816c54163b8e46d27553798233bef6eaf848c64e
n=p*q
mm=pow(c,e,n)
print("Output:", long_to_bytes(mm).decode())
#Output: This_is_a_fake_flag
然后Writeup说,这里的sub_291B4函数被Hook了,根据前面的分析,这个函数应该是返回琴键对应序号的。
然后libD3piano.so链接了这些So文件,前面RSA用了libgmp.so,没有使用另外一个,所以这里就去libMediaPlayer.so文件看。
看libMediaPlayer.so的init_array,这里不知道为啥,但是也是学到了,So文件看来也要关注init_array,毕竟是基于Linux的,易错题!
找到了一个函数
__int64 sub_8938CC()
{
__int64 result; // x0
int j; // [xsp+14h] [xbp-4Ch]
sem_t *i; // [xsp+28h] [xbp-38h]
_QWORD v3[5]; // [xsp+38h] [xbp-28h] BYREF
v3[4] = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
for ( i = &unk_BBBF04; i != &unk_BBBF34; i = (i + 16) )
result = sem_init(i, 0, 0);
for ( j = 0; j <= 3; ++j )
{
pthread_create(&v3[j], 0LL, *(&off_BBAD20 + j), 0LL);
result = pthread_detach(v3[j]);
}
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return result;
}
初始化了三个信号量,创建了四个线程,每个线程都执行不同的函数,off_BBAD20是个函数表,就是要执行的函数。我们先看第一个函数
这里的代码喂给ai是可以识别出代码的核心目的是检测是否有调试器或某些敏感线程(如 JDWP、gdbus、gmain 等)存在,如果检测到即立刻退出程序,从而达到检测调试与Frida的目的。
Writeup还说剩下的三个函数涉及到了信号量。执行顺序为func3→func2→func4,这里我不知道怎么看出来,对Linux编程这块不是很熟悉
第3个函数
这里把sub_8933B8函数喂给AI可以知道大概是获取一个So文件的基地址,然后0x291B4就是函数偏移,可以发现这个偏移就是上面返回琴键序号sub_291B4的地址,这里的目的就是得到sub_291B4函数地址,还调用两个虚表函数,就是通过指针调用的。
然后看第2个函数
这里得到了check函数的地址,然后sub_8A2748函数里面有Frida的相关字符串
Writeup说这里Hook的逻辑是通过Frida写的。gumpp是Frida的C++接口,可以在github上找到源代码
学到了,用So文件中的C++的Frida来Hook自身。
两个虚表函数一个就是attach 一个是detach
这里既然时Hook So文件的函数,应该就是类似用JavaScript写的Interceptor.attach,创建拦截器,实现 on_enter、on_leave 函数
官方的示例文件创建拦截器部分的示例
class BacktraceTestListener : public Gum::InvocationListener
{ // 创建拦截器,继承自 InvocationListener,实现 on_enter、on_leave。
public:
BacktraceTestListener() // 构造函数,势例化 backtracer
: backtracer(Gum::Backtracer_make_accurate())
{
}
// 实现 on_enter 函数,在 hook 函数被调用时执行
virtual void on_enter(Gum::InvocationContext *context)
{
g_string_append_c(static_cast<GString *>(
context-
>get_listener_function_data_ptr()),
'>');
Gum::ReturnAddressArray return_addresses;
//生成返回地址数组
backtracer->generate(context->get_cpu_context(),
return_addresses);
g_assert_cmpuint(return_addresses.len, >=, 1);
#if !defined(HAVE_DARWIN) && !defined(HAVE_ANDROID)
Gum::ReturnAddress first_address = return_addresses.items[0];
Gum::ReturnAddressDetails rad;
g_assert_true(Gum::ReturnAddressDetails_from_address(first_address
, rad));
g_assert_true(g_str_has_suffix(rad.function_name,
"_can_get_stack_trace_from_invocation_context"));
gchar *file_basename = g_path_get_basename(rad.file_name);
g_assert_cmpstr(file_basename, ==, "backtracer.cxx");
g_free(file_basename);
#endif
}
// 实现 on_leave 函数,在 hook 函数返回时执行
virtual void on_leave(Gum::InvocationContext *context)
{
g_string_append_c(static_cast<GString *>(
context-
>get_listener_function_data_ptr()),
'<');
}
Gum::RefPtr<Gum::Backtracer> backtracer; // backtracer 对象
};
这里就找Listener的实现,字符串窗口搜索
也是虚函数表的形式,这里第三个函数是on Enter函数、第四个函数是on Leave函数,这里MyListener1的on Enter函数为空,on Leave函数如下:
这里应该是Hook的返回琴键序号sub_291B4函数,因为是12轮循环,并且之前RSA解密那里也验证过刚好也是按顺序的序号,也就是init_array中第3个函数的Hook。
init_array中第2个函数对check函数进行了Hook,这里我们在MyListener2中找到了大量的加密逻辑,就可以猜测是check函数的真实逻辑。
这是官方Writeup对onenter的注解,这分析是真有难度
__int64 __fastcall sub_896B98(__int64 a1, __int64 a2)
{
_BYTE *v2; // x0
unsigned __int8 *v3; // x0
__int64 v4; // x0
size_t v5; // x0
__int64 result; // x0
char v7; // [xsp+14h] [xbp-ACh]
int j; // [xsp+28h] [xbp-98h]
int i; // [xsp+2Ch] [xbp-94h]
__int64 v12; // [xsp+30h] [xbp-90h]
int k; // [xsp+4Ch] [xbp-74h] BYREF
__int128 v14[6]; // [xsp+50h] [xbp-70h] BYREF
int v15; // [xsp+B0h] [xbp-10h]
__int64 v16; // [xsp+B8h] [xbp-8h]
v16 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
//调用 get_nth_argument_ptr(2)获取第三个参数其实就是 check(i)的 i,也就是传进去的琴键序号
v12 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a2 + 24LL))(a2, 2LL);
v15 = 0;
memset(v14, 0, sizeof(v14));
//如果长度小于 91
if ( (unsigned __int64)sub_897580(a1 + 8) < 0x5B )
{
// CDEFGABdegab 根据序号 push 音调
result = sub_897630(a1 + 8, &aCdefgabdegab[v12]);
}
else //长度大于91时
{
for ( i = 0; i <= 90; ++i )
{ //先异或
v7 = byte_BBACC2[i];
v2 = (_BYTE *)sub_8975A0(a1 + 8, i);
*v2 ^= v7;
}
//然后根据异或后的字符串,获取到对应的音调的下标保存到&unk_BBBF40
for ( j = 0; j <= 90; ++j )
{
for ( k = 0; k <= 11; ++k )
{
v3 = (unsigned __int8 *)sub_8975A0(a1 + 8, j);
if ( *v3 == (unsigned __int8)aCdefgabdegab[k] )
sub_8975C4(&unk_BBBF40, &k);
}
}
//调用虚表函数1
(*(void (__fastcall **)(__int64))(*(_QWORD *)a1 + 32LL))(a1);
v4 = sub_897580(a1 + 8);
//调用虚表函数 2
(*(void (__fastcall **)(__int64, __int128 *, __int64, __int64, char *, __int64))(*(_QWORD *)a1 + 40LL))(
a1,
v14,
v4,
a1 + 32,
aCdefgabdegab,
2232865LL);
v5 = sub_897580(a1 + 8);
//比较
result = memcmp(&unk_BBAD40, v14, v5);
if ( !(_DWORD)result )
byte_BBBF00 = 1;
}
_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
return result;
}
第2个函数Hook 函数之后,init_array中的第四个函数开始执行,其Hook的是偏移地址为0x28E80的函数,这里在libMediaPlayer.so里面没找到相应函数,那就是Hook的libD3piano.so的函数,发现Hook的是getflag函数里面的一个函数
把sub_28E80代码喂给AI就是一个转为字符串的函数,再看MyListener3的on Enter函数
就是把根据异或加密后的音调再转换成12进制数,丢给sub_28380函数。
现在就需要分析两个虚表函数加密,然后写出解密获取到输入的音调数组转换成12 进制再转换成字符串输出就可以。
enc2函数是一个salsa20加密,没学过,不过搜索加密常量可以找到
这里的密钥是类成员
函数解密得到密钥 welCoME_70_D3c7F_2025-r3VERSE!!!,然后解密
#include <cstdio>
#include <cstring>
#include <format>
#include <iostream>
#include <fstream>
#include <cstdint>
#include <vector>
#define ROTL(a, b) ((((uint32_t)(a)) << ((uint32_t)(b))) |(((uint32_t)(a)) >> (32 - ((uint32_t)(b)))))
#define QUARTERROUND(a, b, c, d) \
a += b; \
d ^= a; \
d = ROTL(d, 16); \
c += d; \
b ^= c; \
b = ROTL(b, 12); \
a += b; \
d ^= a; \
d = ROTL(d, 8); \
c += d; \
b ^= c; \
b = ROTL(b, 7);
unsigned char flag_enc[74] = {
0x2E, 0xD2, 0xDF, 0x53, 0x41, 0xE6, 0x51, 0xA2, 0xD0, 0x8E,
0x43, 0x59, 0x6F, 0xC4, 0x15, 0xAD,
0x97, 0xC2, 0x98, 0xBD, 0x11, 0x05, 0xFE, 0xFF, 0x96, 0x4C,
0xE8, 0x06, 0x50, 0x0E, 0x1D, 0xCA,
0x0E, 0xB2, 0x18, 0xCA, 0x06, 0x54, 0x2E, 0xFA, 0xCD, 0x19,
0xD2, 0x9E, 0xDB, 0x9E, 0x33, 0xCC,
0x5D, 0xAF, 0xED, 0x69, 0x4A, 0xEF, 0x17, 0xB8, 0xD8, 0x40,
0x14, 0x48, 0xCD, 0x37, 0xFC, 0xD0,
0x14, 0x5C, 0x3C, 0x31, 0xC9, 0x15, 0xE6, 0xCF, 0x77, 0x28 };
void salsa20_core(uint32_t out[16], const uint32_t in[16])
{
uint32_t x[16];
int i;
for (i = 0; i < 16; ++i)
{
x[i] = in[i];
}
for (i = 0; i < 10; ++i)
{
// 列轮
QUARTERROUND(x[0], x[4], x[8], x[12])
QUARTERROUND(x[5], x[9], x[13], x[1])
QUARTERROUND(x[10], x[14], x[2], x[6])
QUARTERROUND(x[15], x[3], x[7], x[11])
// 行轮
QUARTERROUND(x[0], x[1], x[2], x[3])
QUARTERROUND(x[5], x[6], x[7], x[4])
QUARTERROUND(x[10], x[11], x[8], x[9])
QUARTERROUND(x[15], x[12], x[13], x[14])
}
for (i = 0; i < 16; ++i)
{
out[i] = x[i] + in[i];
}
}
void salsa20_encrypt(uint8_t* ciphertext, size_t length, const
uint8_t* key, const uint8_t* nonce, uint64_t counter)
{
uint32_t state[16];
uint32_t block[16];
size_t i, j;
uint8_t* keystream = (uint8_t*)block;
uint8_t* plaintext = (uint8_t*)malloc(74);
for (int n = 0; n < 74; n++)
{
plaintext[n] = flag_enc[n];
}
// 初始化状态
state[0] = 0x61707865;
state[1] = *(uint32_t*)&key[0];
state[2] = *(uint32_t*)&key[4];
state[3] = *(uint32_t*)&key[8];
state[4] = *(uint32_t*)&key[12];
state[5] = 0x3320646e;
state[6] = *(uint32_t*)&nonce[0];
state[7] = *(uint32_t*)&nonce[4];
state[8] = (uint32_t)(counter & 0xFFFFFFFF);
state[9] = (uint32_t)(counter >> 32);
state[10] = 0x79622d32;
state[11] = *(uint32_t*)&key[16];
state[12] = *(uint32_t*)&key[20];
state[13] = *(uint32_t*)&key[24];
state[14] = *(uint32_t*)&key[28];
state[15] = 0x6b206574;
for (i = 0; i < length; i += 64)
{
salsa20_core(block, state);
for (j = 0; j < 64 && i + j < length; ++j)
{
ciphertext[i + j] = plaintext[i + j] ^ keystream[j];
}
// 更新计数器
if (++state[8] == 0)
{
++state[9];
}
}
}
int main()
{
uint8_t key[] = "welCoME_70_D3c7F_2025-r3VERSE!!!";
uint8_t nonce[] = "CDEFGABdegab";
uint64_t counter = (uint64_t)0x221221;
uint8_t ciphertext[74];
salsa20_encrypt(ciphertext, sizeof(flag_enc), key, nonce,
counter);
for (int i = sizeof(flag_enc) - sizeof(ciphertext); i <
sizeof(flag_enc); i++)
{
printf("%c", ciphertext[i]);
}
return 0;
}
这里解密可以解密出可见字符,但是不是一个完整的字符串,还要分析enc1,这里enc1函数的输入是91个字节,输出是74个字节,Writeup说这里就是一个压缩算法。出题人由于代码编写失误写错了压缩算法的一个变量类型意外的定义成了 uint8,导致 LZW 压缩算法不是一个完全正确的 LZW,然后意外发现非常有反 AI 的效果而且可以写出解压缩代码,所以打算保留。
这题两个对数据处理的算法都不认识😢
#include <cstdio>
#include <cstring>
#include <format>
#include <iostream>
#include <fstream>
#include <cstdint>
#include <vector>
#include<unordered_map>
#define ROTL(a, b) ((((uint32_t)(a)) << ((uint32_t)(b))) |(((uint32_t)(a)) >> (32 - ((uint32_t)(b)))))
#define QUARTERROUND(a, b, c, d) \
a += b; \
d ^= a; \
d = ROTL(d, 16); \
c += d; \
b ^= c; \
b = ROTL(b, 12); \
a += b; \
d ^= a; \
d = ROTL(d, 8); \
c += d; \
b ^= c; \
b = ROTL(b, 7);
unsigned char flag_enc[74] = {
0x2E, 0xD2, 0xDF, 0x53, 0x41, 0xE6, 0x51, 0xA2, 0xD0, 0x8E,
0x43, 0x59, 0x6F, 0xC4, 0x15, 0xAD,
0x97, 0xC2, 0x98, 0xBD, 0x11, 0x05, 0xFE, 0xFF, 0x96, 0x4C,
0xE8, 0x06, 0x50, 0x0E, 0x1D, 0xCA,
0x0E, 0xB2, 0x18, 0xCA, 0x06, 0x54, 0x2E, 0xFA, 0xCD, 0x19,
0xD2, 0x9E, 0xDB, 0x9E, 0x33, 0xCC,
0x5D, 0xAF, 0xED, 0x69, 0x4A, 0xEF, 0x17, 0xB8, 0xD8, 0x40,
0x14, 0x48, 0xCD, 0x37, 0xFC, 0xD0,
0x14, 0x5C, 0x3C, 0x31, 0xC9, 0x15, 0xE6, 0xCF, 0x77, 0x28 };
void salsa20_core(uint32_t out[16], const uint32_t in[16])
{
uint32_t x[16];
int i;
for (i = 0; i < 16; ++i)
{
x[i] = in[i];
}
for (i = 0; i < 10; ++i)
{
// 列轮
QUARTERROUND(x[0], x[4], x[8], x[12])
QUARTERROUND(x[5], x[9], x[13], x[1])
QUARTERROUND(x[10], x[14], x[2], x[6])
QUARTERROUND(x[15], x[3], x[7], x[11])
// 行轮
QUARTERROUND(x[0], x[1], x[2], x[3])
QUARTERROUND(x[5], x[6], x[7], x[4])
QUARTERROUND(x[10], x[11], x[8], x[9])
QUARTERROUND(x[15], x[12], x[13], x[14])
}
for (i = 0; i < 16; ++i)
{
out[i] = x[i] + in[i];
}
}
void salsa20_encrypt(uint8_t* ciphertext, size_t length, const
uint8_t* key, const uint8_t* nonce, uint64_t counter)
{
uint32_t state[16];
uint32_t block[16];
size_t i, j;
uint8_t* keystream = (uint8_t*)block;
uint8_t* plaintext = (uint8_t*)malloc(74);
for (int n = 0; n < 74; n++)
{
plaintext[n] = flag_enc[n];
}
// 初始化状态
state[0] = 0x61707865;
state[1] = *(uint32_t*)&key[0];
state[2] = *(uint32_t*)&key[4];
state[3] = *(uint32_t*)&key[8];
state[4] = *(uint32_t*)&key[12];
state[5] = 0x3320646e;
state[6] = *(uint32_t*)&nonce[0];
state[7] = *(uint32_t*)&nonce[4];
state[8] = (uint32_t)(counter & 0xFFFFFFFF);
state[9] = (uint32_t)(counter >> 32);
state[10] = 0x79622d32;
state[11] = *(uint32_t*)&key[16];
state[12] = *(uint32_t*)&key[20];
state[13] = *(uint32_t*)&key[24];
state[14] = *(uint32_t*)&key[28];
state[15] = 0x6b206574;
for (i = 0; i < length; i += 64)
{
salsa20_core(block, state);
for (j = 0; j < 64 && i + j < length; ++j)
{
ciphertext[i + j] = plaintext[i + j] ^ keystream[j];
}
// 更新计数器
if (++state[8] == 0)
{
++state[9];
}
}
}
void LZW_decode(std::vector<uint8_t>& compressed)
{
std::unordered_map<int, std::vector<uint8_t>> dictionary;
for (int i = 0; i < 256; ++i)
{
dictionary[i] = { static_cast<uint8_t>(i) };
}
std::vector<uint8_t> codes;
for (uint8_t byte : compressed)
{
codes.push_back(static_cast<int>(byte));
}
std::vector<uint8_t> result;
uint8_t next_code = 0;
if (codes.empty())
return;
uint8_t prev_code = codes[0];
result.insert(result.end(), dictionary[prev_code].begin(),
dictionary[prev_code].end());
for (size_t i = 1; i < codes.size(); ++i)
{
uint8_t curr_code = codes[i];
std::vector<uint8_t> entry;
if (curr_code == next_code)
{
entry = dictionary[prev_code];
entry.push_back(dictionary[prev_code][0]);
}
else if (dictionary.count(curr_code))
{
entry = dictionary[curr_code];
}
else
{
std::cerr << "Error: Invalid code " << curr_code <<
std::endl;
return;
}
result.insert(result.end(), entry.begin(), entry.end());
std::vector<uint8_t> new_entry = dictionary[prev_code];
new_entry.push_back(entry[0]);
dictionary[next_code++] = new_entry;
prev_code = curr_code;
}
std::string decoded_string(result.begin(), result.end());
std::cout << decoded_string << std::endl;
}
int main()
{
uint8_t key[] = "welCoME_70_D3c7F_2025-r3VERSE!!!";
uint8_t nonce[] = "CDEFGABdegab";
uint64_t counter = (uint64_t)0x221221;
uint8_t ciphertext[74];
salsa20_encrypt(ciphertext, sizeof(flag_enc), key, nonce,counter);
// for (int i = sizeof(flag_enc) - sizeof(ciphertext); i <
//sizeof(flag_enc); i++)
// {
// printf("0x%02x,", ciphertext[i]);
// }
std::vector<uint8_t> inputkeysname;
/* inputkeysname = {0x62, 0x45, 0x62, 0x41, 0x41, 0x43, 0x45,
0x42, 0x43, 0x47, 0x09, 0x42, 0x64, 0x42, 0x45, 0x05, 0x43, 0x62,
0x64, 0x61, 0x0e, 0x08, 0x47, 0x42, 0x41, 0x61, 0x18, 0x65, 0x0c,
0x45, 0x65, 0x44, 0x64, 0x47, 0x41, 0x64, 0x44, 0x61, 0x42, 0x67,
0x1b, 0x1b, 0x46, 0x46, 0x42, 0x46, 0x65, 0x45, 0x61, 0x46, 0x47,
0x64, 0x46, 0x01, 0x41, 0x34, 0x36, 0x67, 0x64, 0x67, 0x44, 0x42,
0x3c, 0x27, 0x67, 0x67, 0x02, 0x65, 0x46, 0x61, 0x67, 0x13, 0x1b,
0x02
};
*/
for (int i = 0; i < sizeof(ciphertext); i++)
{
inputkeysname.push_back(ciphertext[i]);
}
LZW_decode(inputkeysname);
return 0;
}
//bEbAACEBCGGGBdBECECbdaECCGGBAaAaedBEeDdGAdDaBgededFFBFeEaFGdFEbAFEAFgdgDBDBgeggbAeFagaEedbA
Python解密代码
from Crypto.Util.number import long_to_bytes
enc="bEbAACEBCGGGBdBECECbdaECCGGBAaAaedBEeDdGAdDaBgededFFBFeEaFGdFEbAFEAFgdgDBDBgeggbAeFagaEedbA"
keys="CDEFGABdegab"
twelve="0123456789AB"
tmp=""
for i in enc:
tmp+=twelve[keys.index(i)]
tmp=int(tmp,12)
print(long_to_bytes(tmp).decode())
#Fly1ng_Pi@n0_Key$_play_4_6e@utiful~melody