본문 바로가기

3. Pwnable (포너블)/1) Write UP

[7주차] pwnable.kr 문제 풀이_pwnabless

pwnable.kr/play.php

 

https://pwnable.kr/play.php

 

pwnable.kr

1. mistake

문제

 

문제 풀이

(1)

문제에서 제시한 ssh mistake@pwnable.kr -p2222 로 접속 / pw 입력

 

(2)

ls 명령어와 ls -l 명령어를 통해 파일 목록 및 권한 확인

 

(3)

cat mistake.c 명령어를 입력해 C코드 확인

#include <stdio.h>
#include <fcntl.h>

#define PW_LEN 10
#define XORKEY 1

void xor(char* s, int len){
	int i;
	for(i=0; i<len; i++){
		s[i] ^= XORKEY;
	}
}

int main(int argc, char* argv[]){
	
	int fd;
	if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
		printf("can't open password %d\n", fd);
		return 0;
	}

	printf("do not bruteforce...\n");
	sleep(time(0)%20);

	char pw_buf[PW_LEN+1];
	int len;
	if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
		printf("read error\n");
		close(fd);
		return 0;		
	}

	char pw_buf2[PW_LEN+1];
	printf("input password : ");
	scanf("%10s", pw_buf2);

	// xor your input
	xor(pw_buf2, 10);

	if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
		printf("Password OK\n");
		setregid(getegid(), getegid());
		system("/bin/cat flag\n");
	}
	else{
		printf("Wrong Password\n");
	}

	close(fd);
	return 0;
}

 

 

xor 함수를 살펴보면

void xor(char* s, int len){
	int i;
	for(i=0; i<len; i++){
		s[i] ^= XORKEY;
	}
}

xor 라는 이름의 사용자 정의 함수를 정의하고

인자로 받은 문자열의 각 문자에 XORKEY(== 1)와 XOR 연산을 취해 저장

 

 

main 함수를 살펴보면

int main(int argc, char* argv[]){
	
	int fd;
	if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
		printf("can't open password %d\n", fd);
		return 0;
	}

 

특정 경로의 파일 open 가능 여부의 확인하는 코드임을 알 수 있음

 

옵션 설명
O_RDONLY 읽기 모드(Read Only)
O_WRONLY 쓰기 모드(Write Only)
O_RDWR 읽기/쓰기 모드(Read Write)
O_CREAT 파일 생성
O_APPEND 기존 파일 뒤에 이어서 작성(내용 추가)
O_WRONLY 또는 O_RDWR과 함께 사용
O_TRUNC 파일을 0바이트로 잘라내기(초기화)
O_WRONLY 또는 O_RDWR과 함께 사용

 

open("/home/mistake/password",O_RDONLY,0400)

0400 == 파일 권한 비트 (사용자 에게만 읽기 권한 부여)

e.g) 0644 == 사용자 권한(읽기+쓰기) / 그룹, 일반 사용자 권한(읽기)

like (chmod 4755)

 

파일 권한 비트 사용 시 무조건 O_CREAT 옵션과 함께 사용해야 함

상단 코드의 경우 해당 옵션을 사용하지 않았기 때문에, 0400 비트는 의미 X

 

여기서 "/home/mistake/password" 경로의 파일을 읽기 전용(O_RDONLY)으로 열어

fd에 저장하고 fd와 0과 비교한다고 생각할 수 있지만, 연산자 우선 순위를 잘 생각해야 함

 

< 비교 연산자
= 대입 연산자

이 중 우선 순위가 높은 것은 비교 연산자임

 

따라서 (fd=open("/home/mistake/password",O_RDONLY,0400) < 0)를 다시 살펴보면

"/home/mistake/password" 경로의 파일을 O_RDONLY 옵션을 통해

파일 open 가능 여부에 따른 결과값과 0을 비교하고

비교값을 fd에 저장한다는 것을 알 수 있음

 

