[문제]


[풀이]

보호기법 분석

  • 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

+ Recent posts