首页
论坛
课程
招聘
[原创]pwnable.kr input 分析
2020-6-20 20:17 3516

[原创]pwnable.kr input 分析

2020-6-20 20:17
3516

0x00 前言

最近做htb的时候遇到了一个bof的题,但是由于很久之前学过的东西有点想不起,所以就找到了一个pwn的平台的练习。做了几个发现实际难度并不是很大,直到做到这个input的时候,感觉难度一下子就上来了(可能是对于linux理解太菜了,昨天看了一天),所以想进行一下详细的分析,希望可以把这道题所有的知识点都吃透。

 

本篇文章想从目标c语言开始分析,分析对应的汇编,以及到python和c两种语言的poc编写。

0x01 题目环境

1.题目

图片描述

2.目标c语言

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

0x02 分析正文

发现c中需要满足5个要求,方能得到flag,所以我们也分成5个部分进行分析

1.argv

1.1 目标c语言

        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");

这里c语言描述的是需要至少100个参数,并且argv['A']得是\x00
也就是终止符,argv['B']为\x20\x0a\x0d.

1.2 ida对比学习

由于直接写在ida中比较方便也比较清楚,所以就直接标注在ida里了。
图片描述

1.3 python poc

import subprocess

#argv
input_path="./input"
argv=[]
argv.append(input_path)
for i in range(1,100):
    argv.append('A')
argv[ord('A')]=""
argv[ord('B')]="\x20\x0a\x0d"
print(argv[ord('B')])
subprocess.Popen(argv)

效果如下:
图片描述

1.4 c语言 poc

#include <stdio.h>
#include <unistd.h>
int main(){
    char *argv[101] = {"/root/Desktop/tools/bof/pwnablekr/input/input", [1 ... 99] = "A", NULL};
        argv['A'] = "\x00";
        argv['B'] = "\x20\x0a\x0d";
    char *envp[]={0,NULL};
    execve("/root/Desktop/tools/bof/pwnablekr/input/input",argv,envp);    
}

效果如下:
图片描述

2.stdio

2.1 目标c语言

    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");

这里作为c语言来说理解还是比较简单的,第一个read,读取标准输入,就是从键盘上获取一个标准输入,第二个read,获取标准错误输出,然后进行对比。真正的难点就是去如何构造这个标准错误输出了,输入还是比较好构造的。

2.2 ida对比学习

图片描述
感觉看了源码之后发现ida看起来也很清楚了

2.3 python poc

import subprocess
import os,sys
#argv
input_path="./input"
argv=[]
argv.append(input_path)
for i in range(1,100):
    argv.append('A')
argv[ord('A')]=""
argv[ord('B')]="\x20\x0a\x0d"
print(argv[ord('B')])
r,w=os.pipe()
r_e,w_e=os.pipe()
os.write(w, "\x00\x0a\x00\xff")
os.write(w_e, "\x00\x0a\x02\xff")
subprocess.Popen(argv,stdin=r,stderr=r_e)

相对于c来说,还是觉的解决问题,python更快一点。
效果:
图片描述

2.4 c语言 poc

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(){
    char *argv[101] = {"/root/Desktop/tools/bof/pwnablekr/input/input", [1 ... 99] = "A", NULL};
        argv['A'] = "\x00";
        argv['B'] = "\x20\x0a\x0d";
    char *envp[]={0,NULL};


    int fd_0[2];
    int fd_2[2];

    pid_t child;

    if (pipe(fd_0)<0||pipe(fd_2)<0)
    {
        perror("error");
    }

    write(fd_0[1],"\x00\x0a\x00\xff",4);
    write(fd_0[1],"\x00\x0a\x02\xff",4);
    dup2(fd_0[0],0);
    dup2(fd_0[0],2);
    execve("/root/Desktop/tools/bof/pwnablekr/input/input",argv,envp);            
}

效果如下:
图片描述

 

这里还是要说一下的,不然的话就没什么意义了。

 

涉及到知识点:
pipe,linux下的管道问题,这个管道有俩,一个是输入,一个是输出,我们需要做的就是控制这个输出的内容。
根据查到的资料,可以利用先fock一个子进程,在子进程进行输入,然后主进程复制文件流,拿到输出。此时就满足了题目的要求。但是之后发现不需要fock子进程,直接写也是可以的,没什么问题。
最终的实质就是利用pipe构造了标准错误输出。

3.env

3.1 目标c

if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
    printf("Stage 3 clear!\n");

这里的重点就是getenv,查了一下详细的说明如下。
该函数返回一个以 null 结尾的字符串,该字符串为被请求环境变量的值。如果该环境变量不存在,则返回 NULL。
我们的目标就是要构造一个环境变量

3.2 ida对比学习

图片描述

3.3 python poc

envs={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
subprocess.Popen(argv,stdin=r,stderr=r_e,env=envs)

图片描述

3.4 c语言 poc

char *envp[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
    execve("/root/Desktop/tools/bof/pwnablekr/input/input",argv,envp);

c语言也是直接设置就可以。

 

图片描述

4.file

4.1 目标c语言

    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");

这里就是读取一个文件,然后读取的内容为\x00\x00\x00\x00即可。

4.2 ida对比学习

图片描述

4.3 python poc

with open("\x0a","w") as f:
    f.write("\x00\x00\x00\x00")

图片描述

4.4 c语言 poc

FILE* fp = fopen("\x0a", "w");    
    fwrite(buf, sizeof(buf) , 1, fp );
    fclose(fp);

图片描述

5 network

5.1 目标c语言

    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");

这里的socket就是开了一个argv['C']的端口的socket,只要传进去一个 \xde\xad\xbe\xef就可以了。

5.2 ida 对比分析

图片描述

5.3 python poc

s = socket.socket()
port = 24444
s.connect(("127.0.0.1", port))
s.send("\xde\xad\xbe\xef")

图片描述

5.4 c语言 poc

ruct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;  
    servaddr.sin_port = htons(1111);  
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
    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);
    }    
    strcpy(bufs, "\xde\xad\xbe\xef");
    len = strlen(bufs);
    send(sockfd, bufs, len, 0);
    close(sockfd); 

    return 0;

我用c语言写的时候写的有点问题,当程序监听的时候就不会出现socket连接的过程。所以就单独开了一个。
图片描述

0x03 总结

其实在做这道题的时候就一直纠结,到底要不要认真的看一下,在没有仔细研究的时候觉得这个东西很难,但是一旦弄懂了之后就会豁然开朗,感谢老婆的安慰和陪伴。


[公告]看雪技术峰会,技术大牛大型线下交流见面会,2020年10月23日 上海浦东喜来登由由大酒店!

最后于 2020-6-21 21:14 被王嘟嘟编辑 ,原因:
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (3)
雪    币: 5131
活跃值: 活跃值 (6148)
能力值: (RANK:65 )
在线值:
发帖
回帖
粉丝
Editor 活跃值 2020-6-21 18:34
2
0
这题有附件吗?若有,麻烦上传一下
雪    币: 1038
活跃值: 活跃值 (302)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
王嘟嘟 活跃值 2020-6-21 21:15
3
0
Editor 这题有附件吗?若有,麻烦上传一下
有的,已上传
雪    币: 1271
活跃值: 活跃值 (279)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
开花的水管 活跃值 2020-6-24 15:24
4
0
感谢分享~
游客
登录 | 注册 方可回帖
返回