1. ezcrypt(史)
pyinstxtractor.py解包exe,然后pycdc反编译NSSCTF.pyc
得到的源码并不完整,但是重要的部分已经有了,就是一个blowfish加密
但是密钥是crypto.SomeEncode
,这并不是字面意义的字符串,而是引用的其他文件
NSSCTF.exe_extracted
可以找到可疑文件crypto.cpython.pyc
是一个魔改的TEA(这里出题逻辑有问题,按给的代码逻辑SomeEncode是v加密得到的,但实际上却要我们解密得到)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
unsigned int enc[5] =
{
1396533857,
0xCC8AE275,
0x89FB8A63,
940694833
};
unsigned int key[4] =
{
17,
34,
51,
68
};
int i, j;
for (i = 0; i < 4; i += 2)
{
unsigned int v0 = enc[i], v1 = enc[i + 1], sum = 0xC6EF3720;
unsigned int delta = 0x9e3779b9;
unsigned int k0 = key[0], k1 = key[1], k2 = key[2], k3 = key[3];
for (j = 0; j < 32; j++)
{
v1 -= ((v0 << 3) + k2 ^ v0 + sum ^ (v0 >> 4) + k3 ^ 2310) & 0xFFFFFFFF;
v0 -= ((v1 << 3) + k0 ^ v1 + sum ^ (v1 >> 4) + k1 ^ 596) & 0xFFFFFFFF;
sum -= delta & 0xFFFFFFFF;
}
enc[i] = v0; enc[i + 1] = v1;
}
for (i = 0; i < 4; i++)
{
printf("%u ", enc[i]);
}
printf("%s", enc);//sNzEveRsjorPstce
return 0;
}
这里有个每四位一个逆序(第三行就是ASCII转字符的意思),解出来手动改一下吧,就是 EzNssRevProjects
#ezcrypt wp
from Crypto.Cipher import Blowfish
from Crypto.Util.Padding import unpad
from Crypto.Random import get_random_bytes
def decrypt_file(input_path, output_path, key):
with open(input_path, 'rb') as f:
# 读取整个文件内容,包括IV和密文
data = f.read()
# 分割IV和密文
iv = data[:Blowfish.block_size]
print(iv)
ciphertext = data[Blowfish.block_size:]
print(ciphertext)
# 验证密钥长度
#if len(key) != Blowfish.key_size:
# raise ValueError("Invalid key size for Blowfish. It must be exactly 8 bytes long.")
# 创建一个新的Blowfish cipher对象,并设置密钥和IV
cipher = Blowfish.new(key, Blowfish.MODE_CBC, iv=iv)
# 解密数据
plaintext = (cipher.decrypt(ciphertext), Blowfish.block_size)
print(plaintext[0])
# 将解密后的数据写入输出文件
with open(output_path, 'wb') as f:
f.write(plaintext[0])
key = b'EzNssRevProjects'
input_path = 'D:\\下载\\CTF附件\\NSS22re\\output'
output_path = 'D:\\下载\\CTF附件\\NSS22re\\flag'
decrypt_file(input_path, output_path, key)
密钥解码得到一个二进制文件,IDA打开是个虚拟机(笑)
动调不出来(这里逻辑也莫名其妙,明明这个代码就应该能跑出flag,但实际上是要你把代码反过来写)
硬做,动调取出opcode,key
VM函数抄下来符号改一下,有个地方给的代码还不对,看汇编也是sub
这里应该是加号
#include<stdio.h>
__int64 __fastcall vm(unsigned char* a1, int* a2, int a3)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-8h]
int v5; // [rsp+20h] [rbp-4h]
for (i = 0; ; ++i)
{
result = i;
if (i >= a3)
break;
v5 = i % 4;
if (i % 4 == 3)
{
a1[i] -= a2[8] + a2[0];
}
else if (v5 <= 3)
{
if (v5 == 2)
{
a1[i] += a2[4] ^ a2[12];
}
else if (v5 <= 2)
{
if (v5)
{
if (v5 == 1)
{
a1[i] ^= a2[0] - a2[8];
}
}
else
{
a1[i] ^= a2[16] + a2[4];
}
}
}
}
return result;
}
int main()
{
unsigned char opcode[] =
{
0x37, 0x8D, 0x0F, 0xAB, 0x2D, 0x98, 0x37, 0xB5, 0x48, 0xA6,
0x30, 0xDA, 0x0C, 0xED, 0x1B, 0xB8, 0x00, 0xE9, 0x24, 0x98,
0x17, 0x81, 0xED, 0xB6, 0x26, 0x8C, 0x21, 0xDE, 0x04, 0xDE
};
int key[] =
{
0x23, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x45, 0x00,
0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x67
};
vm(opcode, key, 30);
printf("%s", opcode);//NSSCTF{M1xtru3_Py7h0n_1N_Rev}
return 0;
}
2. EzHook
看起来只是异或,但是最后的字符有?很奇怪
联系题目实际上使用了Windows IAT Hook技术
动调试试,断在密文处
随你怎么输都是走左边
F8到MessageBoxA这里F7进去
进到另一个函数里,应该是hook注入的函数
这里是真正的提示分支,qword_7FF63596A090应当是真实的MessageBox
我们注意所有输入在78行xxtea加密之后,经过密文比较。又在83和88行解密
只要在解密之前把输入a2改成密文Str2即可
a2应该存在rcx寄存器里
每16个地址Change byte一次,把对应的密文填进去
然后跑到这里,a1点进去
3. 简简又单单
jadx打开
md还是个魔改XTEA
#include<stdio.h>
void xtea_decipher(unsigned int num_rounds, unsigned int v[2], unsigned int const key[4]) {
unsigned int i;
unsigned int 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)^918) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= ((((v1 << 4) ^ (v1 >> 5)) + v1)^918) ^ (sum + key[sum & 3]);
}
v[0] = v0; v[1] = v1;
}
int main()
{
unsigned int enusr[] = { 0x3c36eb49,0x81acb0c0,0xfac269ae,0xca5bf9ec }; // 密文
unsigned int key[4] = { 0x12345678, 0x5678ABCD, 0x89ABCDEF, 0xCDEF1234 }; // 密钥
for (int i = 0; i < 4; i += 2) {
unsigned int tmp[2] = { 0 };
tmp[0] = enusr[i];
tmp[1] = enusr[i + 1];
xtea_decipher(32, tmp, key);
enusr[i] = tmp[0];
enusr[i + 1] = tmp[1];
}
printf("%s", enusr);
//NS5_R0Un6_z2_apK
return 0;
}
密码看不到,因为在native层中
解压apk文件,找到so文件
可以搜到相应的函数
Java_com_example_nss_MainActivity_validatePassword
内可以找到密文
应该是RC4加密过,但是脚本跑不出,又魔改了
Java_com_example_nss_MainActivity_encryptWithRC4
看看
改成了128轮加密
加密密钥是用户名
#ez_APK wp
def rc4(data, key):
S = list(range(128))
j = 0
out = []
for i in range(128):
j = (j + S[i] + key[i % len(key)]) % 128
S[i], S[j] = S[j], S[i]
i = j = 0
for char in data:
i = (i + 1) % 128
j = (j + S[i]) % 128
S[i], S[j] = S[j], S[i]
out.append(char ^ S[(S[i] + S[j]) % 128])
return bytes(out)
data = bytes.fromhex("572e180b1a680b3e5276344b241d5b52525a043173346b1355442028")
key = b'NS5_R0Un6_z2_apK'
decrypted = rc4(data, key)
print(decrypted)
#NSSCTF{V3ry_4z_1ib_W1th_4pk}
4. GO!GO!GO!
shell看不出东西,分析主文件
好像是调用了shell
大量的WinAPI调用,有个函数进去看看
似乎是要卸载表面上的内存映像,注入傀儡进程
查询qword_140005080的交叉引用定位到
动调发现qword_140005080内资源开头为MZ,是PE文件
ResourceHacker正好可以提取
非常恶心go语言
这是输入的key1,可以用hashcat爆破
hashcat -m 0 -a 3 b098cacb2d43b882ef9a83168d13c3a7 ?a?a?a?a?a?a
稍等一段时间,烧一会GPU就出来了 G0@K3y
key2是一个道理
hashcat -m 1400 -a 3 c32a69f4609191a2c3e6dbe2effc329bff20617230853462e8745f2c058bec2f ?a?a?a?a?a?a
得到n3SC1f
又输入一段东西进了main_Function2
第三次输入的就是flag
发现RC4特征
有魔改,但是无所谓直接动调
先找一下RC4的密钥,应该和之前的输入有关
看到密钥被拼接了
由于RC4是对称加密,所以可以考虑把密文提出来再写入用原程序解密
先动调取出密文
然后重新动调,找到存储flag的内存位置
patch成密文
动调到加密之后