pwnable.kr [Toddler's Bottle] - input

Mom? how can I pass my input to a computer program?

ssh input2@pwnable.kr -p2222 (pw:guest)

这题流程相对较长,考查Linux编程的基本功(笔者做到这题不禁感叹自己基本功还是欠了不少火候)。
在一开始,尝试写Python脚本去完成验证,但stage 2关于stdio的验证却苦无思路。
这里感谢werew在他的writeup中提供的解决思路,这才豁然开朗。
参考链接:https://werewblog.wordpress.com/2016/01/11/pwnable-kr-input/

关于file descriptor maping的内容我另外做了整理,
详见 UNIX 下 C 实现 Pipe Descriptors 映射

先放上题目源码:

/* ssh input2@pwnable.kr -p2222 (pw:guest) */

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;
}

一共分为五个验证步骤,先一个个来看。

Stage 1 argv

第一步比较常规,argv需要传递100个参数,即实际为argv[101](argv[0]指向执行的路径,argv[100]为NULL)。

    /* stage 1 */
    char *argv[101] = {0};
    for(int i = 1; i<100; ++i)
        argv[i] = "a";
    argv[0] = "/home/input2/input";
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";
    argv[100] = NULL;

Stage 2 stdio

可以根据上面的链接查看我整理的关于file descriptor maping的帖子,个中内容不再赘述。主要原理就是利用fork()创建一个当前进程的复刻,父进程和子进程分开进行两个操作:使用dup2()函数将管道重定向,将所需的内容写入相应的pipe。按题设要求我们需要重定向0-stdin和2-stderr。

然后是关于execve,这里使用该函数创建input进程:
在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。

  • 头文件: unistd.h
  • 函数定义: int execve(const char *filename, char *const argv[ ], char *const envp[ ]);
  • 返回值: 函数执行成功时没有返回值,执行失败时的返回值为-1.
  • 函数说明: execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
    /* stage 2 */
    int pipe_stdin[2] = {-1, -1};
    int pipe_stderr[2] = {-1, -1};
    pid_t pid_child;
    if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 )
    {
        perror("Cannot create the pipe.");
        exit(1);
    }

    #define STDIN_READ   pipe_stdin[0]
    #define STDIN_WRITE  pipe_stdin[1]
    #define STDERR_READ  pipe_stderr[0]
    #define STDERR_WRITE pipe_stderr[1]
    if ( ( pid_child = fork() ) < 0 )   // do not forget the ()!
    {
        perror("Cannot create fork child.");
        exit(1);
    }

    if( pid_child == 0 )
    {
        /* child proc */
        sleep(1); //wait to pipe link 0,2
        close(STDIN_READ);
        close(STDERR_READ);
        write(STDIN_WRITE, "\x00\x0a\x00\xff", 4);
        write(STDERR_WRITE, "\x00\x0a\x02\xff", 4);
    }
    else
    {
        /* parent proc */
        close(STDIN_WRITE);
        close(STDERR_WRITE);
        dup2(STDIN_READ, 0);  //dup to 0-stdin
        dup2(STDERR_READ, 2); //dup to 2-stderr
        printf("start execve input.\n");
        execve("/home/input2/input", argv, envp);  //envp see stage 3
        perror("Fail to execute the program");
        exit(1);
    }
    printf("pipe link.\n");

Stage 3 envp

getenv("\xde\xad\xbe\xef")意为查看名为"\xde\xad\xbe\xef"的环境变量的值,这里我们只需传递一个环境变量,名为"\xde\xad\xbe\xef",值为"\xca\xfe\xba\xbe"即可。

/* stage 3 */
char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};

Stage 4 file

按要求创建文件写入内容即可。需要注意的是对于文件的操作需要在execve函数被调用之前完成,因为input进程不会等待当前进行对该文件的创建和写入,需要保证在执行验证之前完成文件的构造。

    /* stage 4 */  
    // ! : file open before execve , or the check will fail 
    FILE *fp = fopen("\x0a", "wb"); // wb,w are similar in linux but differ in win
    if(!fp)                         //see \x0d\x0a in win and \x0a in linux
    {
        perror("Cannot open file.");
        exit(1);
    }
    printf("open file success.\n");
    fwrite("\x00\x00\x00\x00", 4, 1, fp);
    fclose(fp);

Stage 5 network