파일 open 성공 시 0 이상의 값(보통 3이상), 실패 시 -1 저장

fd(0) == stdin / fd(1) == stdout / fd(2) == stderr 이기 때문

 

상단에서 ls -l 명령어를 통해 파일 권한 확인 시

password 파일은 읽기 권한만 부여되어 있었음

따라서 "/home/mistake/password" 파일에 O_RDONLY 옵션을 부여했을 때

파일 open은 성공하므로 open() 함수의 반환값은 0 이상의 값이 됨

 

이후, 0과 비교하게 되는데 open() 함수의 반환값이 0보다 작지 않으므로

비교 연산자를 통한 반환값은 false == 0 임

 

그 후 우선 순위인 대입연산자를 통해 반환값 0을 fd에 저장함

따라서 fd(0)이 됨

 

printf("do not bruteforce...\n");
	sleep(time(0)%20);

print문 출력 후 일정시간 지연시킴

 

sleep(time(0)%20)

→ 20으로 나눈 나머지(0~19) 초만큼 랜덤하게 지연

 

	char pw_buf[PW_LEN+1];
	int len;
	if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
		printf("read error\n");
		close(fd);
		return 0;		
	}

11byte(PW_LEN(==10) + 1) 크기만큼 pw_buf 이름으로 char형 배열 선언

 

fd(파일 디스크립터) 값을 통해 버퍼 크기만큼(PW_LEN) 읽고(read)

해당 값을 pw_buf에 저장

 

상단에서 fd == 0을 확인했으므로, pw_buf 값은 사용자 입력을 통해 저장될 예정

 

if(!(len=read(fd,pw_buf,PW_LEN) > 0))

여기서도 연산자 2개 사용 → 연산자 우선 순위 주의

 

그러나 상단 코드와 달리 () 사용으로

(len=read(fd,pw_buf,PW_LEN) 대입 후 0과 비교 수행

 

0이 아닐 경우 printf문 수행

 

	char pw_buf2[PW_LEN+1];
	printf("input password : ");
	scanf("%10s", pw_buf2);

11byte(PW_LEN(==10) + 1) 크기만큼 pw_buf2 이름으로 char형 배열 선언

사용자 입력(scanf)을 통해 10byte를 입력받아 pw_buf2에 저장

 

	// xor your input
	xor(pw_buf2, 10);

	if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
		printf("Password OK\n");
		setregid(getegid(), getegid());
		system("/bin/cat flag\n");
	}
	else{
		printf("Wrong Password\n");
	}

	close(fd);
	return 0;
}

 

상단의 xor 함수 코드를 다시 살펴보면

void xor(char* s, int len){
	int i;
	for(i=0; i<len; i++){
		s[i] ^= XORKEY;
	}
}

xor 함수에는 문자열과 길이값이 인자로 들어감

 

따라서 xor(pw_buf2, 10) 코드는 pw_buf2에 저장된 문자열과 10을 인자로 받음

pw_buf2의 값과 XORKEY(== 1)에 XOR 연산을 취해 저장

 

strncmp == 비교할 문자열의 길이를 지정해 비교

 

strncmp(pw_buf, pw_buf2, PW_LEN)

pw_buf와 pw_buf2의 값을 PW_LEN의 길이만큼 지정해 서로 비교함

 

strncmp 함수 앞에 논리 부정 연산자(!)가 있으므로

 

!strncmp(pw_buf, pw_buf2, PW_LEN) 해당 코드가 참(True == 1)이 되려면

pw_buf와 pw_buf2의 값이 서로 일치해야 함

 

 

[ 내용 정리 ]

pw_buf와 pw_buf2는 사용자 입력을 통해 값을 저장하는데

pw_buf2는 입력받은 문자열과 XORKEY( == 1)에 XOR 연산을 취해 저장함

 

사용자로부터 입력받은 pw_buf와 XOR 연산을 취한 pw_buf2 값이

서로 일치하면 flag 획득 가능

 

 

