首页
社区
课程
招聘
[原创]pwnable.kr - input2 & pkexec(CVE-2021-4034)漏洞
发表于: 2023-4-11 23:42 12327

[原创]pwnable.kr - input2 & pkexec(CVE-2021-4034)漏洞

2023-4-11 23:42
12327

分为5个stage,需要依次解决

命令行执行程序调用的是execve系统调用,其函数原型如下所示,第一个参数是运行程序文件名,第二个参数是argv[],第三个参数是envp[].同时,检测的是main函数的argcargv.

main函数的第一个参数argc表示的是命令行参数个数。argv则是命令行参数 argv[0]= filename

翻译一下:

read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.

the memcmp() function compares the first n bytes (each interpreted as unsigned char) of the memory areas s1 and s2.

即:

fd = 0 很好解决,stdin,直接输入即可;fd = 2的输入不是很好解决,但是pwntools提供了对应的接口,如果没有pwntools,应该怎么实现?

因此有:

上述execve函数已经讲过,因此直接添加即可:

基本文件流操作:

需要注意的是,能写入的目录只有/tmp,如何才能读取到想要的文件是需要考虑一下的。

需要注意的是,最后一步由于远程防火墙问题,pwntools是连不上去的,只有提交到本地之后才可以,另外,由于我们的目录在/tmp,而读取的flag并不在,需要想办法获取对应的内容,一个好的方法是软连接。

本道题包含的知识点比较多文件IO,进程通信,网络通信均有涉及,如果对此不是很了解建议翻阅CSAPP相关章节。

另在整理本题时想到一个比较简单的漏洞和本题有一定相似性,借此把之前的简单分析也放出来,可以看看真实漏洞和习题的异同。



结论:

问题:

如果执行 argc 1 2 3 4 5
得到结果是:

如果执行execve("./argc", NULL, NULL)得到的结果是:

如果将argc.cfor (int i = 0; i < argc; i++)改为 for (int i = 0; i < 8; i++) 执行argc 1 2 3 4 5结果是

后面的结果是什么?

如果设定argv和envp参数,执行execve.c得到的结果是:

execve系统调用中连续的参数为argc argv 以及 envp

如下图所示:

结论:
如果存在应用,直接使用argv[1]而未对参数进行校验,那么如果此时使用execve执行命令 传递参数为NULL,*那么此时:

可以查看一篇slide

小结:

如果普通用户执行具有suid权限应用比如passwd,那么其efftive id = root,real id = user. 即具有suid权限应用可以被普通用户启动,并且运行权限会被修改为root。
那么如果想办法使用环境变量启动一个bash,这个bash就能具有root权限,可利用环境变量有哪些?
LD_PRELOAD
看起来很简单?直接使用LD_PRELOAD是否可行?

NO

结论:环境变量中,不安全的环境变量不会被引入

那么如何把shell写进入呢?

漏洞发现者提出了一种巧妙的方法

g_printerr()会调用glibc的iconv_open()函数将UTF-8格式的信息转为其他格式。

为了转换格式,iconv_open()会调用一个共享库函数。

之后会调用对应文件中的gconv()函数和gconv_init()函数。

但是通过环境变量GCONV_PATH 可以强制指定iconv_open()读取某一个特定的共享库

问题:GCONV_PATH是不安全函数,不会被加载到程序中。

pkexec可以将GCONV_PATH环境变量重新载入。 为什么?

到目前位置所知道的信息:

到这里,基本上就已经明确了所有步骤:

回到源码,分析调用链。

假设存在环境变量 PATH=name,如果name存在,并且name目录中有一个名为path的文件,传递给envp[0]。
如果PATH=name=.并且有一个目录name=.,此目录中有一个shell.so:.,那么这个shell.so:.会被传递给argv[1]->envp[0]

如果在开始,使用execve()执行pkexec,并且传递第一个参数为NULL,那么 在第二步,一开始读取的argv[1] 实际上是envp[0].
并且后续会找到一个可执行文件传递给s,继而传递给argv[1],注意argv[1]指向的实际上是第一个环境变量。
通过这种方式,就能修改掉第一个环境变量。

