首页
社区
课程
招聘
[原创]pwnable.kr input 分析
2020-6-20 20:17 18455

[原创]pwnable.kr input 分析

2020-6-20 20:17
18455

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 总结

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


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-6-21 21:14 被王嘟嘟编辑 ,原因:
上传的附件:
收藏
点赞5
打赏
分享
最新回复 (7)
雪    币: 19586
活跃值: (60093)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
Editor 2020-6-21 18:34
2
0
这题有附件吗?若有,麻烦上传一下
雪    币: 1110
活跃值: (544)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
王嘟嘟 2020-6-21 21:15
3
0
Editor 这题有附件吗?若有,麻烦上传一下
有的,已上传
雪    币: 1535
活跃值: (695)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
开花的水管 2020-6-24 15:24
4
0
感谢分享~
雪    币: 22
活跃值: (23)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
CharesFang 2020-11-13 14:13
5
0
想问问楼主题目附件是怎么拿下来的啊,还是说自己把源码弄下来编译呢
雪    币: 481
活跃值: (2163)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
Zard_ 2021-1-5 14:34
6
0
CharesFang 想问问楼主题目附件是怎么拿下来的啊,还是说自己把源码弄下来编译呢[em_23]
scp 能下载下来   scp -P 2222 -r  input2@pwnable.kr:/home/input2 /tmp/input2  密码guest
雪    币: 22
活跃值: (23)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
CharesFang 2021-1-19 14:27
7
0
Zard_ scp 能下载下来 scp -P 2222 -r input2@pwnable.kr:/home/input2 /tmp/input2 密码guest
感谢大佬啦!
雪    币: 228
活跃值: (577)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
KSr01dO 2021-9-6 18:04
8
1

<这里原本是一个很傻的问题,现在删除了>

最后于 2021-10-4 21:25 被KSr01dO编辑 ,原因:
游客
登录 | 注册 方可回帖
返回