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부터 1/4까지 무게정보받기
- 가짜코인이 없었다면 중간값부터 3/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 값 획득
'3. Pwnable (포너블) > 1) Write UP' 카테고리의 다른 글
[6주차] pwnable.kr 문제 풀이_pwnabless (0) | 2025.05.20 |
---|---|
[5주차] pwnable.kr 문제 풀이_pwnabless (0) | 2025.05.13 |
[2020.04.14] pw.Sly - pwnable.kr의 bof(2) (0) | 2020.04.26 |
[2020.04.07] pw.Sly - pwnable.kr의 bof(1) (0) | 2020.04.26 |