qiandao
python写的exe
python pyinstxtractor.py E:\firefox\qiandao1.exe
pycdc E:\retools\pyinstxtractor-2026.04\qiandao1.exe_extracted\qiandao.pyc -o aaa.py
py
# Source Generated with Decompyle++
# File: qiandao.pyc (Python 3.11)
a = '53 44 50 43 53 45 43 7b 72 65 76 65 72 73 65 5f 71 69 61 6e 5f 64 61 6f 7d 0a'
correct_flag = bytes.fromhex(a).decode('utf-8').strip()
user_input = input('please input your flag: ')
if user_input == correct_flag:
print('great')
return None
print('wrong')
很简单
py
a = '53 44 50 43 53 45 43 7b 72 65 76 65 72 73 65 5f 71 69 61 6e 5f 64 61 6f 7d 0a'
correct_flag = bytes.fromhex(a).decode('utf-8').strip()
print(correct_flag)
SDPCSEC{reverse_qian_dao}
Roses
Sss电脑桌面上的 1.txt 文件被一个 恶意程序(flower22.exe)加密了,加密后的内容在encode.txt中,经初步分析,这个恶意程序存在某种代码混淆,你能帮Sss恢复他的1.txt的内容吗,这里面会有你想要的东西
添加了一种花指令(3处),注意留意ida中变红的地方
前置知识
花指令
扰反汇编器和反编译器的静态分析的无用或混淆指令
特征:
-
信号 1:call 到诡异的地址
call near ptr 14177FF06h正常调用跳去一个函数地址(比如 14000xxxx),但这个跳到了 14177xxxx------程序一共才 0x12000 字节(140000000 ~ 140012000),这地址在程序范围外,明显是错的。
这就是最大的标志:E8 被误读成 CALL,算出来的地址巨大无比。
-
信号 2:
jz / jnz显示奇怪的写法jz short near ptr loc_1400015FC+2正常的跳转写法是
jz loc_xxxxxx(一个具体的标签)。+2 这种写法暴露了 IDA 的困惑------它知道 jz 跳到了某个地址的"中间",但它没法给那里命名,只能凑合写。 -
信号 3:反汇编中间有巨大的空白
1400015FC: call near ptr 14177FF06h ← 这里 CALL 14000179A: add rsp, 60h ← 下一个指令隔了 0x19E 字节间隔 400 多字节,中间全是灰色 db → 说明 IDA 放弃了那片区域的翻译。正常的函数体不会断。
-
信号 4:Hex View 里 E8 后面跟的是正常指令字节
打开 Hex View 看 call 的位置。比如在 0x1400015FC,E8 后面跟的是 05 E9 77 01 00 00 C7 45 EC。
- 如果后面真的是一个 CALL 的 4 字节地址 → 应该看起来像一段有规律的数值
- 如果后面是 E9(JMP 指令)、48(REX 前缀)、C7(MOV 指令)→ 这就是正常代码的开头,而不是地址
-
信号 5:F5 反编译显示 JUMPOUT
if (malloc(...)) JUMPOUT(0x1400015FELL);这行不是 C 语法,是 IDA 找不到跳转目标的控制流的投降声明
对于本题的分析
这个二进制里有三处相同的陷阱,都在 0xE8 这个数字上。
什么是 0xE8?
0xE8 是 CPU 的 "CALL" 指令的第一个字节。CPU 看到 0xE8,就知道要执行一个"打电话"操作------先记住下一行地址,然后跳到另一个地方去执行。
关键: 0xE8 就是一个5字节长的指令:
E8 XX XX XX XX
│ └───┬────┘
│ └─ 电话要打给谁的"地址"(4字节)
└─ "打电话" 指令
IDA 的默认翻译方式是线性扫描------从前往后逐字节翻译,碰到 E8 就按5字节解读。
陷阱的设计者做了什么?
他在正常的代码中间插了 6 个精心挑选的字节: 31 C0 74 02 E8 05
我们一步步看这 6 个字节干了什么:
第 1 步:31 C0 → xor eax, eax
- 作用 :异或相同为0,把
eax寄存器清零 - 附带效果:零标志位(ZF)被设置为1
第 2 步:74 02 → jz +2
- jz = Jump if Zero = "如果ZF=1就跳"
- +2 跳 2两字节指令
现在 CPU 的跳转目标是:这条指令的结尾地址 + 2。我们来算一下从哪里跳到哪里。
用本题里实际的位置来算:
31 C0在地址0x1400015F8~0x1400015F9(2字节)74 02在地址0x1400015FA~0x1400015FB(2字节)- 这个
jz指令结束在0x1400015FB,再加上偏移量02 - 跳转目标
0x1400015FC、0x1400015FD,跳到0x1400015FE
第 3 步:E8 05 → 垃圾字节
- 这两字节在
0x1400015FC~0x1400015FD - CPU 永远不会执行它们 ,因为上一步的
jz直接跳过去了 - 但是!
0xE8这个数字在 IDA 看来就是 CALL 指令的开头
于是,IDA 误以为这里有一个函数调用,开始尝试反汇编后面的 4 个字节作为目标地址,从而产生误导性的伪代码或错误的控制流图,而实际上这部分代码在运行时根本不会被 CPU 执行。
题解
先看String
题目说这是一个加密程序
看到有Encrypted Result:,跟过去看看
点击x看引用
sub_1400017A0+181 lea rax, aEncryptedResul; "Encrypted Result:\n%s\n"
1507处有jz 2跳转,指令本身是两个字节,占据1507和1508,跳两个,应该从150B开始执行
但是ida错误的把应该跳过的1509识别成call 5
修复方法:点击1509行,按u(Undefine),点击150B行,按c(Code),再按F5即可查看反汇编
修改前
pseudocode
__int64 __fastcall sub_1400014E7(__int64 a1, __int64 a2, __int64 a3)
{
__int64 result; // rax
result = 0;
if ( a3 )
JUMPOUT(0x14000150BLL);
return result;
}
修改后
pseudocode
unsigned __int64 __fastcall sub_1400014E7(__int64 a1, __int64 a2, unsigned __int64 a3)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+8h] [rbp-8h]
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(a2 + i) = aRoses[i % 5] ^ *(_BYTE *)(a1 + i);
}
return result;
}
同理把剩下两个花指令修复
注意不要无脑看到jz后面有call就修复,算一些跳转的位数到底有没有跳过call
如果修复后f5还是不可读,按alt+P重新分析函数
跟着start内容跳转到sub_140001180
看到关键逻辑
Code = sub_1400017A0();
::Code = Code;
if ( !dword_14000D00C )
exit(Code);
查看sub_1400017A0的伪代码
pseudocode
__int64 sub_1400017A0()
{
__int64 v0; // rax
char *Block; // [rsp+20h] [rbp-30h]
void *v3; // [rsp+28h] [rbp-28h]
void *Buffer; // [rsp+30h] [rbp-20h]
size_t Size; // [rsp+38h] [rbp-18h]
FILE *Stream; // [rsp+40h] [rbp-10h]
sub_140001A20();
Stream = fopen("C:\\Users\\Lenovo\\Desktop\\1.txt", "rb");
if ( Stream )
{
fseek(Stream, 0, 2);
Size = ftell(Stream);
fseek(Stream, 0, 0);
if ( Size )
{
Buffer = malloc(Size);
v3 = malloc(Size);
if ( Buffer && v3 )
{
fread(Buffer, 1u, Size, Stream);
fclose(Stream);
sub_1400014E7((__int64)Buffer, (__int64)v3, Size);
Block = (char *)sub_140001578(v3, Size);
if ( Block )
{
sub_140001493("Encrypted Result:\n%s\n", Block);
free(Block);
}
else
{
sub_140001493("Encoding failed.\n");
}
free(Buffer);
free(v3);
return 0;
}
else
{
perror("Memory allocation failed");
fclose(Stream);
return 1;
}
}
else
{
fclose(Stream);
return 0;
}
}
else
{
v0 = psub_140007A50();
sub_140001450(v0, "Error: Cannot open file %s\n", "C:\\Users\\Lenovo\\Desktop\\1.txt");
return 1;
}
}
找关键逻辑
c
sub_1400014E7((__int64)Buffer, (__int64)v3, Size);
Block = sub_140001578((__int64)v3, Size);
if ( Block )
{
sub_140001493("Encrypted Result:\n%s\n", Block);
free(Block);
}
sub_1400014E7
c
unsigned __int64 __fastcall sub_1400014E7(__int64 Buffer, __int64 a2, unsigned __int64 Size)
{
unsigned __int64 Size_2; // rax
unsigned __int64 Size_1; // [rsp+8h] [rbp-8h]
for ( Size_1 = 0; ; ++Size_1 )
{
Size_2 = Size_1;
if ( Size_1 >= Size )
break;
*(_BYTE *)(a2 + Size_1) = aRoses[Size_1 % 5] ^ *(_BYTE *)(Buffer + Size_1);
}
return Size_2;
}
aRoses的值是'Roses',与Roses异或加密
sub_140001578
c
_BYTE *__fastcall sub_140001578(__int64 a1, unsigned __int64 Size)
{
__int64 v3; // rax
__int64 v4; // rax
unsigned __int64 n2; // [rsp+30h] [rbp-30h]
_BYTE *v6; // [rsp+38h] [rbp-28h]
unsigned int v7; // [rsp+4Ch] [rbp-14h]
__int64 v8; // [rsp+50h] [rbp-10h]
__int64 v9; // [rsp+50h] [rbp-10h]
unsigned __int64 Size_1; // [rsp+58h] [rbp-8h]
Size_1 = 0;
v8 = 0;
if ( !a1 || !Size )
return 0;
v6 = malloc(4 * ((Size + 2) / 3) + 1);
if ( !v6 )
return 0;
while ( Size_1 < Size )
{
n2 = Size - Size_1;
v7 = *(unsigned __int8 *)(a1 + Size_1) << 16;
if ( Size - Size_1 > 1 )
v7 |= *(unsigned __int8 *)(Size_1 + 1 + a1) << 8;
if ( n2 > 2 )
v7 |= *(unsigned __int8 *)(Size_1 + 2 + a1);
v6[v8] = aVxkw7qtsmd5bri[(v7 >> 18) & 0x3F];
v6[v8 + 1] = aVxkw7qtsmd5bri[(v7 >> 12) & 0x3F];
v3 = v8 + 2;
v9 = v8 + 3;
if ( n2 <= 1 )
v6[v3] = 61;
else
v6[v3] = aVxkw7qtsmd5bri[(v7 >> 6) & 0x3F];
v4 = v9;
v8 = v9 + 1;
if ( n2 <= 2 )
v6[v4] = 61;
else
v6[v4] = aVxkw7qtsmd5bri[v7 & 0x3F];
Size_1 += 3LL;
}
v6[v8] = 0;
return v6;
}
看起来是使用了自定义表的base64编码
'VxKw7QTsMd5Bri83NZe9Ut6pChXzD4IAYqmLuakbHofRWycvjGPnS2JE/+l01OFg'
py
import base64
CUSTOM_ALPHABET = "VxKw7QTsMd5Bri83NZe9Ut6pChXzD4IAYqmLuakbHofRWycvjGPnS2JE/+l01OFg"
STD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def decode(text):
trans = str.maketrans(CUSTOM_ALPHABET, STD_ALPHABET)
b64_std = text.translate(trans)
raw = base64.b64decode(b64_std)
key = b"Roses"
return bytes(raw[i] ^ key[i % 5] for i in range(len(raw)))
data="QY/sxQrVKqD77KCTsVy97GHpwV4PMGjKAaYR7q76X7OxtUQbNudppkxAAk11MYHxxVDOsUaQMZDRrmCbQPyFznMFKxjpTmCssaO9DLD1i2i+9nVNVKCVsmDeMNoQUtrupt26AaYksZUTdu1ExVDnttrJijMWMKVj5edKU7CipqD7BKCs7YWCwZhw7KjSpj7lVwSwxtN4i9VzVV7iTj75xLxCQqqF6wjsxVDbs7aQrxSmMPuJxmoFznSOTGhAUGrwsSULTPh9wZMq9G7VQLG3VNVprjjsVx4PsGCpUGURMn497GSseSdaCbu="
result = decode(data)
print(result)
SDPCSEC{M155_1da_thank_y0u_f0r_solv1ng_her_troub7e}
Gift
Some parts of the base64 encoding process seem different from the standard encoding, and it looks like it's not just in one place.
魔改base64,魔改了两处位置
ida打开
c
__int64 __fastcall main(int a1, char **a2, char **a3)
{
size_t v3; // rax
const char *s1; // rax
char *ptr; // rbx
char s_[4104]; // [rsp+2h] [rbp-1008h] BYREF
puts("Input:");
if ( !fgets(s_, 4096, stdin) )
goto LABEL_10;
v3 = strlen(s_);
if ( v3 && s_[v3 - 1] == 10 )
s_[v3 - 1] = 0;
s1 = (const char *)sub_1260(s_);
ptr = (char *)s1;
if ( s1 )
{
if ( !strcmp(s1, "YeBCCdVNCf4eMTNjXjFjXeNHJjNwFNlwWPxHLwZfXdxtFwZkGgZkHC==") )
puts("Correct");
else
puts("Wrong");
free(ptr);
return 0;
}
else
{
LABEL_10:
fwrite("Wrong\n", 1u, 6u, stderr);
return 1;
}
}
看看sub_1260如何处理输入
c
_BYTE *__fastcall sub_1260(const char *s)
{
size_t n2_3; // rax
size_t n2_1; // rbp
_BYTE *v4; // rsi
size_t n2; // rdi
__int64 n4; // rdx
size_t n2_2; // rcx
unsigned int v8; // eax
char n61; // al
unsigned int v10; // eax
char v11; // r8
_BYTE *v13; // rax
n2_3 = strlen(s);
if ( n2_3 )
{
n2_1 = n2_3;
v4 = malloc(4 * ((n2_3 + 2) / 3) + 1);
if ( v4 )
{
n2 = n2_1;
n4 = 4;
n2_2 = 0;
while ( 1 )
{
v10 = (unsigned __int8)s[n2_2] << 16;
if ( n2 == 1 )
{
v4[n4 - 2] = 61;
v11 = aZyxabcdefghijk[(v10 >> 18) ^ 0x15];
v4[n4 - 3] = aZyxabcdefghijk[(v10 >> 12) & 0x30 ^ 0x15];
v4[n4 - 4] = v11;
}
else
{
v8 = ((unsigned __int8)s[n2_2 + 1] << 8) | v10;
if ( n2 != 2 )
v8 |= (unsigned __int8)s[n2_2 + 2];
v4[n4 - 4] = aZyxabcdefghijk[(v8 >> 18) ^ 0x15];
v4[n4 - 3] = aZyxabcdefghijk[(v8 >> 12) & 0x3F ^ 0x15LL];
v4[n4 - 2] = aZyxabcdefghijk[(v8 >> 6) & 0x3F ^ 0x15LL];
if ( n2 != 2 )
{
n61 = aZyxabcdefghijk[v8 & 0x3F ^ 0x15LL];
goto LABEL_8;
}
}
n61 = 61;
LABEL_8:
n2_2 += 3LL;
v4[n4 - 1] = n61;
n2 -= 3LL;
if ( n2_2 >= n2_1 )
{
v4[n4] = 0;
return v4;
}
n4 += 4;
}
}
}
else
{
v13 = malloc(1u);
v4 = v13;
if ( v13 )
*v13 = 0;
}
return v4;
}
看一眼魔改base64表
ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/
第二处魔改应该就是异或
见识少不敢直接确定只是加了个异或
第一次看这么长的伪代码,脑子快炸了
稍微整理下
c
if ( n2 == 1 )
{
v4[n4 - 2] = 61;
v4[n4 - 3] = aZyxabcdefghijk[(v10 >> 12) & 0x30 ^ 0x15];
v4[n4 - 4] = aZyxabcdefghijk[(v10 >> 18) ^ 0x15];
}
if ( n2 == 2 ){
v8 = ((unsigned __int8)s[n2_2 + 1] << 8) | v10;
v4[n4 - 4] = aZyxabcdefghijk[(v8 >> 18) ^ 0x15];
v4[n4 - 3] = aZyxabcdefghijk[(v8 >> 12) & 0x3F ^ 0x15LL];
v4[n4 - 2] = aZyxabcdefghijk[(v8 >> 6) & 0x3F ^ 0x15LL];
n61 = 61;
LABEL_8:
}
if ( n2 == 3 ){
v10 = (unsigned __int8)s[n2_2] << 16;
v8 = ((unsigned __int8)s[n2_2 + 1] << 8) | v10;
v8 |= (unsigned __int8)s[n2_2 + 2];
v4[n4 - 4] = aZyxabcdefghijk[(v8 >> 18) ^ 0x15];
v4[n4 - 3] = aZyxabcdefghijk[(v8 >> 12) & 0x3F ^ 0x15LL];
v4[n4 - 2] = aZyxabcdefghijk[(v8 >> 6) & 0x3F ^ 0x15LL];
n61 = aZyxabcdefghijk[v8 & 0x3F ^ 0x15LL];
goto LABEL_8;
}
每次循环都会把n61重新赋值为61
LABEL_8中v4[n4 - 1] = n61;将每组的最后一位赋值为n61
前面的几组循环都是3位,执行了
n61 = aZyxabcdefghijk[v8 & 0x3F ^ 0x15LL];
goto LABEL_8;
把n61替换为真实结尾,跳过n61=61直接进入LABEL_8
这里不要被缩进骗了,其实LABEL_8:也在循环里
n2=2的时候没有把n61改为"真实值",而是带着上一次的重置值61进入LABEL_8:
可算清楚点了,好难
先把之前写的decode脚本拿出来,改一下
py
STANDARD_ALPHABET = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/"
def decode(data: str, alphabet: str = STANDARD_ALPHABET) -> str:
padding = data.count("=")
data = data.rstrip("=")
result = []
for i in range(0, len(data), 4):
chunk = data[i : i + 4]
value = 0
for j, c in enumerate(chunk):
value |= (alphabet.index(c) ^ 0x15) << (18 - j * 6)
n = 4 - padding if i >= len(data) - 4 else 4
for j in range(n - 1):
result.append(chr((value >> (16 - j * 8)) & 0xFF))
return "".join(result)
a=decode("YeBCCdVNCf4eMTNjXjFjXeNHJjNwFNlwWPxHLwZfXdxtFwZkGgZkHC==")
print(a)
SDPCSEC{th1s_7s_A_g1ft_f0r_new_Reverser}