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

今回はなかば書き写す面が多いです。正直、わからないことが多かったので。
言わずもがな、使ってるpythonは2系です。

問題概要

以下のソースコードを実行して、flagを読み出す。

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

Stage1

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

この部分。
プログラムの引数の数を変化させて、内容も変える。

input2@ubuntu:~$ ./input `python -c "args=['a' for i in range(99)];args[ord('A')-1]='\x00';args[ord('B')-1]='\x20\x0a\x0d';print ' '.join(args)"`

でStage 1 clearかなと安易に思った。残念。

この問題点は、

  • ヌル文字で引数が止まる
  • スペース(\x20)を引数にできない
  • ラインフィード(\x0a)を引数にできない
  • キャリッジリターン(\x0d)を引数にできない

とたくさん問題がある。何も満たせていない...
ここで、わからなかったので、答えを検索した。
で、出てきたのが、これ

0x3f97.github.io

コードがわかりやすかった。
この部分は、
exec系のシステムコールを使って、引数を渡すようだ。
これを見て思ったのが、

ox.execv("/home/input2/input", ["input"]+args)

の部分で、自分で、引数の0番目を渡しているということ。
ここら辺は全部シェル(bash, zsh etc)がやってくれてるんだなという新たな知見を得ました。
なるほど。
じゃあ、やってみよう。

# /tmp/myfiles/stage1.py
import os
def main():
        args = ["0"] * 99
        args[ord("A")-1] = ""
        args[ord("B")-1] = "\x20\x0a\x0d"
        pid = os.fork()
        if pid == 0:
                os.execv("/home/input2/input",["input"]+args)

if __name__ == '__main__':
        main()
input2@ubuntu:/tmp/myfiles$ python stage1.py 
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!

おっけい。
補足的な説明になるんだが、飛んだ先のパイプを使ったstage1クリアー方法はまちがっているんですよね。どうやら、execvが勝手に引数の後にヌル文字を追加してくれるので。
とりあえず、今日はここまでで。