pwnable.kr(7)(2) ~input~

前回の続きです。

ステージ 2 からやっていきます。

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

とりあえずコード貼っておく。 ステージ2は標準入出力周り。 単純にsys.stdin.writeとsys.stderr.writeが使えなかった。結局パイプを使う方法がいいみたいだ。forkでexecだと、stdinとstderrをパイプに接続できないので、subprocessのプロセスオープンで対応。

    stdinr, stdinw = os.pipe()
    stderrr, stderrw = os.pipe()
    os.write(stdinw, "\x00\x0a\x00\xff")
    os.write(stderrw, "\x00\x0a\x02\xff")

パイプを作って書き込む。シェルからかくとヌル文字で区切られてしまうので書き込むことができないようだ。低水準は奥が深いなぁ。

ステージ3は唯一書き込み可能な /tmpディレクトリで行わなければならない。chdirをすることを最初考えて進めていたが、あとでダメだということがわかった(これめちゃめちゃ重要だった)なので、/tmpで自分のディレクトリを作って、その中で実行というのが正解みたいだ。ステージ3、4、5は初見で突破できた。以下ステージ3、4、5の

    f = open("\x0a","w")
    f.write("\x00\x00\x00\x00")
    f.close()
    args[ord("C")-1] = '1234'
    subprocess.Popen(["/home/input2/input"]+args,stdin=stdinr,stderr=stderrr)
    time.sleep(1)
    sock = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
    sock.connect(('127.0.0.1',1234))
    sock.sendall(b'\xde\xad\xbe\xef')
    sock.close()

これで、ステージ 5までは突破できた。最後のsystemが実行しても何も吐き出さないので困った。ここで、chdirで/tmpに移行したことがダメだということがわかった。inputプログラムを実行後はcwdを動かせないので、どうやってもflagファイルがあるディレクトリに戻れず、最後のsystemが実行されても何も起こらない。

調べたらここはシンボリックリンクを使うみたいだった。ファイルが保存できるのは、/tmp以下なので、ここで、”実行する場所は/tmp以下でやら”なければならないことがわかった。シンボリックリンクを作成することで実行したディレクトリで、flagを開くというギミックだった。

以下、/tmp内に作った作業用ディレクトリで実行するスクリプト(解答)

import os
import subprocess
import socket
import time


def main():
    os.system("ln -s /home/input2/flag flag")
    os.putenv("\xde\xad\xbe\xef", "\xca\xfe\xba\xbe")
    args = ["0"] * 99
    args[ord("A")-1] = ""
    args[ord("B")-1] = "\x20\x0a\x0d"
    stdinr, stdinw = os.pipe()
    stderrr, stderrw = os.pipe()
    os.write(stdinw, "\x00\x0a\x00\xff")
    os.write(stderrw, "\x00\x0a\x02\xff")
    f = open("\x0a","w")
    f.write("\x00\x00\x00\x00")
    f.close()
    args[ord("C")-1] = '1234'
    subprocess.Popen(["/home/input2/input"]+args,stdin=stdinr,stderr=stderrr)
    time.sleep(1)
    sock = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
    sock.connect(('127.0.0.1',1234))
    sock.sendall(b'\xde\xad\xbe\xef')
    sock.close()


if __name__ == '__main__':
    main()

サーバーのポートは適当に指定。最初65432でやったら別のでやってねって言われた。何回か適当な数入れてこれが大丈夫だった。