Dreamhack/Dreamhack Wargame (Challenge)

[34] IT 비전공자 [dreamhack]Broken Password문제 풀기

imaginefuture-1 2024. 10. 13. 08:11

34번째 문제 가보자구!

 

ghidra로 이용해서 디컴파일했다

 

undefined8 main(void)

{
  int iVar1;
  long in_FS_OFFSET;
  undefined8 *local_48;
  undefined8 local_40;
  char local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  proc_init();
  iVar1 = open("/dev/urandom",0);
  read(iVar1,password,8);
  close(iVar1);
  puts("can u guess me?");
  sleep(0);
  read_input(local_38,0x20);
  iVar1 = strncmp(password,local_38,8);
  if (iVar1 == 0) {
    system("cat flag");
  }
  else {
    puts("wrong... :p");
    puts("can you try another path? maybe impossible");
    printf("> ");
    read_input(&local_48,8);
    printf("> ");
    read_input(&local_40,8);
    *local_48 = local_40;
    puts("wish your happy sleep");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

지피티야 지피티야!

 

이 코드는 C로 작성된 프로그램의 일부분으로, 특정 기능을 수행하는 구조를 가지고 있습니다. 코드를 단계별로 분석하여 설명하겠습니다.

### 코드 분석

1. **함수 시그니처**:
   ```c
   undefined8 main(void)
   ```
   - `main` 함수가 정의되어 있으며, 반환형은 `undefined8`로 나타나 있습니다. 이는 `unsigned long` 또는 8바이트 정수를 반환하는 것으로 볼 수 있습니다.

2. **변수 선언**:
   ```c
   int iVar1;
   long in_FS_OFFSET;
   undefined8 *local_48;
   undefined8 local_40;
   char local_38 [40];
   long local_10;
   ```
   - `iVar1`: 반환 값을 저장할 변수입니다.
   - `in_FS_OFFSET`: 스택 보호를 위한 값입니다.
   - `local_48`: 포인터로 사용될 변수를 저장합니다.
   - `local_40`: 8바이트 정수로 사용될 변수입니다.
   - `local_38`: 입력을 저장하기 위한 문자 배열입니다.
   - `local_10`: 스택 보호를 위한 값을 저장하는 변수입니다.

3. **스택 보호 초기화**:
   ```c
   local_10 = *(long *)(in_FS_OFFSET + 0x28);
   ```
   - 스택 보호를 위한 초기값을 설정합니다. 이는 나중에 스택 오버플로우가 발생했는지 확인하는 데 사용됩니다.

4. **초기화 및 난수 읽기**:
   ```c
   proc_init();
   iVar1 = open("/dev/urandom",0);
   read(iVar1,password,8);
   close(iVar1);
   ```
   - `proc_init()` 함수가 호출되어 초기화를 수행합니다.
   - `/dev/urandom` 파일을 열어 8바이트의 랜덤 값을 읽어 `password` 변수에 저장합니다.

5. **사용자 입력 요청**:
   ```c
   puts("can u guess me?");
   sleep(0);
   read_input(local_38,0x20);
   ```
   - 사용자에게 "can u guess me?"라는 메시지를 출력합니다.
   - `sleep(0)`는 즉시 반환되어 아무런 효과가 없습니다.
   - `read_input(local_38, 0x20)`는 사용자로부터 입력을 받고 최대 32바이트(`0x20`)를 `local_38`에 저장합니다.

6. **비밀번호 비교**:
   ```c
   iVar1 = strncmp(password, local_38, 8);
   if (iVar1 == 0) {
       system("cat flag");
   }
   ```
   - `password`와 `local_38`의 처음 8바이트를 비교합니다. 두 값이 같으면 `system("cat flag")`가 실행되어 플래그 파일의 내용을 출력합니다.

7. **잘못된 비밀번호 처리**:
   ```c
   else {
       puts("wrong... :p");
       puts("can you try another path? maybe impossible");
       printf("> ");
       read_input(&local_48, 8);
       printf("> ");
       read_input(&local_40, 8);
       *local_48 = local_40;
       puts("wish your happy sleep");
   }
   ```
   - 비밀번호가 틀린 경우, "wrong... :p"라는 메시지를 출력하고 다른 입력을 요청합니다.
   - 사용자로부터 두 개의 추가 입력을 받아서 `local_48` 포인터가 가리키는 메모리 주소에 `local_40`의 값을 저장합니다. 이는 버퍼 오버플로우 공격을 수행할 수 있는 기회를 제공합니다.

8. **스택 보호 검사**:
   ```c
   if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
       __stack_chk_fail();
   }
   ```
   - 스택 보호 변수가 변경되었는지 확인하여, 만약 변경되었다면 프로그램이 비정상적으로 종료됩니다.

9. **종료**:
   ```c
   return 0;
   ```
   - 프로그램이 정상적으로 종료됩니다.

### 요약
이 프로그램은 사용자가 8바이트 길이의 비밀번호를 입력하도록 요구하고, 입력한 비밀번호가 랜덤으로 생성된 비밀번호와 일치하는지 확인합니다. 일치하면 플래그를 출력하고, 일치하지 않으면 추가 입력을 받아 그 값을 다른 메모리 주소에 저장하는데, 이는 버퍼 오버플로우 공격을 수행할 수 있는 기회를 제공합니다. 스택 보호 메커니즘이 도입되어 있어, 스택이 변경되면 프로그램이 종료됩니다.

### 플래그를 획득하는 방법
- **비밀번호 맞추기**: `/dev/urandom`에서 생성된 비밀번호를 맞추면 플래그를 얻을 수 있습니다. 하지만 이를 맞추는 것은 난이도가 높습니다.
- **버퍼 오버플로우**: 추가 입력을 통해 메모리 주소를 조작하여 공격을 시도할 수 있습니다. 

이 프로그램의 취약점을 이용해 플래그를 확보하는 방법에 대한 질문이 있으시면 더 도와드리겠습니다!

 

랜덤으로 생성되는 나수를 어떻게 맞추죠

살려줘

 

버퍼 오버플로우 공격인건가..

 

하지만 질문글에서 흰트를 봤을때는

 

strncmp 함수의 허점!

 

 

`strncmp` 함수의 허점을 이용하여 취약한 프로그램을 공격하는 방법은 버퍼 오버플로우 또는 비밀번호 유추 공격과 관련이 있습니다. 아래에서는 `strncmp`를 이용한 공격 방법에 대한 설명을 드리겠습니다.

### `strncmp`의 기능
`strncmp` 함수는 두 문자열을 지정된 길이만큼 비교합니다. 두 문자열이 같으면 0을 반환하고, 다르면 다른 값을 반환합니다. 주의할 점은 `strncmp`가 문자열의 길이를 제한하여 비교를 수행하기 때문에, 길이가 짧은 문자열과 길이가 긴 문자열의 비교에서 정확하게 일치하지 않을 수 있습니다.

 

### 공격 방법
1. **비밀번호 유추**:
   - 프로그램이 `/dev/urandom`에서 생성한 비밀번호의 첫 8바이트와 사용자가 입력한 값만 비교합니다.
   - 사용자가 비밀번호를 입력할 때, 랜덤 값의 길이가 8바이트로 제한되기 때문에, 사용자가 입력하는 문자열이 8바이트 이상인 경우 여전히 비교는 첫 8바이트만 수행됩니다.

2. **시간적 측면에서의 공격**:
   - `strncmp`는 두 문자열을 비교할 때, 일치하지 않는 첫 번째 문자까지의 모든 문자를 비교하기 때문에, 비밀번호와 입력값의 일치 여부에 따라 함수가 반환되는 시간이 다를 수 있습니다.
   - 예를 들어, 비밀번호의 첫 문자와 입력한 문자열의 첫 문자가 다르다면, `strncmp`는 즉시 반환되지만, 두 문자열의 첫 문자가 일치한다면 계속해서 다음 문자를 비교하게 됩니다. 이렇게 되면, 올바른 비밀번호를 찾기 위한 브루트 포스 공격을 시도할 수 있습니다.

### 공격 절차
1. **패턴 분석**:
   - 비밀번호의 첫 문자부터 차례대로 유추해 나가는 방식입니다. 예를 들어, 비밀번호가 `abcd1234`라면, 먼저 `a`, 그 다음 `b`를 유추해 나갑니다.

2. **비교 과정**:
   - 각 문자를 추측하여 입력하고, 프로그램의 응답 시간을 측정합니다. 입력한 문자가 올바르면 다음 문자로 진행하고, 틀리면 다른 문자를 시도합니다.

3. **자동화**:
   - 위의 과정을 자동화할 수 있습니다. 스크립트를 작성하여 가능한 모든 문자를 시도하고, 그 결과를 기록하여 올바른 비밀번호를 찾아낼 수 있습니다.

### 예시 코드
아래는 위에서 설명한 방식을 자동화하는 간단한 예시 코드입니다. 이 코드는 비밀번호 길이가 8자라고 가정합니다.

```python
import time
import string
import subprocess

# 비밀번호의 길이
password_length = 8

# 가능한 모든 문자 집합 (예: 소문자, 대문자, 숫자)
charset = string.ascii_lowercase + string.ascii_uppercase + string.digits

def guess_password():
    guess = ['a'] * password_length  # 초기값으로 'aaaaaaa' 설정
    for i in range(password_length):
        for char in charset:
            guess[i] = char  # 현재 자리의 문자를 변경
            start_time = time.time()  # 시간 측정 시작
            # 입력값을 프로그램에 전달
            process = subprocess.Popen(['./chal'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
            output, error = process.communicate(input=''.join(guess).encode())
            elapsed_time = time.time() - start_time  # 경과 시간 계산
            
            # 반환된 출력이나 시간에 따라 결과를 분석
            if "wrong..." not in output.decode():  # 잘못된 입력이 없을 경우
                print("Found password: ", ''.join(guess))
                return
            print(f'Trying {"".join(guess)} - Time: {elapsed_time:.5f}s')

# 비밀번호 추측 함수 호출
guess_password()
```

 

https://supervingo.tistory.com/152

 

Dreamhack - Broken Password

처음에는 포인트 조작인가 했는데, strncmp로 password(/dev/urandom)값이랑 비교한다.입력으로 null을 주면 0이랑 비교하는데, stnrcmp는 null이후는 비교하지 않으니까 그냥 null로 브포하면 된다...

supervingo.tistory.com

https://hyungin0505.tistory.com/90

 

[DreamHack] Broken Password #634

int __fastcall main(int argc, const char **argv, const char **envp){ int fd; // [rsp+Ch] [rbp-44h] _QWORD *v5; // [rsp+10h] [rbp-40h] BYREF __int64 v6; // [rsp+18h] [rbp-38h] BYREF char s2[40]; // [rsp+20h] [rbp-30h] BYREF unsigned __int64 v8; // [rsp+48h]

hyungin0505.tistory.com

 

gpt가 준 소스코드로 python3파일 만들어서 브포하는데 오류뜬다

 

살려줘..

 


2024-10-22

 

다시 문제 풀러왔다

 

드림핵 qna를 보면 strncmp() 허점을 이용하면 된다했다

 

난수인 password가 \x00으로 시작하는 경우, input으로 null을 넣으면
그냥 바로 같은 문자열로 인식하는?...그런 거라고 함.
그래서 확률적으로 256번 null을 쏘는 코드를 만들면 그 중에 한 번은 플래그를 얻을 수 있다...

출처: https://hxxxxng.tistory.com/52

 

입력으로 null을 주면 0이랑 비교하는데, stnrcmp는 null이후는 비교하지않으니까 그냥 null로 브포하면 된다
출처 ㅣ https://supervingo.tistory.com/152

 

256번 null을 쏘는 코드

 

 

 

 

 

gpt:

 

이 코드는 **DreamHack**의 문제를 풀기 위한 Python 스크립트로, **pwntools** 라이브러리를 사용하여 원격 서버와 통신하는 프로그램입니다. `pwntools`는 주로 **취약점 탐색 및 익스플로잇**을 자동화하는 데 사용됩니다. 아래에서 이 코드를 단계별로 설명하겠습니다.

### 주요 목적:
- 서버에 반복적으로 연결을 시도하며, 서버가 맞추기를 요구하는 값을 보내서 **특정 응답**을 찾는 작업을 합니다.
- 서버에서 'DH' 문자열이 포함된 응답을 받을 때까지 반복합니다.

### 코드 설명:

1. **`from pwn import *`**:
   - `pwntools` 라이브러리에서 모든 기능을 임포트합니다. 이 라이브러리는 **원격 연결**, **패킷 송수신**, **익스플로잇** 등을 쉽게 할 수 있게 도와줍니다.
   
2. **`while True:`**:
   - **무한 루프**를 사용하여, 조건을 만족할 때까지 계속 서버에 연결하고 데이터를 보내는 과정을 반복합니다.

3. **`r = remote("host3.dreamhack.games", 12955)`**:
   - DreamHack 서버 `host3.dreamhack.games`의 **포트 12955**에 연결합니다. 이 포트는 서버가 데이터를 받을 준비가 되어 있는 특정 서비스나 프로그램을 의미합니다.

4. **`r.recvuntil(b'can u guess me?\n')`**:
   - 서버로부터 **"can u guess me?"**라는 메시지를 받을 때까지 데이터를 기다립니다. `recvuntil()` 함수는 주어진 문자열이 수신될 때까지 데이터를 읽습니다.
   - `b'can u guess me?\n'`는 **바이트 문자열**로, 서버에서 보내는 메시지를 그대로 받는 형식입니다.

5. **`r.send(b'\0')`**:
   - 서버에 **null 바이트** (`\0`)를 전송합니다. 이 메시지는 사용자가 서버가 요청한 값을 "맞추기" 위해 보낸 값입니다. 여기서 `\0`은 **null 바이트**로, 정수로는 0을 의미합니다.
   
6. **`res = r.recv(100)`**:
   - 서버로부터 **최대 100바이트**의 응답을 받습니다. 이 응답은 서버가 클라이언트(이 코드)를 처리한 결과로 보내는 데이터입니다.
   - 이 데이터를 **`res`**라는 변수에 저장합니다.

7. **`r.close()`**:
   - 서버와의 연결을 종료합니다. 이를 통해 메모리 누수를 방지하고 불필요한 연결을 차단합니다.

8. **`if b'DH' in res:`**:
   - 서버로부터 받은 응답(`res`)에 **`'DH'`라는 문자열이 포함되어 있는지** 확인합니다. DreamHack과 관련된 문제에서는 주로 `DH`로 시작하는 플래그를 사용하므로, 이 문자열을 찾습니다.

9. **`print(res)`**:
   - `res`에 `'DH'` 문자열이 포함되어 있을 경우, **응답을 출력**합니다. 주로 이 응답에는 **플래그**나 문제 해결의 중요한 정보가 들어 있습니다.

10. **`break`**:
    - `while True` 무한 루프를 종료합니다. 즉, `'DH'`가 포함된 응답을 받으면 루프가 끝나고 프로그램이 종료됩니다.

### 동작 요약:
1. **DreamHack 서버**에 계속 연결합니다.
2. 서버에서 **"can u guess me?"** 메시지를 받을 때까지 대기합니다.
3. 서버가 값을 맞추라고 요청하면 **null 바이트 (`\0`)**를 서버에 전송합니다.
4. 서버가 반환한 응답에서 **'DH'** 문자열을 찾습니다.
5. **'DH'** 문자열이 포함된 응답을 받으면, 응답을 출력하고 프로그램을 종료합니다.

### 목적:
이 코드는 DreamHack 서버에서 **특정 문자열 ('DH')**을 포함하는 응답을 받을 때까지 계속해서 시도하는 작업입니다. 일반적으로 이런 코드는 **플래그**를 찾거나 **취약점**을 탐지하기 위해 사용됩니다.

 

야무지다 야무져..