input2@pwnable:~$ ls
flag input input.c
ls로 파일을 열어 보면 3개의 파일이 보인다.
cat으로 열어 본 input.c는 다음과 같다.
#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;
}
main
- stage 1 ~ 5까지의 문제들이 보인다.
- stage5까지 전부 풀면 flag 출력
stage1부터 천천히 보자
stage 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");
- argc가 100이 아니면 return 0
- argv[’A’], 즉 argv[65]의 값이 \x00이 아니면 return 0
- argv[’B’], 즉 argv[66]의 값이 \x20\x0a\x0d이 아니면 return 0
처음엔 collision 문제에서 했던 것처럼 python을 이용해 넣어 주려고 했다.
하지만 이렇게 한 줄로 넣으면 그저 문자열 하나로 들어가서 실패…
끈기의 한국인답게 100개를 적어 보기도 했다.
… 당연히 될 줄 알았는데 안 돼서 당황했지만, argv를 넣어 줄 다른 방법을 찾아 보았다.
찾아보던 중 pwntools라는 걸 써 보기로 했다.
vs code로 python 파일을 하나 만들어서 코드를 만들었다.
from pwn import *
argv1 = ["a" for i in range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"
p = process(executable= "/input",argv= argv1)
하지만 input 파일의 경로를 어떻게 찾아야 할지 감이 잡히지 않았다…
로컬에서 서버로 어떻게 접근하지?? 라는 의문이 들 때 tmp 파일에 다른 파일을 생성할 수 있다는 정보를 들었다.
mkdir로 tmp 파일에 내 파일을 하나 만들고, 그 안에 python 파일을 만들었다.
서버에서 파이썬 파일을 만드니 금방 경로를 찾을 수 있었다.
from pwn import *
argv1 = ["a" for i in range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"
p = process(executable= "/home/input2/input",argv= argv1)
p.interactive()
결과
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");
처음 포너블을 풀 때 봤던 파일 디스크립터를 이용한 문제이다. 0은 표준 입력을, 2는 표준 에러를 의미한다.
그럼 간단하게 표준 입력과 표준 에러를 입력해 주면 되는 것이다!
fd 문제를 풀 때 read(2, but, …)처럼 2가 들어가도 입력을 받았던 것이 생각나서 똑같이 보내 주었다. 하지만 clear가 안 되는 걸 보니 이렇게 푸는 게 아닌 것 같다.
process 함수에 대해 찾아 볼 때 표준 에러를 넣어 줄 수 있다는 것을 알게 되었다. 또한 표준 에러는 파일 형식으로 넣어 줘야 한다고 한다.
from pwn import *
argv1 = ["a" for i in range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"
stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()
p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'))
a = "\x00\x0a\x00\xff"
p.send(a)
p.interactive()
그래서 dummy.txt 파일을 만들어 안에 값을 넣어 주었다. 그리고 process 함수 인자로 stderr를 넘겨 주면 stage 2 clear!
결과
stage 3
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
stage1, 2를 풀고 넘어오니 stage 3, 4는 금방 풀 수 있었다.
stderr를 process함수의 인자로 넘겨 준 것처럼 env도 인자로 넘겨 줄 수 있다고 한다.
이때 env는 딕셔너리 형식으로 맞춰 줘야 한다!
따라서 \xde\xad\xbe\xef에 대응되는 곳에 \xca\xfe\xba\xbe를 넣어 주고, process의 인자로 env를 넘겨 주면 된다.
from pwn import *
_env = {}
_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe"
argv1 = ["a" for i in range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"
stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()
p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'), env = _env)
a = "\x00\x0a\x00\xff"
p.send(a)
p.interactive()
결과
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");
stage4도 상당히 간단하다.
/x0a라는 파일을 열고, 읽었을 때 \x00\x00\x00\x00의 값을 가지면 된다.
따라서 /x0a 파일을 만들고 그 안에 \x00\x00\x00\x00를 넣어 주면 된다.
from pwn import *
_env = {}
_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe"
argv1 = ["a" for i in range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"
stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()
c = open("\x0a", 'w')
c.write("\x00\x00\x00\x00")
c.close()
p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'), env = _env)
a = "\x00\x0a\x00\xff"
p.send(a)
p.interactive()
결과
stage5
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
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 함수를 통해 소켓을 생성하여 반환한다.
- 포트 번호는 argv[’C’], 즉 argv 67번째 인자 값으로 한다.
- bind 함수를 통해 소켓에 IP 주소와 포트 번호를 지정한다.
- 클라이언트와 연결되면 buf에 데이터를 수신한다.
- 수신한 데이터가 \xde\xad\xbe\xef와 같다면 클리어.
stage 5는 원격 서버에 접속해야 하므로 process 함수가 아닌 remote 함수를 사용해 접속해야 한다.
argv[’C’]에 포트 번호를 넣고, 동일한 포트 번호로 접속해 \xde\xad\xbe\xef의 값을 넘겨 주기만 하면 된다!
from pwn import *
_env = {}
_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe"
argv1 = ["a" for i in range(100)]
argv1[65] = "\x00"
argv1[66] = "\x20\x0a\x0d"
argv1[67] = '23132'
stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()
c = open("\x0a", 'w')
c.write("\x00\x00\x00\x00")
c.close()
p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'), env = _env)
a = "\x00\x0a\x00\xff"
p.send(a)
m = remote("127.0.0.1", 23132)
qq = "\xde\xad\xbe\xef"
m.send(qq)
p.interactive()
결과
하지만 이렇게만 하면 flag를 얻지 못한 상태로 종료된다.
flag
// here's your flag
system("/bin/cat flag");
return 0;
그 이유는 flag 파일이 현재 경로인 /tmp/hwannow에 없기 때문이다
Symbolinc Link
원본 파일을 가리키도록 링크를 연결 시키는 것.
심볼릭 링크를 이용해 home/input2/flag에 있는 파일을 코드에 있는 flag에서 접근할 수 있도록 하였다.
ln -s /home/input2/flag flag 명령어를 사용하여 현재 경로에 flag를 생성한 후 실행 시켜 주면, flag까지 나오며 문제를 해결해 줄 수 있다.
최종 코드
from pwn import *
_env = {}
_env["\xde\xad\xbe\xef"] = "\xca\xfe\xba\xbe"
argv1 = ["a" for i in range(100)]
argv1[65] "\x00"
argv1[66] = "\x20\x0a\x0d"
argv1[67] = '23132'
stderr = open("dummy.txt", 'w')
stderr.write("\x00\x0a\x02\xff")
stderr.close()
c = open("\x0a", 'w')
c.write("\x00\x00\x00\x00")
c.close()
p = process(executable= "/home/input2/input",argv= argv1, stderr = open("dummy.txt", 'r'), env = _env)
a = "\x00\x0a\x00\xff"
p.send(a)
m = remote("127.0.0.1", 23132)
qq = "\xde\xad\xbe\xef"
m.send(qq)
p.interactive()
'포너블' 카테고리의 다른 글
[포너블/pwnable.kr] shellshock (1) | 2023.09.02 |
---|---|
[포너블/pwnable.kr] blukat (0) | 2023.08.30 |
[포너블/pwnable.kr] cmd2 (0) | 2023.08.30 |
[포너블/pwnable.kr] cmd1 (0) | 2023.08.30 |
[포너블/pwnable.kr] passcode (0) | 2023.08.30 |