#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;
}
input의 코드이다. 보아하니 Stage를 하나씩 해결해가면서 마지막 Stage 5를 해결하면 될 듯 하다. 이번 문제는 전부 로컬 ubuntu 16.04LTS 로 복사해서 한 후 서버로 옮겨갔다.
0x01. Stage 1
우선 Stage1을 보자.
// 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");
//argv -> argv에 관한 문제이다.
if(argc != 100) return 0;
argv를 100개 넣어줘야만 한다.
if(strcmp(argv['A'], "\x00")) return 0;
argv의 'A'번째, 즉 0x41번째, 65번째 가 0x00이어야 한다.
if(strcmp(argv['B'], "\x20\x0a\x0d")) return 0;
argv의 'B'번째, 즉 0x42번째, 66번째 가 0x200a0d이어야 한다.
pwntool을 이용하기 위해 py파일을 만들자
argv를 여러개 전달하기 위해 pwntools 의 process함수를 볼 필요가 있다.
classpwnlib.tubes.process.
process
(argv=None, shell=False, executable=None, cwd=None, env=None, stdin=-1, stdout=<pwnlib.tubes.process.PTY object>, stderr=-2, close_fds=True, preexec_fn=<function <lambda>>, raw=True, aslr=None, setuid=None, where='local', display=None, alarm=None, *args, **kwargs)
argv=None인 부분에 []로 배열을 만들어서 넣어주면 될 듯 하다.
from pwn import *
r = process([ 'a',\
'a', 'a', 'a', 'a', 'a' ,'a' ,'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a' ,'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', '\x00', '\x20\x0a\x0d', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a' ,'a', 'a', 'a', 'a', 'a' ,'a' ,'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a' , 'a', 'a'], executable='./input')
print(r.recv(1024))
이 정도면 stage1은 가볍게 통과할 것이다.

Clear!
0x02. Stage 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");
read(0, buf, 4);
fd가 0이므로 stdin이 들어간다. 가볍게 python으로 \x00\x0a\x00\xff를 보내주도록 하자
read(2, buf, 4);
아마 헤맨다면이 부분이지 않을까 싶다. 2는 stderr이며 여기서 값을 받아 buf에 넣어야 한다. stderr은 입력할 수 없으므로 입력할 방법이 없을 것 같다.
하지만, pwntools의 process를 다시한번 읽어보자.
classpwnlib.tubes.process.
process
(argv=None, shell=False, executable=None, cwd=None, env=None, stdin=-1, stdout=<pwnlib.tubes.process.PTY object>, stderr=-2, close_fds=True, preexec_fn=<function <lambda>>, raw=True, aslr=None, setuid=None, where='local', display=None, alarm=None, *args, **kwargs)
stderr=-2라는 것이 보인다. 이것을 이용해 보자.
stderr도 stdin과 stdout과 마찬가지로 fd이다. 이 fd는 파일을 열때 주어지는 고유번호라고 생각하면 된다.
pwntools의 process의 stderr에 fd값을 입력해주면 read(2, buf, 4);를 실행할때 해당 fd에서 받아올 것이다.
우선 파일을 만들고, \x00\x0a\x02\xff를 넣자
f = open("abcd", "w")
f.write('\x00\x0a\x02\xff")
해당 코드를 한번 실행하면 abcd라는 파일이 생기며, f에는 fd가 들어갈 것이다.
stderr=f로 해두자.
from pwn import *
import socket
context.log_level= 'debug'
f = open("abcd", "r") //abcd파일을 r권한으로 염
// fd가 f로 들어감
r = process([ 'a',\
'a', 'a', 'a', 'a', 'a' ,'a' ,'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a' ,'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', '\x00', '\x20\x0a\x0d', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a' ,'a', 'a', 'a', 'a', 'a' ,'a' ,'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a' , 'a', 'a'], stderr=f, executable='./input')
r.recv(1024)
r.send('\x00\x0a\x00\xff')
r.recv(1024)
이 정도면 stage2도 성공하지 않을까?

clear!
0x03. Stage 3
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
코드가 간단하다. 그리고 그만큼 쉽다.
classpwnlib.tubes.process.
process
(argv=None, shell=False, executable=None, cwd=None, env=None, stdin=-1, stdout=<pwnlib.tubes.process.PTY object>, stderr=-2, close_fds=True, preexec_fn=<function <lambda>>, raw=True, aslr=None, setuid=None, where='local', display=None, alarm=None, *args, **kwargs)
얘를 다시한번 보자. env라는 인자가 있는것을 볼 수 있다. 이 인자에 dict형식으로 값을 넘겨주도록 하자. dict형식은 다음과 같이 선언한다.
envs = {'\xde\xad\xbe\x\ef":"\xca\xfe\xba\xbe"}
env의 인자로 envs를 넘겨주자.

Clear
0x04. Stage 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");
File을 사용하는 코드이다.
보아하니 \x0a라는 이름의 파일을 read권한으로 열어서 그 안에 든 4바이트가 \x00\x00\x00\x00이면 합격인 듯 하다.
위에서 stderr을 할때와 비슷하게 해결 가능할 듯 하다.
i = open("\x0a", "w")
i.write("\x00\x00\x00\x00")
i.close()
이 코드를 한번 실행시켜주면 원하는 파일이 생성될 것이다. 이 코드를 한번 실행 해 주면 Stage 4도 가볍게 클리어 할 듯 하다.

Clear!
0x05. Stage 5
대망의 마지막 Stage 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");
으.. 역시 제일 길다. 소켓이니 하나하나 따져서 가보자
먼저 ip와 port에 대해 알아보자.
sd = socket(AF_INET, SOCK_STREAM, 0);
AF_INET 은 Ipv4 프로토콜이다.
saddr.sin_addr.s_addr = INADDR_ANY
INADDR_ANY는 서버의 IP주소를 자동으로 반환해 주는 함수로 define에 정의되어 있다. 즉 자기 자신의 서버니 IP로는 localhost나 127.0.0.1(loopback)를 사용하면 될 듯하다.
saddr.sin_port = htons( atoi(argv['C']) );
이말인 즉슨 입력했던 argv중에 C번째 즉 0x43번째 즉 67번째 입력한것이 포트번호가 된다. 뭐 점유되지 않았을 만한 포트를 고르자. 필자는 9999를 선택하였다.
pwntools에서 socket을 위해 제공하는 함수, remote를 사용하자.
m = remote('localhost', 9999)
로 호출해서
m.send를 사용해 \xde\xad\xbe\xef를 보내주자
from pwn import *
import socket
f = open("abcd", "r")
envs={'\xde\xad\xbe\xef':'\xca\xfe\xba\xbe'}
r = process([ 'a',\
'a', 'a', 'a', 'a', 'a' ,'a' ,'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a' ,'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', '\x00', '\x20\x0a\x0d', '9999', 'a', 'a', 'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', \
'a', 'a', 'a' ,'a', 'a', 'a', 'a', 'a' ,'a' ,'a', \
'a', 'a', 'a', 'a', 'a', 'a', 'a' , 'a', 'a'], stderr=f, env=envs, executable='./input')
print(r.recv(1024))
r.send('\x00\x0a\x00\xff')
r.recv(1024)
sleep(5) //소켓 여는데 시간이 걸리므로 sleep을 써 주자.
m = remote('localhost', 9999)
m.send('\xde\xad\xbe\xef')
print(r.recv(1024))

CLEAR!
0x06. pwnable
이제 서버로 가자.
ssh -p 2222 input2@pwnable.kr 로 연결하자 비밀번호는 guest다.
/home/input2/ 에는 권한이 없어 쓸 수 없으니 tmp내에 아무 폴더나 만들고 사용하자.
Stage 2와 Stage4에 사용할 file들을 전부 python 코드를 사용해 만들자
input과 flag는 심볼릭 링크로 만들어두면 된다.
ln -s /home/input2/input ./input
ln -s /home/input2/flag ./flag
를 한 후 python으로 py파일을 실행해 주자.

CLEAR