(4)

pw_buf에 임의의 문자열 'AAAAAAAAAA' 입력한다고 가정 (A = 0x41)

pw_buf2에는 사용자 입력값에 1과 XOR 연산을 취한 후 pw_buf와 같아야 하므로

이를 다르게 해석하면 pw_buf와 1에 XOR 연산을 취하면 pw_buf2의 사용자 입력 문자열이 나옴

 

 

0x40 == '@' 이므로 pw_buf2의 사용자 입력 문자열에 '@@@@@@@@@@'을 입력 시

해당 문자열과 1에 XOR 연산을 취해 'AAAAAAAAAA'가 됨을 알 수 있음

 

따라서 pw_buf와 pw_buf2가 동일해져 flag 획득 가능

 


2. coin1

문제

 

문제 풀이

(1)

문제에서 제시한 ssh coin1@pwnable.kr -p2222 로 접속 / pw 입력

 

(2)

ls 명령어와 ls -l 명령어를 통해 파일 목록 및 권한 확인

 

(3)

파일이 1개밖에 없으므로 해당 파일 실행해서 동작 방식 확인

 

출력문 대로 nc를 사용해 포트 9007에 접속

 

 

(4)

 

코인 찾기 문제 확인

 

Q. 보유한 코인 중 하나의 위조 코인을 찾아라

 

조건

-N개의 코인이 주어지며, 이 중 하나는 위조 코인임

   (진짜 동전의 무게는 10이고 가짜동전의 무게는 9)

-서버는 N(코인 개수)과 C(시도할 수 있는 횟수)를 제공

- 제한된 횟수 안에 가짜 코인을 찾아야 함

 

N 과 C의 정보가 주어지면

1. 무게를 측정하고싶은 코인의 인덱스 지정 가능

2. 무게 정보를 얻고

1~2단계를 C 번 수행한뒤 정답을 입력해야 함

 

해당 과정들을 반복하며 총 100개의 위조 코인을 찾으면 flag 획득 가능

 

시간 제한이 있어 일일이 대입 불가하므로 코드 작성 필요

 

파일 생성 및 작성 권한이 부여된 tmp 디렉토리에서 코드 작성 진행

1. cd ./tmp → tmp 디렉토리로 이동

2. coin1.py 파일 생성

 

 

(5)

[ 코인의 인덱스 값을 이용한 무게 측정 방법 예시 ]

  1. 1부터 중간값까지 무게 정보 받기
  2. 가짜코인이 있다면 1부터 1/4까지 무게정보받기
  3. 가짜코인이 없었다면 중간값부터 3/4까지 무게정보 받기
  4. 2~3을 반복(매번 중간값을 찾아서 가짜코인이 있는 쪽을 탐색)

 이진 탐색 알고리즘을 활용

from pwn import *

server = remote("pwnable.kr", 9007)
server.recvuntil("- Ready? starting in 3 sec... -\n")

for _ in range(100):  # 100번 반복
    server.recvuntil("=")
    N = int(server.recvuntil(" ").strip())
    server.recvuntil("=")
    C = int(server.recvline().strip())

# 위조 코인 탐색 범위 지정(left = 0 / right = N -1)
    left, right = 0, N - 1

# 전체 C번 중 중간 지점에 있는 코인 선택 후 서버에 질의
    for _ in range(C):
        mid = (left + right) // 2
        query = " ".join(str(i) for i in range(left, mid + 1))
        server.sendline(query)
        weight = int(server.recvline())

# 진짜 코인 무게 == 10 / 가짜 코인 무게 == 9
        if weight % 10 == 0:  
            left = mid + 1
        else:  
            right = mid

# 위조 코인 발견 시 해당 인덱스 저장 및 서버 제출
    server.sendline(str(left))
    server.recvline()
    print(f"Stage {_} clear")

server.interactive()

 

 

(6)

coin1.py 파일 실행

 

flag 값 획득