menu EDS
Moectf2024开发与运维基础writeup
237 浏览 | 2024-10-23 | 分类:默认分类 | 标签:

开发与运维基础

哦不!我的libc!

题目说环境没有libc,那就需要一个不需要libc就能执行的可执行文件,用纯汇编写一段orw。

    # 将/flag.txt字符串写入栈并将地址保存到r11寄存器
    mov r10,0x74;
    push r10;
    mov r10,0x78742E67616C662f;
    push r10;
    mov r11,rsp;

    # 构造sys_open 打开/flag.txt
    sub rsp,0x100;
    mov rax,2;
    mov rdi,r11;
    xor rsi,rsi;
    mov rdx,rsi;
    syscall;

   # 构造sys_read 读取/flag.txt
    mov rdi,rax;
    mov rsi,rsp;
    mov rdx,0x80;
    xor rax,rax;
    syscall;

   # 构造sys_write 输出/flag.txt
    mov rdi,1;
    mov rax,rdi;
    syscall;

生成的shellcode

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   49 C7 C2 74 00 00 00 41  52 49 BA 2F 66 6C 61 67   
00000010   2E 74 78 41 52 49 89 E3  48 81 EC 00 01 00 00 48   
00000020   C7 C0 02 00 00 00 4C 89  DF 48 31 F6 48 89 F2 0F   
00000030   05 48 89 C7 48 89 E6 48  C7 C2 80 00 00 00 48 31   
00000040   C0 0F 05 48 C7 C7 01 00  00 00 48 89 F8 0F 05      

发现用什么工具编译都不能弄出不依赖libc的东西,静态编译的话又太大。这时想到metasploit编译的meterpreter客户端都短小精悍,一般只有250b,用ida打开发现都是纯纯大汇编。利用winhex将入口(0x78处)的shellcode换成我们的。
覆写完的文件hex

Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   7F 45 4C 46 02 01 01 00  00 00 00 00 00 00 00 00    ELF            
00000010   02 00 3E 00 01 00 00 00  78 00 40 00 00 00 00 00     >     x @     
00000020   40 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   @               
00000030   00 00 00 00 40 00 38 00  01 00 00 00 00 00 00 00       @ 8         
00000040   01 00 00 00 07 00 00 00  00 00 00 00 00 00 00 00                   
00000050   00 00 40 00 00 00 00 00  00 00 40 00 00 00 00 00     @       @     
00000060   FA 00 00 00 00 00 00 00  7C 01 00 00 00 00 00 00   ?      |       
00000070   00 10 00 00 00 00 00 00  49 C7 C2 74 00 00 00 41           I锹t   A
00000080   52 49 BA 2F 66 6C 61 67  2E 74 78 41 52 49 89 E3   RI?flag.txARI夈
00000090   48 81 EC 00 01 00 00 48  C7 C0 02 00 00 00 4C 89   H ?   H抢    L?
000000A0   DF 48 31 F6 48 89 F2 0F  05 48 89 C7 48 89 E6 48   逪1鯤夠  H壡H夋H
000000B0   C7 C2 80 00 00 00 48 31  C0 0F 05 48 C7 C7 01 00   锹€   H1? H乔  
000000C0   00 00 48 89 F8 0F 05 25  49 FF C9 74 18 57 6A 23     H夬  %I蓆 Wj#
000000D0   58 6A 00 6A 05 48 89 E7  48 31 F6 0F 05 59 59 5F   Xj j H夌H1? YY_
000000E0   48 85 C0 79 C7 6A 3C 58  6A 01 5F 0F 05 5E 6A 7E   H吚y莏<Xj _  ^j~
000000F0   5A 0F 05 48 85 C0 78 ED  FF E6                     Z  H吚x??

由于我们直接写入的文件没有可执行权限,chmod命令又不能使用。需要覆写进入一个已有的可执行文件内。利用printf函数无损写入

