【Writeup】Pwnable.kr 0x07 input

0x07 input

题目描述

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

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

题目代码

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

解题分析

这道题是一道很综合的题,主要是考察linux下,argv,env,stdio,file,socket等的运用,查询了大量资料,也学习到了很多知识,主要参考了http://blog.csdn.net/SmalOSnail/article/details/53048109的writeup,在这里,我自己主要是分析一下其中的脚本思路吧,再详细得记录每条知识点,当作学习,同时要感谢分享的writeup。
在这里,先附上python的解题脚本:

import os
import socket
import time
import subprocess

stdinr, stdinw = os.pipe()
stderrr, stderrw = os.pipe()

args = list("A"*99)
args[ord('A') - 1] = ""
args[ord('B') - 1] = "\x20\x0a\x0d"
args[ord("C") - 1] = "8888"

os.write(stdinw, "\x00\x0a\x00\xff")
os.write(stderrw, "\x00\x0a\x02\xff")

environ = {"\xde\xad\xbe\xef" : "\xca\xfe\xba\xbe"}

f = open("\x0a" , "wb")
f.write("\x00"*4)
f.close()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

pro = subprocess.Popen(["/home/input2/input"]+args, stdin=stdinr,stderr=stderrr,env=environ)

time.sleep(2)
s.connect(("127.0.0.1", 8888))
s.send("\xde\xad\xbe\xef")
s.close()

这里,我们分步详解:

part 1

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

这里呢,首先是要求参数要等于100个,并且argv['A'],argv['B']分别要符合要求,那么,这里的A和B其实分别是其ascii码作为索引值,即参数第64个要为"\x00"而参数第65个要为"\x20\x0a\x0d",这样就完成了第一部分。对于第一部分,在python脚本中是先用99个A初始化,为什么用99个A而不是100个呢,可以看到,在最后的Popen()函数中,其实是将第100个参数加上的,至于为什么是/home/input2/input呢?这是因为,对于命令行参数,argc[0]通常是脚本名的。

part 2

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

对于第二部分的解题思路,writeup的作者主要采用os的pipe()函数,所以我们需要了解一下os的pipe函数:os.pipe()用于创建一个管道,返回一对文件描述符(r,w)分别为读和写。这段c代码中,还有一个需要注意的是read函数,read函数作用是从文件描述如中读取size大小的元素,并送入buffer中,这里我们看到文件描述符是0与2,回想一下pwnable第一关fd时,我们便知道这里的0其实是stdin,2是stderr,所以在python脚本的最初,我们声明了两个pipe管道,stdinr,stdinw和stderrr,stderrw,其作用就是向stdin和stderr写入题目需要的字符串,而如果直接用stdin和stderr是无法直接写入的,所以利用管道的双向读写功能来完成这一部分。

part 3

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

这里呢,重点是getenv()这个函数,这个函数的功能是:返回一给定的环境变量值,环境变量名可大写或小写。如果指定的变量在环境中未定义,则返回一空串。这里呢,是要让"\xde\xad\xbe\xef"的环境变量等于"\xca\xfe\xba\xbe",那么在python脚本中就可以通过定义一个字典,然后让"\xde\xad\xbe\xef"对应"\xca\xfe\xba\xbe",在Popen函数的时候,将这个字典当作env的参数就好了。

part 4

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

接下来是第四部分,重点解读一下fread函数,该函数原型 size_t fread(void *buffer, size_t size, size_t count FILE *stream),
就是读取stream流中count个元素,每个元素size大小,读取到buffer中,在这里的c代码中,是从fp中读取一个元素,4字节大小到buf中,因此,只要在python脚本中写入4个\x00就好了。

part 5

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

最后一部分,第五部分,前面都是建立socket连接,只要成功建立socket连接就不会报错,后面recv(cd, buf, 4, 0)这里,cd是之前accept返回的套接字描述符,这段socket代码是一个服务端的代码,accept返回的这个cd可以想象成服务端与客户端建立连接的一个钥匙,那么这个recv其实就很好理解了,就是服务端接受的4字节的数据并且放到buf中,第四个参数0是flag一个标志位,一般为0,这里可以不用管他。然后是一个memcmp函数,这个函数就是比较buf与"\xde\xad\xbe\xef"的前4字节的内容是否相等,相等则返回0,因此这部分就是向服务端发送"\xde\xad\xbe\xef"这个字符串即可通过。

part 6

总共有5部分,那么我为什么要写part 6呢?这是因为这道题有几个坑,首先/home/input这个目录下没有写的权限,所以需要把脚本放在/tmp去执行,而且part 4创建文件的操作也需要在tmp下进行,但是读取flag的system("/bin/cat flag")中的/bin/cat flag是使用相对路径的,是无法读取到flag的,所以这里使用了软连接的方法,那么在这里我们可以学习一下linux下的ln链接命令:

ln src dest
这里需要注意的是,第一,ln命令会保持每一处链接文件的同步性,也就是说,不论你改动了哪一处,其它的文件都会发生相同的变化;第二,ln的链接又软链接 和硬链接两种,软链接就是ln -s str dest,它只会在你选定的位置上生成一个文件的镜像,不会占用磁盘空间,硬链接ln src dest,没有参数-s, 它会在你选定的位置上生成一个和源文件大小相同的文件,无论是软链接还是硬链接,文件都保持同步变化。
如果我们用ls察看一个目录时,会发现有的文件后面有一个@的符号,那就是一个用ln命令生成的文件,用ls -l命令去察看,就可以看到显示的link的路径了。 

所以这里就将flag通过软链接的方式在/tmp目录下创建一个副本,这样相对路径的问题就可以解决了。
但是解决了相对路径的问题后运行还是不能弹出flag,发现是因为/tmp目录下没有读的权限,但是有写的权限,就有建了一个input目录,最后就可以弹出flag了。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容