x2658y's Blog

杂七杂八的记事本

给了两个文件, 一个加密后的文件, 一个公钥, 开始我以为是用公钥去解密私钥加密的文件, 但是很多加密库都没有提供这样的功能, 比如OpenSSL, 只有用公钥验签的功能. 看了别人的WP才知道这个题是分解公钥的来实现算出私钥再进行解密.

OpenSSL可以查看公钥的一些信息

image-20220125132123596

我们可以得知 \[ \begin{aligned} &e=65537\\ &n=C0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD \end{aligned} \]

而且公钥比常见的要短很多, 只有256Bit, 这为攻击提供了方便

一些分解的工具:

RsaCtfTool: Github //这个工具不支持Windows

yafu: Sourceforge

Msieve: Sourceforge

yafu方式, 运行后进入yafu命令行, 输入

1
factor(0xC0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD)

即可开始分解

image-20220125140140434

RsaCtfTool方式, 安装好后输入

1
./RsaCtfTool.py -n 0xC0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD -e 65537 --private --dumpkey

工具就会综合判断自动采用合适的方式得出私钥(也可以直接用这个工具解密文件, 详细用法见Github)

image-202201251408520

RsaCtfTool已经帮我们生成好了私钥, 如果是用yafu的话还需要计算一下d

我们已经得出 \[ \begin{aligned} &p=285960468890451637935629440372639283459 \\ &q=304008741604601924494328155975272418463 \end{aligned} \]

有了\(p\)\(q\)就可以轻松求出欧拉函数的值

\[\phi(n)=\phi(p)\times\phi(q)=(p-1)\times(q-1)\]

根据公式 \(ed\equiv1(\mod\phi(n))\), 则de\(\phi(n)\)模逆元, Python可以通过gmpy2这个库来求得

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
#此题的密文并非PKCS1_OAEP填充方式
import gmpy2

n = 0xC0332C5C64AE47182F6C1C876D42336910545A58F7EEFEFC0BCAAF5AF341CCDD
e = 65537
p = 285960468890451637935629440372639283459
q = 304008741604601924494328155975272418463
phi_n = (p-1)*(q-1) #求欧拉函数值
d = int(gmpy2.invert(e, phi_n)) #求模逆元d

private_key = RSA.construct((n,e,d,p,q)) #用参数构造私钥
cipher = PKCS1_v1_5.new(private_key) #用私钥生成一个Cipher
ciphertext = open("flag.enc","rb").read()
plaintext = cipher.decrypt(ciphertext, None).decode()
print(plaintext)
#flag{decrypt_256}
话说BUU为什么把这个题放到Reverse板块里面呢🤨

UPX壳, 先脱壳image-20220123231753142

这次采用堆栈平衡法(esp定律法)来寻找popad, 从而找到程序的真正的入口. 也可以采用直接找popad的方法, UPX一般可以这么干, 见 [BUUCTF]新年快乐

程序的入口点是一个pushad, F8让它执行, 改变esp, 对esp下硬件断点

image-20220123232551388

然后让程序跑起来, 被硬件断点断下, 可以看到刚好断在popad之后.

一般对于UPX来说popad之后的第一个jmp就是跳到程序真正的入口点, 这里是0x401280

image-20220123232745208

然后用Scylla插件脱壳, 用法见 [BUUCTF]新年快乐

脱壳完了拖进IDA, F5查看伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE v4[12]; // [esp+12h] [ebp-2Eh] BYREF
_DWORD v5[3]; // [esp+1Eh] [ebp-22h]
_BYTE v6[5]; // [esp+2Ah] [ebp-16h] BYREF
int v7; // [esp+2Fh] [ebp-11h]
int v8; // [esp+33h] [ebp-Dh]
int v9; // [esp+37h] [ebp-9h]
char v10; // [esp+3Bh] [ebp-5h]
int i; // [esp+3Ch] [ebp-4h]

sub_401A10();
qmemcpy(v4, "*F'\"N,\"(I?+@", sizeof(v4));
printf("Please input:");
scanf("%s", v6); //利用scanf()的溢出去修改v7~v10
if ( v6[0] != 'A' || v6[1] != 'C' || v6[2] != 'T' || v6[3] != 'F' || v6[4] != '{' || v10 != '}' )
//判断输入格式符不符合ACTF{...}
return 0;
v5[0] = v7;
v5[1] = v8;
v5[2] = v9;
for ( i = 0; i <= 11; ++i )
{
if ( v4[i] != byte_402000[*((char *)v5 + i) - 1] )
//逐个字节对比, 可以通过在码表中逐位穷举来逆推出v5的每一个字节
return 0;
}
printf("You are correct!");
return 0;
}

取得v5的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <cstdio>

using namespace std;

int main()
{
char v4[] = "*F'\"N,\"(I?+@";
char v5[16] = { 0 };
unsigned char byte_402000[] =
{
126, 125, 124, 123, 122, 121, 120, 119, 118, 117,
116, 115, 114, 113, 112, 111, 110, 109, 108, 107,
106, 105, 104, 103, 102, 101, 100, 99, 98, 97,
96, 95, 94, 93, 92, 91, 90, 89, 88, 87,
86, 85, 84, 83, 82, 81, 80, 79, 78, 77,
76, 75, 74, 73, 72, 71, 70, 69, 68, 67,
66, 65, 64, 63, 62, 61, 60, 59, 58, 57,
56, 55, 54, 53, 52, 51, 50, 49, 48, 47,
46, 45, 44, 43, 42, 41, 40, 39, 38, 37,
36, 35, 32, 33, 34, 0
};
for (int i = 0; i <= 11; ++i)
for (int j = 0; j < sizeof(byte_402000); j++)
//设v5[i]-1=j, 穷举出j的值
if (v4[i] == byte_402000[j])
v5[i] = j + 1;
printf("%s", v5);
}
//U9X_1S_W6@T?

我们就得出ACTF{U9X_1S_W6@T?}, 验证一下

image-20220124004429711

没问题, 所以flag就是flag{U9X_1S_W6@T?}

Python的逆向, 采用uncompyle6进行"反编译"

uncompyle6: Github

安装很简单, 直接用pip安装就可以了(pip建议先换源)

1
pip install uncompyle6

使用也简单, 比如要反编译test.pyc输出到decompile.py文件

1
uncompyle6 -o decompile.py test.pyc
image-20220123180336910

反编译出来的Python代码有些问题, 还是Python2的, 不过出题人的意图很明显, 最下面的code应该是input1经过两次变换后的结果, 要用它逆推出input1.

image-20220123181846852

可以对每一位穷举, 照着这个代码里的变换规则来就行, 解密代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <cstdio>

using namespace std;

int main()
{
char arr[] = { '\x1f', '\x12', '\x1d', '(', '0', '4', '\x01', '\x06', '\x14', '4', ',', '\x1b', 'U', '?', 'o', '6', '*', ':', '\x01', 'D', ';', '%', '\x13' };
char result[64] = { 0 };
for (int i = sizeof(arr) - 2; i >= 0; i--) //先进行一次异或,初步还原内容
arr[i] = arr[i] ^ arr[i + 1];
for (int i = 0; i < sizeof(arr); i++)
for (int j = 32; j < 127; j++) //穷举所有可打印ASCII字符
if (((j + i) % 128 + 128) % 128 == arr[i])
result[i] = j;
printf("%s", result);
}
//GWHT{Just_Re_1s_Ha66y!}

所以flag就是flag{Just_Re_1s_Ha66y!}

0%