printf "\x7F\x45\x4C\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3E\x00\x01\x00\x00\x00\x78\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\xFA\x00\x00\x00\x00\x00\x00\x00\x7C\x01\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x49\xC7\xC2\x74\x00\x00\x00\x41\x52\x49\xBA\x2F\x66\x6C\x61\x67\x2E\x74\x78\x41\x52\x49\x89\xE3\x48\x81\xEC\x00\x01\x00\x00\x48\xC7\xC0\x02\x00\x00\x00\x4C\x89\xDF\x48\x31\xF6\x48\x89\xF2\x0F\x05\x48\x89\xC7\x48\x89\xE6\x48\xC7\xC2\x80\x00\x00\x00\x48\x31\xC0\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\x89\xF8\x0F\x05\x25\x49\xFF\xC9\x74\x18\x57\x6A\x23\x58\x6A\x00\x6A\x05\x48\x89\xE7\x48\x31\xF6\x0F\x05\x59\x59\x5F\x48\x85\xC0\x79\xC7\x6A\x3C\x58\x6A\x01\x5F\x0F\x05\x5E\x6A\x7E\x5A\x0F\x05\x48\x85\xC0\x78\xED\xFF\xE6" > /usr/bin/base64

执行

root@ret2shell-89-9018:~# base64
moectf{BU5YB0x-i5_SOO0OoO10OOOo0OOoOOOOO0o0OOo_BUSyd}
Segmentation fault (core dumped)
root@ret2shell-89-9018:~# 

哦不!我的nginx!

同上题,先构造纯纯大汇编读取/etc/nginx/nginx.confnginx的配置文件

mov r10,0x666E6F632E;
push r10;
mov r10,0x786E69676E2F786E;
push r10;
mov r10,0x69676E2F6374652F;
push r10;
mov r11,rsp;

sub rsp,0x300;
mov rax,2;
mov rdi,r11;
xor rsi,rsi;
mov rdx,rsi;
syscall;

mov rdi,rax;
mov rsi,rsp;
mov rdx,0x800;
xor rax,rax;syscall;

mov rdi,1;
mov rax,rdi;
syscall;

读取到

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    #
    # Basic Settings
    #

    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;
    # server_tokens off;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    #
    # SSL Settings
    #

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    #
    # Logging Settings
    #

    access_log /var/log/nginx/access.log;

    #
    # Gzip Settings
    #

    gzip on;

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    #
    # Virtual Host Configs
    #

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}


#mail {
#    # See sample authentication script at:
#    # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
#    # auth_http localhost/auth.php;
#    # pop3_capabilities "TOP" "USER";
#    # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
#    server {
#        listen     localhost:110;
#        protocol   pop3;
#        proxy      on;
#    }
#
#    server {
#        listen     localhost:143;
#        protocol   imap;
#        proxy      on;
#    }
#}

用cd命令获取各个conf的文件夹,发现仅cd /etc/nginx/sites-enabled/的时候存在default文件,再用纯纯大汇编读取/etc/nginx/sites-enabled/default。

xor r10,r10;
push r10;
mov r10,0x746C75616665642F;
push r10;
mov r10,0x64656C62616E652D;
push r10;
mov r10,0x73657469732F786E;
push r10;
mov r10,0x69676E2F6374652F;
push r10;
mov r11,rsp;

sub rsp,0x300;
mov rax,2;
mov rdi,r11;
xor rsi,rsi;
mov rdx,rsi;
syscall;

mov rdi,rax;
mov rsi,rsp;
mov rdx,0x800;
xor rax,rax;syscall;

mov rdi,1;
mov rax,rdi;
syscall;

读取到

server {
    listen       80 default_server;
    root         /var/www/html;

    location / {
        index index.html;
    }
}
/etc/nginx/sites-enabled/default

现在得知nginx监听的是80端口,用纯纯大汇编写一个监听80端口,并将接收到的tcp流量输出出来,直接写起来有点费劲,想起来meterpreterbind_tcp功能与我期望的功能类似,bind_tcp是将接收的tcp流量shellcode写入内存跳转执行,我只需要将跳转执行这一段汇编代码改为输出,就能够获取接收的字符串了。
先用msfvenom生成一个马,

msfvenom -p linux\x64\meterpreter\bind_tcp LPORT=80 -f elf -o nginx

得到的程序反汇编

LOAD:0000000000400078                 push    29h ; ')'
LOAD:000000000040007A                 pop     rax
LOAD:000000000040007B                 cdq                     ; protocol
LOAD:000000000040007C                 push    2
LOAD:000000000040007E                 pop     rdi             ; family
LOAD:000000000040007F                 push    1
LOAD:0000000000400081                 pop     rsi             ; type
LOAD:0000000000400082                 syscall                 ; LINUX - sys_socket
LOAD:0000000000400084                 xchg    rax, rdi        ; fd
LOAD:0000000000400086                 push    rdx
LOAD:0000000000400087                 mov     [rsp+8+var_8], 50000002h
LOAD:000000000040008E                 mov     rsi, rsp        ; backlog
LOAD:0000000000400091                 push    10h
LOAD:0000000000400093                 pop     rdx             ; upeer_addrlen
LOAD:0000000000400094                 push    31h ; '1'
LOAD:0000000000400096                 pop     rax
LOAD:0000000000400097                 syscall                 ; LINUX - sys_bind
LOAD:0000000000400099                 pop     rcx
LOAD:000000000040009A                 push    32h ; '2'
LOAD:000000000040009C                 pop     rax
LOAD:000000000040009D                 syscall                 ; LINUX - sys_listen
LOAD:000000000040009F                 xchg    rax, rsi        ; upeer_sockaddr
LOAD:00000000004000A1                 push    2Bh ; '+'
LOAD:00000000004000A3                 pop     rax
LOAD:00000000004000A4                 syscall                 ; LINUX - sys_accept
LOAD:00000000004000A6                 push    rax
LOAD:00000000004000A7                 push    rsi
LOAD:00000000004000A8                 pop     rdi             ; addr
LOAD:00000000004000A9                 push    9
LOAD:00000000004000AB                 pop     rax
LOAD:00000000004000AC                 cdq
LOAD:00000000004000AD                 mov     dh, 10h
LOAD:00000000004000AF                 mov     rsi, rdx        ; len
LOAD:00000000004000B2                 xor     r9, r9          ; off
LOAD:00000000004000B5                 push    22h ; '"'
LOAD:00000000004000B7                 pop     r10             ; flags
LOAD:00000000004000B9                 mov     dl, 7           ; prot
LOAD:00000000004000BB                 syscall                 ; LINUX - sys_mmap
LOAD:00000000004000BD                 xchg    rax, rsi
LOAD:00000000004000BF                 xchg    rax, rdi
LOAD:00000000004000C1                 pop     rdi
LOAD:00000000004000C2                 syscall                 ; LINUX -
LOAD:00000000004000C4                 jmp     rsi

这里,只需要将末端的jmp rsi指令替换成以下指令,rsi为转跳地址,就是获取的字符串地址,无需修改,由于汇编码长于之前的代码,需要修改0x60处的文件大小。

mov rax,1;
mov rdi,rax;
mov rdx,0x800;
syscall;

试一试