通过socket完成linux下进程间的通信,和在windows下基本类似。
具体可见我关于windows下的socket通信的简单实例。http://blog.csdn.net/qq_19550513/article/details/54965653
唯一区别在于windows下需要额外对winsock信息 WSAData 利用 WSAStartup() 函数进行初始化操作。

    /* stage 5 */
    sleep(2); // wait the server start
    int sockfd;
    char buf[10] = {0}; // buf to be sent
    int len;            // len of avail buf
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;  
    servaddr.sin_port = htons(9999);  // port in argv['C'] 
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local
    if( (sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )  
    {  
        perror("socket error.");  
        exit(1);  
    }  
    if ( connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0 )
    {
        perror("connect error.");
        exit(1);
        }
    printf("socket connect.\n");
    strcpy(buf, "\xde\xad\xbe\xef");
    len = strlen(buf);
    send(sockfd, buf, len, 0);
    close(sockfd);  

Final Stage

整合后,ssh登录后在/tmp目录下我们自己的目录,利用vi写入c文件,再用gcc编译生成可执行文件。
到这里还没有结束,因为只有在当前目录guest才有写的权限,而flag路径为/home/input2/flag。
这里我们需要一个soft link指向flag文件,在当前目录 ln -s /home/input2/flag flag 即可。注意不能创建hard link,因为guest对flag文件本身是没有读写权限的。
c文件内容如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> 
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main()
{
    /* stage 1 */
    char *argv[101] = {0};
    for(int i = 1; i<100; ++i)
        argv[i] = "a";
    argv[0] = "/home/input2/input";
    argv['A'] = "\x00";
    argv['B'] = "\x20\x0a\x0d";
    argv['C'] = "9999"; //server port
    argv[100] = NULL;

    /* stage 3 */
    char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};

    /* stage 4 */  // ! : file open before execve , or the check will fail 
    FILE *fp = fopen("\x0a", "wb"); // wb,w are similar in linux but differ in win
    if(!fp)                         //see \x0d\x0a in win and \x0a in linux
    {
        perror("Cannot open file.");
        exit(1);
    }
    printf("open file success.\n");
    fwrite("\x00\x00\x00\x00", 4, 1, fp);
    fclose(fp);
    
    /* stage 2 */
    int pipe_stdin[2] = {-1, -1};
    int pipe_stderr[2] = {-1, -1};
    pid_t pid_child;
    if ( pipe(pipe_stdin) < 0 || pipe(pipe_stderr) < 0 )
    {
        perror("Cannot create the pipe.");
        exit(1);
    }

    #define STDIN_READ   pipe_stdin[0]
    #define STDIN_WRITE  pipe_stdin[1]
    #define STDERR_READ  pipe_stderr[0]
    #define STDERR_WRITE pipe_stderr[1]
    if ( ( pid_child = fork() ) < 0 )   // do not forget the ()!
    {
        perror("Cannot create fork child.");
        exit(1);
    }

    if( pid_child == 0 )
    {
        /*child proc*/
        sleep(1); //wait to pipe link 0,2
        close(STDIN_READ);
        close(STDERR_READ);
        write(STDIN_WRITE, "\x00\x0a\x00\xff", 4);
        write(STDERR_WRITE, "\x00\x0a\x02\xff", 4);
    }
    else
    {
        /*parent proc*/
        close(STDIN_WRITE);
        close(STDERR_WRITE);
        dup2(STDIN_READ, 0);  //dup to 0-stdin
        dup2(STDERR_READ, 2); //dup to 2-stderr
        printf("start execve input.\n");
        execve("/home/input2/input", argv, envp);
            perror("Fail to execute the program");
            exit(1);
    }
    printf("pipe link.\n");

    /* stage 5 */
    sleep(2); // wait the server start
    int sockfd;
    char buf[10] = {0}; // buf to be sent
    int len;            // len of avail buf
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;  
    servaddr.sin_port = htons(9999);  // port in argv['C'] 
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //local
    if( (sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )  
    {  
        perror("socket error.");  
        exit(1);  
    }  
    if ( connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0 )
    {
        perror("connect error.");
        exit(1);
        }
    printf("socket connect.\n");
    strcpy(buf, "\xde\xad\xbe\xef");
    len = strlen(buf);
    send(sockfd, buf, len, 0);
    close(sockfd);  

    return 0;
}

最后的运行情况如下:

input2@ubuntu:/tmp/umiade$ ./setinput 
open file success.
start execve input.
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
pipe link.
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
socket connect.
Stage 5 clear!
Mommy! I learned how to pass various input in Linux :)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,110评论 2 34
  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,752评论 0 27
  • Linux 进程管理与程序开发 进程是Linux事务管理的基本单元,所有的进程均拥有自己独立的处理环境和系统资源,...
    JamesPeng阅读 2,442评论 1 14
  • 过去中诊一直讲“司外揣内”,现在我们有条件、有能力直观的看到,是否有必要继续“揣”呢?为什么还要“揣”呢!——内镜...
    听听益生阅读 235评论 0 0
  • 在看这本书之前,很庆幸自己认识到了自己,了解了自己,说明我还是有得救。五年十年二十年乃至更久,积习一定要改。就像俞...
    我便如此阅读 267评论 0 0