构造环境变量如上图,按POC所叙述,

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
int main(int argc, char* argv[], char* envp[]){
    printf("Welcome to pwnable.kr\n");
    printf("Let's see if you know how to give input to program\n");
    printf("Just give me correct inputs then you will get the flag :)\n");
 
    // argv
    if(argc != 100) return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n");
 
    // stdio
    char buf[4];
    read(0, buf, 4);
    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
    read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
    printf("Stage 2 clear!\n");
 
    // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");
 
    // file
    FILE* fp = fopen("\x0a", "r");
    if(!fp) return 0;
    if( fread(buf, 4, 1, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n");
 
    // network
    int sd, cd;
    struct sockaddr_in saddr, caddr;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons( atoi(argv['C']) );
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }
    listen(sd, 1);
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 4, 0) != 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
    printf("Stage 5 clear!\n");
 
    // here's your flag
    system("/bin/cat flag");
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
int main(int argc, char* argv[], char* envp[]){
    printf("Welcome to pwnable.kr\n");
    printf("Let's see if you know how to give input to program\n");
    printf("Just give me correct inputs then you will get the flag :)\n");
 
    // argv
    if(argc != 100) return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n");
 
    // stdio
    char buf[4];
    read(0, buf, 4);
    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
    read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
    printf("Stage 2 clear!\n");
 
    // env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");
 
    // file
    FILE* fp = fopen("\x0a", "r");
    if(!fp) return 0;
    if( fread(buf, 4, 1, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n");
 
    // network
    int sd, cd;
    struct sockaddr_in saddr, caddr;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons( atoi(argv['C']) );
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }
    listen(sd, 1);
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 4, 0) != 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
    printf("Stage 5 clear!\n");
 
    // here's your flag
    system("/bin/cat flag");
    return 0;
}
 
int execve(const char *filename, char *const argv[],
                  char *const envp[]);
 
int main( int argc, char *argv[ ] ) { /* */ }
int execve(const char *filename, char *const argv[],
                  char *const envp[]);
 
int main( int argc, char *argv[ ] ) { /* */ }
// argv
    if(argc != 100) return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n");
// argv
    if(argc != 100) return 0;
    if(strcmp(argv['A'],"\x00")) return 0;
    if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
    printf("Stage 1 clear!\n");
char *argv[101] = {"/home/input2/input", [1 ... 99] = "A", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
 
execve("/home/input2/input",argv,NULL);
char *argv[101] = {"/home/input2/input", [1 ... 99] = "A", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
 
execve("/home/input2/input",argv,NULL);
// stdio
    char buf[4];
    read(0, buf, 4);
    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
    read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
    printf("Stage 2 clear!\n");
// stdio
    char buf[4];
    read(0, buf, 4);
    if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
    read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
    printf("Stage 2 clear!\n");
ssize_t read(int fd, void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
int memcmp(const void *s1, const void *s2, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
 
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
 
int main() {
    int fd1 = open("./stdin", O_RDONLY);
    int fd2 = open("./stderr", O_RDONLY);
 
    dup2(fd1, 0); // 将文件描述符fd1重定向到标准输入
    dup2(fd2, 2); // 将文件描述符fd2重定向到标准错误
 
    close(fd1);
    close(fd2);
 
    char *argv[101] = {"/home/input2/input", [1 ... 99] = "A", NULL};
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";
 
    execve("/home/input2/input",argv,NULL);
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
 
int main() {
    int fd1 = open("./stdin", O_RDONLY);
    int fd2 = open("./stderr", O_RDONLY);
 
    dup2(fd1, 0); // 将文件描述符fd1重定向到标准输入
    dup2(fd2, 2); // 将文件描述符fd2重定向到标准错误
 
    close(fd1);
    close(fd2);
 
    char *argv[101] = {"/home/input2/input", [1 ... 99] = "A", NULL};
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";
 
    execve("/home/input2/input",argv,NULL);
    return 0;
}
// env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");
// env
    if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");
char *env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
execve("/home/input/input",argv,env);
char *env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
execve("/home/input/input",argv,env);
// file
    FILE* fp = fopen("\x0a", "r");
    if(!fp) return 0;
    if( fread(buf, 4, 1, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n");
// file
    FILE* fp = fopen("\x0a", "r");
    if(!fp) return 0;
    if( fread(buf, 4, 1, fp)!=1 ) return 0;
    if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
    fclose(fp);
    printf("Stage 4 clear!\n");
FILE* fp = fopen("\x0a","w");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
FILE* fp = fopen("\x0a","w");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
// network
    int sd, cd;
    struct sockaddr_in saddr, caddr;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons( atoi(argv['C']) );
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }
    listen(sd, 1);
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 4, 0) != 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
    printf("Stage 5 clear!\n");
// network
    int sd, cd;
    struct sockaddr_in saddr, caddr;
    sd = socket(AF_INET, SOCK_STREAM, 0);
    if(sd == -1){
        printf("socket error, tell admin\n");
        return 0;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_port = htons( atoi(argv['C']) );
    if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
        printf("bind error, use another port\n");
            return 1;
    }
    listen(sd, 1);
    int c = sizeof(struct sockaddr_in);
    cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
    if(cd < 0){
        printf("accept error, tell admin\n");
        return 0;
    }
    if( recv(cd, buf, 4, 0) != 4 ) return 0;
    if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
    printf("Stage 5 clear!\n");
argv['C'] = "10010";
 
int sockfd;
struct sockaddr_in server;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if ( sockfd < 0){
    perror("Cannot create the socket");
    exit(1);
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("pwnable.kr");
server.sin_port = htons(55555);
if ( connect(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0 ){
    perror("Problem connecting");
    exit(1);
}
char buf[4] = "\xde\xad\xbe\xef";
write(sockfd,buf,4);
close(sockfd);
argv['C'] = "10010";
 
int sockfd;
struct sockaddr_in server;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if ( sockfd < 0){
    perror("Cannot create the socket");
    exit(1);
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("pwnable.kr");
server.sin_port = htons(55555);
if ( connect(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0 ){
    perror("Problem connecting");
    exit(1);
}
char buf[4] = "\xde\xad\xbe\xef";
write(sockfd,buf,4);
close(sockfd);
 
 
/* argc.c */
 
#include <stdio.h>
#include <unistd.h>
int main (int argc, char **argv) {
    printf("argc: %d\n", argc);
    for (int i = 0; i < argc; i++)
    {
        if (argv[i] != NULL)
        {
            printf("argv[%d]: %s\n", i, argv[i]);
        } else {
            printf("argv[%d]: NULL\n", i);
        }
    }
    return 0;
}
/* argc.c */
 
#include <stdio.h>
#include <unistd.h>
int main (int argc, char **argv) {
    printf("argc: %d\n", argc);
    for (int i = 0; i < argc; i++)
    {
        if (argv[i] != NULL)
        {
            printf("argv[%d]: %s\n", i, argv[i]);
        } else {
            printf("argv[%d]: NULL\n", i);
        }
    }
    return 0;
}
{~/pkexec-CVE-2021-4034} greetings, earthling [33.059kb]$ ☞ ./argc 1 2 3 4 5
argc: 6
argv: ./argc
argv: 1
argv: 2
argv: 3
argv: 4
argv: 5

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-4-12 09:56 被安和桥南编辑 ,原因: 找了下之前的参考文献
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//