root@ret2shell-91-9018:~# printf "\x7F\x45\x4C\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3E\x00\x01\x00\x00\x00\x78\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x38\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\xE6\x00\x00\x00\x00\x00\x00\x00\x14\x01\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x6A\x29\x58\x99\x6A\x02\x5F\x6A\x01\x5E\x0F\x05\x48\x97\x52\xC7\x04\x24\x02\x00\x00\x50\x48\x89\xE6\x6A\x10\x5A\x6A\x31\x58\x0F\x05\x59\x6A\x32\x58\x0F\x05\x48\x96\x6A\x2B\x58\x0F\x05\x50\x56\x5F\x6A\x09\x58\x99\xB6\x10\x48\x89\xD6\x4D\x31\xC9\x6A\x22\x41\x5A\xB2\x07\x0F\x05\x48\x96\x48\x97\x5F\x0F\x05\x48\xC7\xC7\x01\x00\x00\x00\x48\xC7\xC2\x00\x10\x00\x00\x48\x89\xF8\x0F\x05\x48\xC7\xC0\x3C\x00\x00\x00\x48\x31\xFF\x0F\x05" > /usr/bin/base32
root@ret2shell-91-9018:~# base32
GET /files/0b9b96e8-65d1-431f-a056-923c1cded988 HTTP/1.1
accept: */*
host: 127.0.0.1

root@ret2shell-91-9018:~#

多次尝试,发现只有对端发送的代码,没有flag,再次读题,题目说明“客户”:会不断请求损坏的服务器,如果客户高兴的话,就会给你 flag(以请求参数形式传递);,并且cd 到html目录,发现服务器内存在对应文件,推断需要客户访问到对应的文件内容,才能反馈flag。这个要求代码量极大,用纯纯大汇编写起来非常坐牢,无奈退而求其次,用万能的chatgpt写一段c语言模拟httpserver服务,静态编译完扔回服务器。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PORT 80
#define BUFFER_SIZE 4096
#define WEB_ROOT "/var/www/html"

void handle_client(int client_socket) {
    char buffer[BUFFER_SIZE];
    char method[16], path[256], protocol[16];
    char full_path[BUFFER_SIZE];
    int file_fd;
    ssize_t bytes_read;

    // 读取客户端请求
    bytes_read = read(client_socket, buffer, sizeof(buffer) - 1);
    if (bytes_read <= 0) {
        perror("read");
        close(client_socket);
        return;
    }
    buffer[bytes_read] = '\0';

    // 将请求内容打印到标准输出
    printf("Received request:\n%s\n", buffer);

    // 解析 HTTP 请求行(GET /path HTTP/1.1)
    sscanf(buffer, "%s %s %s", method, path, protocol);

    // 只处理 GET 请求
    if (strcmp(method, "GET") != 0) {
        const char *response = "HTTP/1.1 501 Not Implemented\r\n\r\n";
        write(client_socket, response, strlen(response));
        close(client_socket);
        return;
    }

    // 处理请求路径
    snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, path);

    // 如果请求的是目录,默认返回 index.html
    if (full_path[strlen(full_path) - 1] == '/') {
        strncat(full_path, "index.html", sizeof(full_path) - strlen(full_path) - 1);
    }

    // 打开文件
    file_fd = open(full_path, O_RDONLY);
    if (file_fd < 0) {
        // 文件未找到,返回404
        const char *response = "HTTP/1.1 404 Not Found\r\n\r\n";
        write(client_socket, response, strlen(response));
        close(client_socket);
        return;
    }

    // 发送响应头
    const char *response_header = "HTTP/1.1 200 OK\r\n\r\n";
    write(client_socket, response_header, strlen(response_header));

    // 发送文件内容
    while ((bytes_read = read(file_fd, buffer, sizeof(buffer))) > 0) {
        write(client_socket, buffer, bytes_read);
    }

    // 关闭文件和客户端连接
    close(file_fd);
    close(client_socket);
}

int main() {
    int server_fd, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    // 创建套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址结构
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定套接字到指定的端口
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 开始监听端口
    if (listen(server_fd, 10) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("HTTP server is listening on port %d, serving %s\n", PORT, WEB_ROOT);

    // 主循环,处理客户端连接
    while (1) {
        // 接受客户端连接
        client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
        if (client_socket < 0) {
            perror("accept failed");
            close(server_fd);
            exit(EXIT_FAILURE);
        }

        // 处理客户端请求
        handle_client(client_socket);
    }

    // 关闭服务器套接字
    close(server_fd);
    return 0;
}

编译

gcc httpserver.c --static -o httpserver

由于生成的静态文件过大,一次性写入服务器会崩溃。

需要分段写入到可执行文件内,写出python脚本分割文件。

def file_to_hex_chunks(file_path, chunk_size=100000):
    i = 1
    with open(file_path, 'rb') as file:
        while True:
            # 读取指定大小的块
            chunk = file.read(chunk_size)
            if not chunk:  # 如果读取不到数据则退出循环
                break
            # 将每个字符转为\x格式的十六进制
            hex_chunk = ''.join(f'\\x{byte:02x}' for byte in chunk)
            # 输出结果
            f = open(str(i)+".txt","w")
            if i == 1:
                f.write('printf "'+hex_chunk+'" > /usr/bin/base32')
            else:
                f.write('printf "'+hex_chunk+'" >> /usr/bin/base32')
            f.close()
            i +=1 

# 示例使用
file_path = './httpserver'
file_to_hex_chunks(file_path)

将分段生成的命令依次输入终端,执行base32命令,得到flag。

root@ret2shell-91-9018:~# base32
HTTP server is listening on port 80, serving /var/www/html
Received request:
GET /files/f4e6aaf0-e319-4f07-941c-a01f4e4622f2 HTTP/1.1
accept: */*
host: 127.0.0.1


Received request:
GET /?flag=moectf{thAT5_not+veRY-H@rD-tO_reSCU3,r1ghT?978492} HTTP/1.1
accept: */*
host: 127.0.0.1


Received request:
GET /files/ca577100-1755-42ab-85d5-8d37b62da6e0 HTTP/1.1
accept: */*
host: 127.0.0.1


Received request:
GET /?flag=moectf{thAT5_not+veRY-H@rD-tO_reSCU3,r1ghT?978492} HTTP/1.1
accept: */*
host: 127.0.0.1

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!