1. 茶:
1.1. 经典查壳
64位无壳软件:
1.2. ida 启动
main
函数还是比较复杂的,他的流程大概就是输入 flag,然后经过加密,在经过 if 语句判断与密文进行对比,我们使用 ida 的查询加密算法的插件来进行查询一下他的加密算法:
他给出的是salsa20加密,但是结果并不是这个加密算法,所以还是要靠自己的经验来观察
这个函数就是我们所要进行解密的函数,这个加密其实是 chacha20
加密,经过搜索可以得到 chacha20
加密他需要一个密钥 key
,一个偏移量 ponce
,其中偏移量是一个 12 位的,但是他给的是 16 位,经过动调发现,他只取了前 12 位,下面给出解密代码(使用 python 的库进行解密,这个 Crypto 是真的难安装)
from Cryptodome.Cipher import ChaCha20
enc_data = bytes.fromhex("f568c48912eed6dc520c7164f44b6378e1d0d3e248914fa8847b405a131f")
key = b"SGludDogSW1wcm92ZSBvZiBTYWxzYTIw"
nonce = b"Is_This_"
cc = ChaCha20.new(key=key, nonce=nonce)
print(cc.decrypt(enc_data))
flag 就是 flag{But_I_Like_ChaCha20_More}
chacha20加密参考文章:链接已丢失,需要补上
2. Pypl
记录一下这个 python 逆向的解决
2.1. 解包
首先可以观察到这个是一个 python 进行打包的程序所以使用 pyinstxtractor 解包。
我刚开始使用的是低于 2.0 的版本,这个解包出来的 pyc 不带后缀,而且也没有自动填补程序头首先将所要解包的程序放在与其 pyinstxtractor 放在一起,然后打开终端运行python pyinstxtractor.py 解包.exe (格式)
,执行完毕会看到 successful
,而且会生成一个文件夹:
上面的文件夹就存放着我们解包的 pyc 文件,我们往他里去找根文件夹名字相同的文件。
然后如果没有 pyc 的后缀,我们手动添加,最后拉入到 010editor 中进行查看文件头。
如果缺少 magic 头,我们就应该重新修改,所以我们还是使用 2.0 以上的版本吧,方便快捷此时我们可以通过一个 dll 文件进行查询他的 python 版本
所以这是一个python311的版本贴一下各个版本的magic头
enum PycMagic {
MAGIC_1_0 = 0x00999902,
MAGIC_1_1 = 0x00999903, /* Also covers 1.2 */
MAGIC_1_3 = 0x0A0D2E89,
MAGIC_1_4 = 0x0A0D1704,
MAGIC_1_5 = 0x0A0D4E99,
MAGIC_1_6 = 0x0A0DC4FC,
MAGIC_2_0 = 0x0A0DC687,
MAGIC_2_1 = 0x0A0DEB2A,
MAGIC_2_2 = 0x0A0DED2D,
MAGIC_2_3 = 0x0A0DF23B,
MAGIC_2_4 = 0x0A0DF26D,
MAGIC_2_5 = 0x0A0DF2B3,
MAGIC_2_6 = 0x0A0DF2D1,
MAGIC_2_7 = 0x0A0DF303,
MAGIC_3_0 = 0x0A0D0C3A,
MAGIC_3_1 = 0x0A0D0C4E,
MAGIC_3_2 = 0x0A0D0C6C,
MAGIC_3_3 = 0x0A0D0C9E,
MAGIC_3_4 = 0x0A0D0CEE,
MAGIC_3_5 = 0x0A0D0D16,
MAGIC_3_5_3 = 0x0A0D0D17,
MAGIC_3_6 = 0x0A0D0D33,
MAGIC_3_7 = 0x0A0D0D42,
MAGIC_3_8 = 0x0A0D0D55,
MAGIC_3_9 = 0x0A0D0D61,
MAGIC_3_10 = 0x0A0D0D6F,
MAGIC_3_11 = 0x0A0D0DA7,
MAGIC_3_12 = 0x0A0D0DCB,
INVALID = 0,
};
参考资料: Python逆向全版本MagicNumber表
接下来就是准备将 pyc 反编译为 python 代码。
2.2 反编译
这里我尝试使用uncompyle6进行反编译,但是一直报错,我要不知道什么原因导致的,贴个图记录一下错误看看以后可以解决吗:
所以我还是使用在线网站将其反编译了
使用这个可以解决得到代码:
#!/usr/bin/env python
# Version: Python 3.11
from Crypto.Util.number import bytes_to_long
def enc(key):
R = bytes_to_long(b'Welcome To PaluCTF!')
MOD = 2 ** 418
R = R ^ R - 60 >> 24
R = R ^ R - 60 << 88
R ^= key
R = -R ** 2 * 2024 % MOD
R = R * key % MOD
return R
flag = input('Welcome To PaluCTF!\nInput FLAG:')
m = bytes_to_long(flag.encode())
cor = 0x2E441F765514CCA89173554726494D37E9FBE774B6F807BC5F6E71117530CE3D7DB5F70554C03CD9055F4E42969600904DF1F4DB8L
if enc(m) == cor:
print('Congratulation!')
return None
print('Wrong FLAG!')
下面解决这个加密,设计异或和位运算所以使用这Z3对其进行解密给一下代码:
from z3 import *
from Cryptodome.Util.number import *
def enc(key):
R = bytes_to_long(b"Welcome To PaluCTF!")
MOD = 2**418
R = R ^ ((R - 60) >> 24)
R = R ^ ((R - 60) << 88)
R ^= key
R = (-R * R * 2024) % MOD
R = (R * key) % MOD
return R
answer = 0x2E441F765514CCA89173554726494D37E9FBE774B6F807BC5F6E71117530CE3D7DB5F70554C03CD9055F4E42969600904DF1F4DB8
s=Solver()
key=BitVec('key',418)
s.add(enc(key)==answer)
s.check()
res=s.model()
#print(res)
flag=long_to_bytes(res[key].as_long())
print(flag)
3. O2
3.1. 经典查壳:
32位elf程序
3.2. IDA启动
可以发现 ELF 的头文件报错了:
所以我们找一个 32 位 ELF 文件参考一下:
下面是正常的:
所以我们修改一下,将偏移 0x0005
处的 01
修改为 02
,意思是这是一个64位的程序:
1
32位程序
2
64位程序
成功进入主函数但是我的 main
函数不能反编译,显示编译错误,这个我没查出来是什么原因。
网上教程说可以通过动调解决一下,就是找到出错的地址,然后F5反编译一下,然后返回就可以F5了试了试果然可以。
得到了主函数:
__int64 __fastcall main(int a1, char ** a2, char ** a3) {
__int64 v3; // rax
std::ostream * v4; // rbx
__int64 v5; // rax
_BYTE * v6; // rbp
char v7; // si
std::ostream * v8; // rax
__int64 v9; // rdx
size_t v10; // rbx
__int64 v11; // r13
__int64 v12; // rdx
__int64 v13; // rbx
__int64 v14; // rdx
__int64(__fastcall * v16)(); // rax
_BYTE v17[32]; // [rsp+0h] [rbp-68h] BYREF
void * s1; // [rsp+20h] [rbp-48h] BYREF
size_t n; // [rsp+28h] [rbp-40h]
v3 = std::operator << < std::char_traits < char >> ( & std::cout, "Hello! ", a3);
v4 = (std::ostream * ) std::__ostream_insert < char, std::char_traits < char >> (v3, qword_607AF3F30340, qword_607AF3F30348);
v5 = * (_QWORD * )( * (_QWORD * ) v4 - 24 LL);
v6 = * (_BYTE ** )((char * ) v4 + v5 + 240);
if (!v6)
std::__throw_bad_cast();
if (v6[56]) {
v7 = v6[67];
} else {
std::ctype < char > ::_M_widen_init( * (_QWORD * )((char * ) v4 + v5 + 240));
v7 = 10;
v16 = * (__int64(__fastcall ** )())( * (_QWORD * ) v6 + 48 LL);
if (v16 != std::ctype < char > ::do_widen)
v7 = ((__int64(__fastcall * )(_BYTE * , __int64)) v16)(v6, 10 LL);
}
v8 = (std::ostream * ) std::ostream::put(v4, v7);
std::ostream::flush(v8);
std::operator << < std::char_traits < char >> ( & std::cout, "Check Flag:", v9);
std::operator >> < char > ( & std::cin, & unk_607AF3F30360);
sub_607AF3F2D5C0(v17, & unk_607AF3F30360, & qword_607AF3F30340);
sub_607AF3F2D6C0((__int64) & s1, (__int64) v17);
v10 = n;
v11 = qword_607AF3F30388;
v12 = qword_607AF3F30388;
if (n <= qword_607AF3F30388)
v12 = n;
if (v12 && memcmp(s1, obj, v12) || (v13 = v10 - v11, v13 > 0x7FFFFFFF) || v13 < (__int64) 0xFFFFFFFF80000000 LL) {
std::string::_M_dispose( & s1);
goto LABEL_14;
}
std::string::_M_dispose( & s1);
if ((_DWORD) v13) {
LABEL_14: std::operator << < std::char_traits < char >> ( & std::cout, "Wrong!", v14);
goto LABEL_12;
}
std::operator << < std::char_traits < char >> ( & std::cout, "Right!", v14);
LABEL_12:
std::string::_M_dispose(v17);
return 0 LL;
}
观察一下,我们可以发现 sub_607AF3F2D5C0(v17, &unk_607AF3F30360, &qword_607AF3F30340);
就是加密函数
__int64 * __fastcall sub_607AF3F2D5C0(__int64 * a1, char ** a2, _QWORD * a3) {
__int64 * v3; // rcx
char * v4; // r12
__int64 v6; // r15
int v7; // ebp
__int64 v8; // rax
unsigned __int64 v9; // r13
unsigned __int64 v10; // rax
int v12; // [rsp+Ch] [rbp-4Ch]
char * v13; // [rsp+10h] [rbp-48h]
v3 = a1 + 2;
*((_BYTE * ) a1 + 16) = 0;
* a1 = (__int64)(a1 + 2);
v4 = * a2;
a1[1] = 0 LL;
v13 = & a2[1][(_QWORD) v4];
if (v4 != v13) {
v6 = 0 LL;
v7 = 0;
while (1) {
v9 = v6 + 1;
v12 = ( * v4 + * (char * )( * a3 + v7)) % 128;
v10 = a1 + 2 == v3 ? 15 LL : a1[2];
if (v10 < v9) {
std::string::_M_mutate(a1, v6, 0 LL, 0 LL, 1 LL);
v3 = (__int64 * ) * a1;
}
++v4;
*((_BYTE * ) v3 + v6) = v12;
v8 = * a1;
a1[1] = v9;
*(_BYTE * )(v8 + v9) = 0;
v7 = (unsigned __int64)(v7 + 1) % a3[1];
if (v13 == v4)
break;
v6 = a1[1];
v3 = (__int64 * ) * a1;
}
}
return a1;
}
v12 = (*v4 + *(char [i])(a3 + v7)) % 128;
这一句就是主要的加密函数可以简单分析一下各个变量的意思
v4
就是我们输入的值,也就是 flag(enc)
a3
就是传入的 key,我们可以找到他也就是 v12=(enc[i]+key[i])% 128
所以我们进行解密:
enc=bytes.fromhex("364d4d5c3e387e00421c597a0a7302144d5b70087e064619567336297d151f56770a7935424f2a780643")
key=b'PaluCTF'
flag=''
for i in range(len(enc)):
flag+=chr((enc[i]-key[i%7])%128)
print(flag)
解释一下代码,方便下次自己使用。
bytes.fromhex
是将连续的两个数转换为 hex:
hex_string = '616263'
byte_array = bytearray.fromhex(hex_string)
print(byte_array)
bytearray(b'abc')
还有一种解法就是爆破,学习一下大佬的答案:
from string import printable
enc = bytearray.fromhex("364d4d5c3e387e00421c597a0a7302144d5b70087e064619567336297d151f56770a7935424f2a780643")
key = "PaluCTF"
for i in range(len(enc)):
for c in printable:
if (ord(c) + ord(key[i % len(key)])) % 128 == enc[i]:
print(c, end="")
break
这里的 from string import printable
:
# import string library function
import string
# Storing the sets of punctuation,
# digits, ascii_letters and whitespace
# in variable result
result = string.printable
# Printing the set of values
print(result)
输出结果:
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+, -./:;<=>?@[\]^_`{|}~
返回一系列可打印的值,所以我们使用双重循环进行爆破就ok了
答案是 flag{d80a0d76-23af-486e-a0bc-43a463eac552}
剩下两道不是很懂,再分析分析再写吧,帕鲁杯收获还是挺大的,对于加密算法也是又了解到一种。
修改记录: