### Đôi Lời Muốn Nói
:::success
* Chuyện là mình vừa thi cuối kỳ thực hành môn lập trình an toàn và khai thác lỗ hổng phần mềm vừa xong. Kết quả là nhóm mình là nhóm duy nhất được 10 điểm bài thực hành này :smiling_face_with_smiling_eyes_and_hand_covering_mouth:.
* Nói thật thì mình là người giải full hết challenge của bài thi và 2 đứa bạn mình chỉ ngồi chơi thôi :smile:. Nhưng bù lại 2 đứa bạn nó gánh mình môn quản trị mạng và hệ thống :neutral_face:.
* Thì bởi vì mình là người tốt bụng nên mình quyết định viết writeup các challenghe của đề bài để chia sẻ cách làm cho các bạn trong lớp :smiling_face_with_smiling_eyes_and_hand_covering_mouth:.
:::
### Stack_architect
:::info
* Đầu tiên ta có thể thấy chương trình không bật PIE, CANARY nhưng bật NX và Parital RELRO.
:::

:::info
* Nhìn vào hàm **main()**, ta thấy rằng chương trình ban đầu sẽ chuẩn bị buffer. Sau đó, nếu như giá trị của check1 khác 0 thì chương trình sẽ gọi **exit(0)**, ngược lại thì nó sẽ gọi hàm gets(local_58) để nhập giá trị cho chuỗi local_58, cuối cùng thì hàm sẽ tăng 1 vào giá trị của check1.
:::

:::info
* Trong hàm **win()**, ta thấy rằng nó kiểm tra lần lượt giá trị của check2, check3 và check4 có khác bằng 0 hay không, nếu như thõa mãn thì nó sẽ gọi hàm **system(‘/bin/sh’)** để mở shell.
:::

:::info
* Đây là địa chỉ của các biến check2, check4, check3 và check1. Dễ thấy các biến này có độ lớn 4 bytes và sắp xếp liền kề nhau theo thứ tự được nêu trên.
:::

:::info
* Để khai thác được bài này, ta phải thay đổi giá trị của check2, check3 và check4 sao cho chúng khác 0. Chương trình không có cơ chế CANARY bảo vệ nên ta có thể buffer overflow chương trình bằng hàm gets(). Hình dưới mô tả cấu trúc của stack khi ta thực hiện buffer overflow, ta sẽ thay đổi giá trị check2, check3 và check4 bằng cách tận dụng lại hàm **gets()**. Tiếp theo, ta ghi đè địa chỉ trả về của hàm main bằng địa chỉ của **gets@plt**, tham số của hàm gets() chính là địa chỉ của check2 và cuối cùng sau khi thực hiện gets() thì luồng thực thi sẽ trả về win() để mở shell.
:::

:::info
* Đây là code khai thác của chương trình.
:::

:::info
* Hình dưới thể hiện flag sau khi thực hiện code khai thác.
:::

### Shellcode
:::info
* Chương trình này bật Full RELRO, PIE nhưng tắt CANARY, và NX.
:::

:::info
* Nhìn vào hàm main(), ta có thể dễ đoán ý tưởng của chương trình. Nó bắt ta nhập giá trị đầu vào cho con trỏ local_148 và sau đó thanh ghi rip sẽ trỏ đến vị trí bắt đầu tại con trỏ local_148 để thực thi. Trong chương trình này, ta không thể buffer overflow được vì hàm read chỉ có thể đọc tối đa 300 bytes, trong khi đó độ lớn của local_148 là 312 bytes.
:::

:::info
* Ở hình dưới, ta thấy rằng trước khi gọi hàm tại vị trí local_148 thì thanh ghi rdx sẽ nhận địa chỉ của local_148.
:::

:::info
* Dựa vào những suy luận trên, ta sẽ thiết lập shell code để khai thác chương trình. Ta sẽ gọi hàm **open()** để mở file “PhaPhaKhongCoDon.txt”, sau đó ta sẽ đọc nội dung của file này bằng hàm read(). Nội dung này sẽ được lưu tại vị trí **local_148 + 0x100**. Cuối cùng ta hiển thị nội dung của file này lên màn hình bằng hàm write(). Khi dùng open() thì ta phải xác định địa chỉ lưu đường dẫn file cần mở, bởi vì ta đã có địa chỉ của local_148 trong thanh ghi rdx nên ta chỉ cần lưu chuỗi “PhaPhaKhongCoDon.txt” ở vị trí local_148 + 0x100 thì lúc này địa chỉ của đường dẫn file sẽ là rdx + 0x100.
:::

:::info
* Kết quả sau khi thực thi code khai thác.
:::

### Autofmt
:::info
* Chương trình autofmt bật hết tất cả các cờ.
:::

:::info
* Trong hàm main() của chương trình, nó xuất giá trị của hai biến có giá trị ngẫu nhiên local_f0, local_e8 và địa chỉ của biến a ra màn hình. Tiếp theo, nó bắt ta nhập giá trị cho chuỗi local_d8. Sau đó nó kiểm tra giá trị của a, b lần lượt có bằng local_f0 và local_e8 hay không, nếu bằng thì nó sẽ mở shell. Chương trình cũng có lỗi format string tại dòng lệnh printf(local_d8). Để có thể mở được shell ta phải tận dụng lỗ hỏng format string để thay đổi giá trị của 2 biến a, b.
:::

:::info
* Dựa vào hình trên, ta thấy rằng địa chỉ của b bằng địa chỉ của a trừ cho 8.
:::

:::info
* Để có thể khai thác lỗ hỏng format string, ta phải xác định offset của chuỗi lưu giá trị đầu vào. Ở hình dưới sẽ mô tả trạng thái của stack sau khi ta nhập chuỗi đầu vào là **“deadbeef”** bằng hàm **fgets()**, dễ thấy rằng offset của chuỗi so với vị trí cao nhất của stack là 4, nhưng offset thật sự của nó là 10 bởi vì chương trình trên là x64 nên 6 tham số đầu tiên của hàm sẽ được truyền theo thanh ghi.
:::

:::info
* Dựa vào những nhận định trên, ta viết code khai thác. Trong code khai thác, ta lưu giá trị của a và b vào biến value_a và value_b, đồng thời lưu giá trị địa chỉ của a vào biến addr_a. Cuối cùng ta sử dụng hàm **fmtstr_payload()** để tạo khai thác và phải xác định offset của chuỗi input, tức là địa chỉ và giá trị cần ghi đè vào trong nó. Trong trường hợp trên thì offset là 10 và địa chỉ cần ghi đè là **addr_a** và **addr_a - 8** (địa chỉ của b).
:::

:::info
* Đây là kết quả cuối cùng khi thực hiện code thực thi.
:::

### Ropchain
:::info
* Chương trình ropchain bật Partial RELRO, tắt CANARY, bật NX và tắt PIE.
:::

:::info
* Chương trình ropchain nhập vào một chuỗi bằng hàm **scanf()** với độ lớn chuỗi tối đa là 500 bytes (tính cả bytes kết thúc chuỗi) và sau đó xuất chuỗi này ra màn hình. Chương trình này cũng bị lỗi tại câu lệnh printf(local_208). Trong chương trình, ta không thể chèn shell code (cờ NX), không có hàm win để mở shell, nhưng ta có thể ghi đè vào **got section**. Ý tưởng ở đây là ta sẽ ghi đè got của **exit()** để sau khi chương trình ta thực thi xong nó sẽ trả lại main(), nhưng sau khi quay trở lại main() thì giá trị của biến a đã khác 0 nên ta sẽ phải ghi đè giá trị của a là **0xffffffff** để khi a tăng giá trị của nó lên 1 thì a sẽ lại bằng 0. Sau khi chương trình trả lại hàm main(), ta sẽ có thể thực hiện hàm scanf() và printf() lần 2, ta cũng có thể leak địa chỉ của libc ở lần printf() đầu tiên.
:::

:::info
* Hình dưới mô tả trạng thái của stack tại thời điểm trước khi gọi hàm printf(). Ta có thể thấy rằng vị trí trả về của hàm main() là __libc_start_main + 243, vị trí này có địa chỉ là 0x7fffffffe390 và địa chỉ của đỉnh stack là 0x7fffffffe180, dễ thấy rằng địa chỉ trả về của hàm main() có offset tương ứng với hàm printf() là 65 + 6 = 71. Ta sẽ dựa vào offset này để thông qua nó tìm được địa chỉ của libc.
:::

:::info
* Ở lần printf() đầu tiên, ta đã có được địa chỉ của libc nên ta sẽ có được địa chỉ của hàm **system()** và chuỗi **“/bin/sh”**. Ta sẽ tận dụng việc này để gọi shell. Ở lần printf() thứ hai ta sẽ ghi đè exit got bằng địa chỉ của gadget add rsp, 0x90 ; pop rbx ; ret, gadget này có mục đích sẽ làm tăng giá trị của thanh ghi rsp để nó trỏ đến vị trí mà ta chèn gadget mở shell và đồng thời xây dựng chuỗi gadget để mở shell. Để xây dựng được chuỗi gadget mở shell thì ta phải tìm gadget pop rdi ; ret với mục đích truyền địa chỉ của chuỗi “/bin/sh” làm tham số đầu tiên cho hàm system().
:::

:::info
* Hình trên mô tả code khai thác theo ý tưởng đã nêu trên.
:::

:::info
* Đây là kết quả cuối cùng khi ta thực thi code khai thác.
:::
