pwnablr.kr Todder's Bottle:input

なんかかなり放置していたかつ日本語解説無かった(するまでも無いかもしれないけど)ので。

問題

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

まあプログラムを頑張って下まで通すだけです。

解説

Pythonsshの内部で動かしています(他の人見るとpwntool使っている人もいますが)

Stage1

argvですね。subprocess.callを使うと、中を配列で受け取れます。"\x00"は使えず、代わりに""が使えます。面倒だったのでargv配列を100個用意してから左端を削るチキンプレー。

import subprocess

# Stage 1
arg=["a" for i in range(100)]
arg[ord("A")]=""
arg[ord("B")]="\x20\x0a\x0d"

s=subprocess.call(["/home/input2/input"]+arg[1:])
Stage2

入出力。subprocess.callでは入出力を指定することが出来ます。ただ、ファイルオブジェクトを渡す必要があるみたいで、適当にファイル作って送りましょう。

import subprocess

# Stage 1
arg=["a" for i in range(100)]
arg[ord("A")]=""
arg[ord("B")]="\x20\x0a\x0d"

# Stage 2
with open("stdin.txt","w") as f:
        f.write("\x00\x0a\x00\xff")
with open("stderr.txt","w") as f:
        f.write("\x00\x0a\x02\xff")

s=subprocess.Popen(["/home/input2/input"]+arg[1:],stdin=open("stdin.txt","r"),stderr=open("stderr.txt","r"))
Stage3

envです。osをインポートしましょう。ググれば出ます。

import subprocess
import os

# Stage 1
arg=["a" for i in range(100)]
arg[ord("A")]=""
arg[ord("B")]="\x20\x0a\x0d"

# Stage 2
with open("stdin.txt","w") as f:
        f.write("\x00\x0a\x00\xff")
with open("stderr.txt","w") as f:
        f.write("\x00\x0a\x02\xff")

# Stage 3
os.environ["\xde\xad\xbe\xef"]="\xca\xfe\xba\xbe"

s=subprocess.Popen(["/home/input2/input"]+arg[1:],stdin=open("stdin.txt","r"),stderr=open("stderr.txt","r"))
Stage4

まさかのファイル入出力。さっきやりました。

import subprocess
import os

# Stage 1
arg=["a" for i in range(100)]
arg[ord("A")]=""
arg[ord("B")]="\x20\x0a\x0d"

# Stage 2
with open("stdin.txt","w") as f:
        f.write("\x00\x0a\x00\xff")
with open("stderr.txt","w") as f:
        f.write("\x00\x0a\x02\xff")

# Stage 3
os.environ["\xde\xad\xbe\xef"]="\xca\xfe\xba\xbe"

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

s=subprocess.Popen(["/home/input2/input"]+arg[1:],stdin=open("stdin.txt","r"),stderr=open("stderr.txt","r"))
Stage5

一番難しいのはこれっぽさそう。インターネット通信とPythonといえばこのページを思い出したのは私だけでしょうか。自分だけですね。

ってことで適当にsocketを送ります。IPアドレスはエディタから戻って「ifconfig」するとループバックアドレス(自分自身を指すアドレス)が127.0.0.1となっているのでそれを使います。ポートはargv['C']らしいので適当な数値をつけましょう。

で、そのまま実行しても何も起きません。ここで、今まで使っていたsubprocess.callなのですが、これは並列処理をするものでは無いので、代わりにPopenに変えます。で、これでも恐らく何も起きないです。出力があまりに早いと受け取ってくれないので少し待ちます。

import subprocess
import os
import socket
import time

# Stage 1
arg=["a" for i in range(100)]
arg[ord("A")]=""
arg[ord("B")]="\x20\x0a\x0d"

# Stage 2
with open("stdin.txt","w") as f:
        f.write("\x00\x0a\x00\xff")
with open("stderr.txt","w") as f:
        f.write("\x00\x0a\x02\xff")

# Stage 3
os.environ["\xde\xad\xbe\xef"]="\xca\xfe\xba\xbe"

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

# Stage 5
arg[ord("C")]="41748"

s=subprocess.Popen(["/home/input2/input"]+arg[1:],stdin=open("stdin.txt","r"),stderr=open("stderr.txt","r"))

time.sleep(3)

host="127.0.0.1"
port=41748  # anything is ok if equal to argv["C"]
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((host,port))
client.send("\xde\xad\xbe\xef")

これで5ステージ全て撃破出来ました。

Extra

実はこの後に自明な罠があって、そこに30分ほど躓いてました。flagの呼び出しはsystem("bin/cat flag")にて行われます。pythonの実行は(僕の場合)/tmp/fiordで行われていて、この時当然/tmp/fiord/flagを探します。なのでシンボリックリンクを貼っていなくてはなりません。ただ、ファイルが無くても何もエラーを吐いてくれないので、つらかった。。。

ln -s /home/input2/flag