# Cơ chế bảo mật ## Canary :::spoiler Canary **Cơ chế**: Khi một hàm bắt đầu, trình biên dịch đặt một giá trị ngẫu nhiên, bí mật (gọi là "canary") lên stack, nằm ngay trước địa chỉ trả về (saved rip) và rbp. **Hoạt động**: Trước khi hàm kết thúc (trước lệnh ret), hàm sẽ kiểm tra xem giá trị canary trên stack có còn nguyên vẹn không. **Mục đích**: Chống lại tràn bộ đệm stack (Stack Buffer Overflow). Nếu kẻ tấn công cố gắng ghi đè lên địa chỉ trả về (để chiếm quyền điều khiển), họ bắt buộc phải ghi đè lên cả giá trị canary. Khi hàm kiểm tra, nó sẽ phát hiện canary đã bị thay đổi và sẽ ngay lập tức dừng chương trình (thường với lỗi "stack smashing detected" ), ngăn chặn cuộc tấn công. **Hạn chế**: nếu attacker có cách đọc canary (info leak) thì có thể viết đúng canary và ghi tràn tiếp; cũng không ngăn được overwrite các con trỏ heap hoặc frame pointer nếu không chạm canary. -Cấu trúc của stack ở 64bit: ↑ địa chỉ cao [rbp + 16] ... caller args ... [rbp + 8 ] return address <- (được call đẩy lên trước khi prologue chạy) [rbp ] saved rbp <- (push rbp trong prologue) [rbp - 8 ] canary (8 bytes) <-- CANARY (copy của __stack_chk_guard) [rbp - 16] other locals... [rbp - 72] buffer[64] (ví dụ) <-- vùng dễ bị overflow (viết quá cỡ sẽ chạm canary) ↓ địa chỉ thấp ::: ![image](https://hackmd.io/_uploads/H1wAOCF1We.png) ![image](https://hackmd.io/_uploads/SyjTF0Fkbe.png) ## NX(Non-Executable Stack): * ![image](https://hackmd.io/_uploads/ryYPqCKyWe.png) ![image](https://hackmd.io/_uploads/r1M_o0tJWl.png) ## PIE (Position-Independent Executable) ![image](https://hackmd.io/_uploads/B1fSkJ91Zg.png) ![image](https://hackmd.io/_uploads/B1E6Ikck-g.png) ## RELRO (Relocation Read-Only) Ví dụ minh họa dễ hiểu: ![image](https://hackmd.io/_uploads/H14LpJqy-g.png) ![image](https://hackmd.io/_uploads/SJMvay9JZx.png) **Hạn chế**: full RELRO chống overwrite GOT nhưng không chống ROP hay overwrite các chỗ khác (ví dụ global variables). Cũng gây chậm (resolve sớm). ## SHSTK (Shadow Stack - Ngăn xếp bóng):là 1 phần trong cơ chế bảo mật Control-Flow Integrity ![image](https://hackmd.io/_uploads/HyOIdZ91-e.png) * IBT (Indirect Branch Tracking - Theo dõi nhánh gián tiếp):đây cũng là 1 phần trong cơ chế bảo mật Control-Flow Integrity ![image](https://hackmd.io/_uploads/SkP1t-cJbl.png) ![image](https://hackmd.io/_uploads/r1ywt-cJbg.png) ![image](https://hackmd.io/_uploads/ryl_FW5ybl.png) * SHSTK và IBT cùng nhau tạo thành CET (Control-Flow Enforcement Technology). -SHSTK bảo vệ luồng điều khiển trả về (qua ret). -IBT bảo vệ luồng điều khiển đi tới (qua call/jmp gián tiếp). -Khi cả hai được bật, việc thay đổi luồng chạy của chương trình (control-flow hijacking) trở nên cực kỳ khó khăn. ## Các kĩ thuật về bug BUFFER OVERFLOW * Kĩ thuật RET2WIN: -Cách nhận biết:dùng "checksec--file (tên file)":khi kiểm tra thấy không có CANARY và không có cả PIE tiếp sau đó dùng ida để kiểm tra hàm nếu thấy các hàm như win(), get_shell(), print_flag() thì dùng kĩ thuật này sẽ có thể vượt qua được ![image](https://hackmd.io/_uploads/H1YQjIygbg.png) ![image](https://hackmd.io/_uploads/BkK4oL1xZg.png) :::warning Khi dùng kĩ thuật này tỉ lệ cao sẽ gặp lỗi là có lệnh trong hàm win sẽ làm thay đổi địa chỉ của stack làm cho địa chỉ đấy không còn chia hết cho 16 ::: -Mục tiêu:kĩ thuật ret2win với buffer overflow sẽ cố gắng ghi đè lên địa chỉ quay lại bằng địa chỉ của hàm win lúc này khi pop ra để quay lại hàm cha thì nó sẽ vào hàm win và ta sẽ có shell # Challenge: -Hàm main: ![image](https://hackmd.io/_uploads/HyTXr8Jgbg.png) -Hàm win: ![image](https://hackmd.io/_uploads/BJxLSI1g-x.png) ![image](https://hackmd.io/_uploads/SkZPHI1gbg.png) -Đọc challenge ta thấy được buf chỉ có 28byte mà lệnh read lại đọc tận 48 byte nghĩa là lỗi buffer overflow;tiếp theo tại lệnh read ta pattern create 48 để tạo ra 48 byte rác trước và nhập nó vào read sau đó đi đến lệnh ret và copy địa chỉ ở đó và dùng lệnh pattern search+địa chỉ đó để tìm khoảng cách từ điểm đầu của buffer nơi read ghi vào đến lệnh ret là bao nhiêu byte;kết quả sẽ là 40 từ đó ta biết được khoảng cách là 40 và bắt đầu từ byte 41 giá trị của địa chỉ quay lại sẽ bị thay đổi;lúc này ta đã hoàn thành được 1 mục tiêu nhỏ là nhập vào bao nhiêu byte để thay đổi địa chỉ quay lại -Tư duy tiếp theo đó là chỉ cần đổi địa chỉ quay lại thành địa chỉ hàm win để nó nhảy vào hàm win thì bài toán đã được giải quyết;ta dùng lệnh p win trong gdb để kiểm tra địa chỉ hàm win vì là không có cơ chế pie nên địa chỉ hàm win luôn không đổi là 0x401249 ,thế nhưng ta lại nhận ra đây là 1 raw byte(byte thô) ta không thể viết nó từ bàn phím vì những thứ ta nhập từ bàn phím đều được coi là những kí tự nó sẽ quy đổi theo bảng mã ascii và biến đổi theo little endian;nên ta phải dùng script để nhập vào bằng lệnh subl solve.py(dùng sublime text) hoặc code solve.py(dùng vs code) nó sẽ mở cửa số để gõ script vào có dạng như sau: ![image](https://hackmd.io/_uploads/HynuNFkgWe.png) ![image](https://hackmd.io/_uploads/rk3tVYke-g.png) với script ta có 3 lệnh được xem như cấu trúc: ![image](https://hackmd.io/_uploads/rJN6PY1xbl.png) -Trước tiên đưa 40 byte rác vào payload;tiếp theo thêm địa chỉ win vào với 2 cách ``` C1:payload+=p64(địa chỉ win) (hữu dụng khi số lần dùng địa chỉ ít) C2:exe=ELF("./bof3",checksec=false) payload+=p64(exe.sym['win']) (nên dùng khi muốn gọi địa chỉ của 1 hay nhiều hàm ra nhiều lần) nếu không có checksec=false thì nó sẽ không hiện ra bảng checksec với mỗi lần chạy script để tải file binary lên ``` -Sau đó ta cần gửi dữ liệu đi với nhiều cách nhưng trong bài này ở phần code có lệnh printf('> ')nên ta dùng p.sendafter nghĩa là ngay sau khi thấy "> "thì nó sẽ gửi dữ liệu vào chương trình;lúc này tưởng chừng bài toán đã được giải quyết vì đã truyền vào 40 byte rác cộng 8byte của địa chỉ hàm win là đã biến địa chỉ trả về thành địa chỉ hàm win rồi;tuy nhiên khi ta chạy file với lệnh python3 solve.py này sẽ thấy nó như sau: ![image](https://hackmd.io/_uploads/HkusI9Jxbg.png) Với EOF là end of file nghĩa là chương trình đã bị crash;chương trình crash chính là cơ chế bảo mật khi phát hiện có kẻ muốn xâm nhập để tự bảo vệ chương trình;khi gặp lỗi như trên ta nên dùng debug động để tìm hiểu bằng cách thêm lệnh input để ngừng chương trình lại;tiếp theo ta dùng python3 solve.py để lấy pid sau đó gdb attach+pid đó;ta dùng disas main tại gdb để lấy địa chỉ của ret và đặt nó làm breakpoint để xem địa chỉ quay lại đã được thay đổi hay chưa sau khi xét thấy nó đã thay đổi thì ta tiếp tục cho nó chạy nhưng khi đến lệnh gọi hàm system thì nó bị lỗi như này: ![image](https://hackmd.io/_uploads/SJCZ5qkxbe.png) -Lỗi này gây ra bởi các thanh ghi xmm0(xmm1,2,... tương tự) và đặc điểm nhận dạng của nó là movaps và xmm nó sẽ làm thay đổi địa chỉ của stack thành 1 số không chia hết cho 16 mà với thanh ghi xmm thì nó bắt buộc địa chỉ của stack phải chia hết cho 16;lúc này ta nên debug lại từ đầu ở hàm ret để xem rằng lệnh nào đã làm thay đổi địa chỉ của stack và cho trong script là sẽ nhảy thẳng đến lệnh phía sau nó chẳng hạn như bài này lệnh ở vị trí win+4 sau khi thực thi sang lệnh win+5 sẽ làm thay đổi địa chỉ stack nên ta cho script thành ![image](https://hackmd.io/_uploads/BkWth9JlWg.png) -Lúc này khi cho chạy lại thì ta đã lấy được shell(thành công rồi đó) :::success ![image](https://hackmd.io/_uploads/BknXKeQxZx.png) Đây được gọi là shebang là thứ nên được viết vào đầu của mỗi chương trình script cùng với ![image](https://hackmd.io/_uploads/ByrhYeXx-x.png) là 1 cặp bài trùng đúng nghĩa với shebang chỉ định chương trình nào sẽ được dùng để dịch và chạy các mã lệnh còn chmod +x là lệnh dùng để cấp quyền thực thi cho file đó và với mỗi file thì ta chỉ cần cấp 1 lần là có thể dùng được mãi mãi trừ khi có gỡ quyền đó đi hoặc chuyển nó đi nơi khác.Khi đã thực hiện các bước trên khi ta mún chạy chương trình thì chỉ cần gõ ./solve.py thay vì phải gõ python3 solve.py;cách để kiểm tra quyền thực thi:dùng lệnh ls -al ./solve.py nếu có x(excutable)nghĩa là file đã được cấp quyền thực thi ::: ## Kĩ thuật ROPCHAIN: Tư duy chung:mục đích của ta là lấy được shell để chiếm quyền kiểm soát chương trình,bình thường ta sẽ đơn giản là tìm địa chỉ của shell và ghi đè lên save rip là có thể nhảy đến shell rồi;tuy nhiên với những bài có cơ chế bảo mật NX(no execute),nghĩa là với những nơi như stack và heap thì nó sẽ đánh dấu là non executable lên đấy nếu ta cố tình viết thêm j đấy thì chương trình sẽ tự động crash;ta buộc phải tận dụng những lệnh có sẵn được gọi là ropgadget với kết thúc là lệnh ret,ghép nhiều gadget như vậy lại sẽ sẽ tạo thành 1 chain và chain này sẽ có thể thực hiện được yêu cầu của bạn.Cụ thể hơn đầu tiên ta dùng lệnh `file + tên file` để hiện ra thông tin của file ví dụ như sau:![image](https://hackmd.io/_uploads/SkO_qTtxZx.png) Thứ ta chú ý ở đây là statically linked(liên kết tĩnh).Có 2 loại liên kết là động(dynamic) và tĩnh(static);với liên kết tĩnh nếu người lập trình không dùng hàm system thì hàm system hoàn toàn không tồn tại trong file và không thể gọi nó.Ta có thể kiểm tra trong ida bằng shift f3 hoặc ctrl+p và ghi tên hàm system ra.Lúc này ta phải tự tìm các mảng gadget và ghép chúng lại để có được 1 cấu trúc có chức năng giống như hàm system có 2 trường hợp xảy ra lúc này: -TH1:tìm đủ gadget lúc này đơn giản là ta tìm các gadget để thực hiện syscall execve(có chức năng tương tự hàm system) nối chúng lại và hoàn thành thôi -TH2:là trường hợp khá hiếm khi ta không thể tìm đủ gadget với liên kết tĩnh lúc này ta phải dùng các kĩ thuật khác như SROP hay viết đè GOT(tutu tìm hiểu sau) Với liên kết động:(tham khảo) TH1:tìm đủ gadget thì ta có thể dùng ropchain giống liên kết tĩnh nhưng thường k tìm thấy syscall trong file binary gốc nên có thể bỏ qua cách này và nên dùng ret2plt TH2:thiếu gadget thì lúc này nên dùng ret2libc -Tóm lại là ta không thể tự thực thi shell mà phải nhờ hệ thống thực thi giúp,mà muốn gọi hệ thống thì phải dùng lệnh syscall để nhờ hệ thống thực thi lệnh execve để chạy shell.Tại sao lại phải syscall?.Vì syscall là lệnh assembly giao tiếp trực tiếp với kernal và luôn có sẵn miễn là tìm được gadget syscall;cũng tương tự như asm thì muốn hệ thống thực thi 1 lệnh nào đó thi trước tiên ta phải xác định được mã số của lệnh đó và truyền mã đấy vào thanh ghi rax nhưng đây là linux chứ không phải asm nên ta đổi nó sang hệ 16 tiếp theo xác định lệnh đấy có bao nhiêu tham số và truyền lần lượt các tham số vào các thanh ghi rdi,rsi,rdx,... tuy nhiên hầu hết các lệnh mà ta dùng chỉ cần tối đa 3 tham số là đủ và với lệnh execve cũng vậy lệnh execve có 3 tham số với ![image](https://hackmd.io/_uploads/r1Q1s5Kg-x.png) lúc này ta chỉ muốn thực thi shell chứ cũng không cần truyền gì vào nên 2 tham số phía sau ta cho nó bằng null(0);lúc này gần như ta đã nắm được cấu trúc lệnh mà ta muốn là pop rdi,pop rsi,pop rdx,pop rax,syscall lúc này ta chỉ cần tìm các gadget trên bằng lệnh ROPgadget --binary tên file nó sẽ ra tất cả các gadget còn muốn tìm 1 gadget cụ thể thì ta dùng lệnh trên kèm bộ lọc thì lệnh sẽ là ROPgadget --binary tên file |grep "lệnh" -Challenge:https://drive.google.com/file/d/1zr9EWGfMQxB7Z_pAbKgj08lMJaUej3Xp/view Trước tiên ta checksec để kiểm tra cơ chế bảo mật rồi mới lựa chọn kĩ thuật bypass phù hợp ![image](https://hackmd.io/_uploads/rkCa7Vfb-e.png) Ta thấy cơ chế Canary và NX hoạt động đặc biệt là cơ chế NX vì nó khiến ta không thể shellcode inject trực tiếp;sau đó kiểm tra nhanh liên kết của file binary là động hay tĩnh ![image](https://hackmd.io/_uploads/SyEPNEMZ-l.png) Với file này thì là liên kết tĩnh;lúc này ta nghĩ ngay đễ kĩ thuật ropchain vì liên kết tĩnh thì k có hàm system và cơ chế NX buộc ta phải gom nhặt các gadget để thực thi shell.Nhưng trước tiên ta phải xác định được gadget mình cần tìm là gì.Các lệnh cần thiết của asm dùng để bypass thì thường chỉ có max là 3 tham số với 3 thanh ghi là rdi,rsi,rdx vậy ta phải tìm các gadget pop rdi,pop rsi,pop rdx để đưa tham số đã ghi đè vào từ stack đưa vào từng thanh ghi,tiếp theo thì muốn gọi kernel thực hiện 1 lệnh gì đó ta phải truyền tham số vào thanh ghi rax và ghi truyền đủ rồi thì ta phải syscall thì mới bắt đầu thực hiện lệnh nên 2 gadget ta phải tìm nữa là pop rax và syscall.Ta dùng ROPgadget --binary bof4 |grep 'tên lệnh' để kiếm gadget cụ thể -Lưu ý khi chọn gadget:nếu có thể hãy chọn gadget chỉ có mỗi lệnh ta cần tìm kèm theo ret chẳng hạn như ![image](https://hackmd.io/_uploads/rk0VsVMWWx.png) -Nếu không kiếm ra lệch sạch như vậy thì hãy nhìn vào đống gadget ấy loại bỏ các lệnh có chứa jump vì không biết nó sẽ jump đi đâu và điều kiện có thỏa để nó jump hay không;các gadget chứa giá trị của địa chỉ mà thanh ghi nào đó trỏ đến cũng bỏ đi;ưu tiên lệnh mà mình đang tìm ở càng gần lệnh đầu càng tốt. Với challenge này sau khi dùng các lưu ý chọn gadget phía trên ta chọn được các gadget sau ![image](https://hackmd.io/_uploads/SyiWRNfbZe.png) ![image](https://hackmd.io/_uploads/By0GREM-Zg.png) ![image](https://hackmd.io/_uploads/ryu7AEfbWl.png) ![image](https://hackmd.io/_uploads/rkW4REz-Wg.png) ![image](https://hackmd.io/_uploads/r1uVA4GZWl.png) Ở gadget syscall nếu như syscall đó là gadget cuối và sau đó không còn thực hiện thao tác nào nữa thì ta lấy syscall không cần ret còn nếu sau syscall mà ta vẫn thao tác thì ta cần phải tìm syscall có ret phía sau.Với bài này ta chỉ cần thiết lập được lệnh execve để thực thi hàm shell là đã xong rồi nên syscall không cần ret.Tiếp theo ta đưa chúng lên script để thiết lập thành 1 lệnh execve có dạng như sau execve("/bin/sh",0,0).Đầu tiên ta đặt tên cho địa chỉ của từng lệnh bằng chính tên của nó để dễ nhận biết ![image](https://hackmd.io/_uploads/Sk-3V44-Ze.png) Lúc này ta phải ghi đè lên save rip bằng địa chỉ của lệnh pop để bắt đầu kế hoạch của chúng ta nhưng muốn ghi đè lên save rip trên stack thì ta phải ghi đè lên cả biến cục bộ và old rbp thì mới tới được save rip.Khi đọc ida bằng code C ta thấy chương trình có khai báo 1 biến v8 có giá trị là 80byte ![image](https://hackmd.io/_uploads/H104UN4Zbe.png) cộng thêm 8byte của old rbp là 88 byte nghĩa là muốn ghi đè lên save rip thì ta phải nhập trước 88 byte và byte thứ 89 chính là ghi đè lên save rip rồi. :::warning Khi hiểu rõ về stack thì ta sẽ biết old rbp lưu địa chỉ của stack frame cũ nhưng nếu ghi đè nó lên bằng byte rác thì lần sau khi làm việc với stack thì chắc chắn chương trình crash nhưng nhấn mạnh là lần sau và với lần thì nó vẫn pop save rip ra rồi jump đến đấy như thường nhưng nếu lần sau làm việc với stack thì nó nhảy đến 1 địa chỉ rác lúc đầu vẫn sẽ thao tác thêm biến cục bộ các thứ như bình thường nhưng sẽ không ret được và làm cho chương trình crash ::: Ta viết lệnh nhập vào chương trình 88 byte ![image](https://hackmd.io/_uploads/B12hyKEZ-x.png) tiếp theo thì ta sẽ ghi đè lên save rip và thực hiện thao tác nhập vào các tham số cho các thanh ghi :::warning Lưu ý: khi bắt buộc phải chọn các gadget không clean mà có các lệnh khác trong đó -Để ý xem lệnh đó có ảnh hưởng lên stack hay không(có làm thay đổi rsp không nếu có thì phải chỉnh lại bằng payload) -Liệu nó có truy cập vào vùng nhớ qua con trỏ hay không nếu có thì phải bỏ đi gadget đấy trừ khi biết được con trỏ đó hợp lệ -Liệu nó có làm thay đổi thanh ghi quan trọng mình đang cần dùng không nếu có thì hãy đổi thứ tự truyền tham số cho các thanh ghi còn không thì vẫn dùng bình thường ::: -Trong các gadget mình chọn thì có gadget pop rdx là không clean và nó add rsp 0x28 nghĩa là thay đổi rsp mà ta không thể sub rsp 0x28 được nên ta phải thêm vào stack 0x28 byte rác luôn bằng cách dùng payload+=b'a'* 0x28 -Ở tham số của rdi ta muốn truyền vào 1 chuỗi "/bin/sh".Lúc này ta sẽ gặp 1 vấn đề là tham số mà mình muốn truyền vào rdi là 1 chuỗi "/bin/sh" và khi gọi lệnh execve thì nó sẽ tự động bỏ qua 2 dấu " mà sẽ đọc đường dẫn tuyệt đối bên trong vậy ta sẽ truyền chuỗi kí tự này vào rdi như nào?Đáp án chính là dùng con trỏ(con trỏ chuỗi).Muốn truyền vào 1 chuỗi thì ta cần tìm địa chỉ của chuỗi đó và cơ chế của hệ thống khi đọc tham số là có 2 trường hợp;1 là tham số đó là 1 giá trị,2 tham số đó là con trỏ;nghĩa là ta chỉ cần đưa vào thanh ghi đó địa chỉ trỏ đến dữ liệu mà ta cần truyền vào thì nó sẽ tự động đưa dữ liệu ở nơi đến vào;1 lưu ý nữa là vì là con trỏ nên nếu ta thay đổi giá trị tại địa chỉ đó thì giá trị nhập vào thanh ghi cũng sẽ thay đổi -Ở đây là chuỗi /bin/sh nên trước tiên ta tìm địa chỉ của nó bằng lệnh search pattern "/bin/sh" ![image](https://hackmd.io/_uploads/SkkwVzLb-e.png) -Nó hiện như này nghĩa là không có và khi không có thì buộc ta phải nhập chuỗi vào 1 địa chỉ.Vậy ta phải chọn địa chỉ như nào.Ta dùng vm để xem các quyền hạn và tìm nơi có địa chỉ tuyệt đối và có quyền rw(readwrite) để ta có thể ghi dữ liệu vào đó.Sau đó ta copy địa chỉ bắt đầu của nó và dùng lệnh x/50xg +địa chỉ để hiện 1 lượt 50 địa chỉ bắt đầu từ địa chỉ đó tìm đến 1 nơi ở khoảng giữa giữa của địa chỉ đó,không có dữ liệu để tránh ghi đè.Chọn 1 địa chỉ và đặt tên cho nó trên script sau đó ta sẽ thêm vào địa chỉ của hàm gets để nhập dữ liệu vào địa chỉ đấy(hầu hết 99% các hàm đều mang theo lệnh ret nên cứ yên tâm gọi nó ra mà không phải lo rằng nó sẽ không jump được vào địa chỉ mà mình muốn);1 lưu ý khác khi gọi hàm để nhập dữ liệu vào là ta phải gọi hàm nhập đó ngay sau khi kết thúc gadget chứa tham số là địa chỉ trỏ đến nơi cần nhập dữ liệu vào Lúc này thay vì pop dữ liệu vào rdi thì ta sẽ pop địa chỉ trỏ đến chuỗi /bin/sh và để nhập chuỗi đó vào ta chỉ cần dùng p.sendline(b'/bin/sh')là đã nhập vào thành công rồi sau đó ta tiếp tục thao tác nhập dữ liệu vào các gadget khác ## Ret2shellcode không leak Khi NX off và với ret2shellcode không leak thì pie off có tư duy tương tự như ropchain với việc ta phải thực thi file /bin/sh nhưng với ropchain thì thì ta phải kiếm gadget có sẵn còn với bài này thì ta code thẳng vào stack luôn nhưng kĩ thuật này sẽ gặp phải 1 số vấn đề gây khó khăn như sau: -Ta phải biết được rằng với ret2shellcode không leak thì sẽ luôn có 1 thanh ghi trỏ đến shellcode và bạn phải chọn được đúng thanh ghi; với 1 lần input thì ta nên dùng debug để xem thanh ghi nào chứa chính xác dữ liệu mình vừa nhập và chọn thanh ghi đó còn nếu dữ liệu bị lệch đi thì ta phải tính offset.Nếu chọn đúng thanh ghi thì chắc chắn thanh ghi đó sẽ trỏ đến shellcode luôn.Còn nếu là 2 input thì ta xem thanh ghi nào có dữ liệu đủ lớn khoảng 80 byte trở đi và trỏ đến đúng từ nơi bắt đầu của dữ liệu và sẽ ưu tiên thanh ghi chứa input 1 hơn và phải tìm được gadget của nó nữa -Ta có các lệnh ``` asm( ''' code asm ''',arch='amd64') ``` b'' chuyển đổi kí tự hoặc chuỗi kí tự bên trong thành byte thô(byte máy tính hiểu được) p64()chuyển đổi địa chỉ hoặc giá trị bên trong nó thành byte thô u64(b'') chuyển đổi kí tự hoặc chuỗi kí tự bên trong thành giá trị có thể hiểu :::spoiler Tại sao jump thanh ghi lại có thể truy cập được vào shellcode? Ta chỉ cần hiểu đơn giản rằng con trỏ của thanh ghi đó đã được thiết lập sẵn để luôn trỏ vào shellcode và nếu ta tìm đúng thì khi ta jump đến thanh ghi đó thi thanh ghi RIP lúc này sẽ chứa giá trị mà con trỏ của thanh ghi đấy trỏ đến(adr shellcode) ::: ## **Ret2shellcode leak** Tương tự như bài không leak nhưng không có thanh ghi nào trỏ sẵn đến shellcode để thực thi nên ta phải đưa shellcode lên stack cụ thể là đưa vào biến buf mà muốn đưa vào biến buf trên stack ta phải leak được địa chỉ của save rbp và tính offset từ đấy đến biến buf và ghi đè lên save rip bằng địa chỉ của save rbp-offset Challenge:https://drive.google.com/file/d/1bAK9qYRAUCbWlgyEjFgqC_mgcqpu5lhi/view ![image](https://hackmd.io/_uploads/By_xIC9bWe.png) Đọc code ta thấy nếu biến v4 nhập là 1 thì nó chạy hàm get_name ![image](https://hackmd.io/_uploads/SJ27UCq-Wg.png) Ta thấy ở get_name thì có buf 80 byte mà hàm read cũng đọc vào 80byte nhưng hàm read không giống như gets là nó không tự động thêm byte null vào cuối vậy nên khi nhập vào 80byte nó sẽ leak được cả dữ liệu phía sau cụ thể là save rbp (giải quyết được 1 vấn đề) Viết shellcode lấy stackleak ![image](https://hackmd.io/_uploads/HJk8vCqZZl.png) Đầu tiên ta chọn v4=1 nên phải gửi v4=1 trước;sau đó ta dùng sendafter để gửi 80 byte vì ta không cần phải xuống hàng để bảo đảm adr được leak ra chuẩn nhất.Dùng revcuntil để bỏ qua 80byte đầu và dùng recv(6) để nhận 6 byte tiếp vì ta thấy stack chỉ có 6 byte adr và 2 byte null.Ta unpack nó để nó từ byte thô biến thành giá trị và dùng dùng log.info để in ra stackleak được -Vấn đề thứ 2 là ta phải tính được offset từ saverbp đến biến buf để ret đến đấy.Ta đọc tiếp hàm get_wish khi v4=2 ![image](https://hackmd.io/_uploads/Sy90LCc-Wg.png) Ta thấy biến buf ở đây chỉ 512 byte nhưng read lại đọc tận 544 byte vậy là lỗi buffer overflow.Ta dùng lỗi này để ghi đè save rip bằng địa chỉ của buf.Trước tiên ta debug để tính offset lấy địa chỉ leak được trừ cho địa chỉ đầu mà ta nhập dữ liệu vào và rsp trỏ đến sẽ ra được offset;dùng lệnh p/x adr-adr.Tiếp theo dùng lệnh tel để check xem cái save rbp mà mình leak được có trùng với cái save rbp gốc không nếu không thì ta trừ đi để trùng.![image](https://hackmd.io/_uploads/S1p_F0cZWx.png) Ta có ljust là lệnh dùng để lấp đầy đến 1 số lượng byte nhất định bằng byte null nếu ở đó không có dữ liệu và mục tiêu của mình là cho nó bao phủ luôn cả save rbp để ta chỉ cần ghi đè lên rip là được mà cái vùng bao phủ của mình hiện tại đang ghi đè lên cả save rip bởi vì theo tính toán trước đó thì ljust của mình sẽ phủ hết save rbp cũ nhưng save rbp cũ của mình bị lệch offset nên ta phải chỉnh để nó bao phủ đúng save rbp.Lúc này viết shellcode là done. # Ret2libc Là kĩ thuật để pass qua cơ chế NX với trường hợp file là liên kết động và ta không tìm đủ gadget ![image](https://hackmd.io/_uploads/Hkp4Ab3WZe.png) ![image](https://hackmd.io/_uploads/SkIUAZ2ZWx.png) Mục tiêu của kĩ thuật này là thực thi hàm system("/bin/sh") ở trên libc.Vậy muốn thực thi hàm này thì ta phải biết được địa chỉ của libc thì mới nhảy đến đấy và thực thi hàm system được. Challenge:[Link] ![image](https://hackmd.io/_uploads/BJjjkMnZ-l.png) Đọc code ta dễ dàng thấy biến buf được khai báo 80 byte nhưng hàm read lại đọc vào tận 120 byte(lỗi buffer overflow) Sau đó ta checksec:![image](https://hackmd.io/_uploads/SyyWGfnWWl.png) Checksec thấy cơ chế NX bật thì check xem nó là liên kết động hay tĩnh:![image](https://hackmd.io/_uploads/BJSXGz3bZl.png) Ta thấy cơ chế NX bật và liên kết động thì nghĩ ngay đến ret2libc -Đầu tiên ta phải leak địa chỉ libc.Ta tận dụng buffer overflow để ghi đè lên save rip.Ta tiếp tục tư duy rằng muốn leak được địa chỉ của 1 cái gì đó thì phải có được địa chỉ của 1 cái nằm trong nó để tính được offset thì mới suy ra được địa chỉ của cái cần tính.Với bài này là hàm 'puts' ta có hàm puts là 1 hàm trên libc và cơ chế của hàm puts là in ra với 1 tham số duy nhất là rdi và ta phải tận dụng hàm put để in ra địa chỉ hàm puts ## Stack pivot -Là kĩ thuật tận dụng 2 lệnh ret và leave của các hàm để fake stack sang 1 nơi khác -Nghĩa là ta sẽ ghi đè để thay đổi save rbp trên stack sang 1 nơi khác và khi end 1 hàm 2 lệnh ret và leave sẽ biến rsp thành nơi fake stack vậy là đã thành công -Giải thích:lệnh leave sẽ bao gồm 2 lệnh là ``` mov rsp,rbp pop rbp ``` nên khi thay đổi save rbp thì leave sẽ thay đổi rsp thành rbp luôn nên khi ret về hàm khác thì khi tương tác với rsp và rbp thì nó sẽ là nơi mình fake stack sang * Stack pivot thay đổi biến: -Vì cũng là stack pivot nên nó cũng chỉ đơn giản là fake stack sang nơi khác.Tuy nhiên thay vì fake những cái cốt lõi của stack như save rbp save rip và canary thì fake cả cái stack của hàm đấy -Kĩ thuật này thì rất khó để có thể áp dụng được nhưng ta có thể tưởng tượng đơn giản qua ví dụ sau: :::info Ta có 1 hàm có 2 biến là v3[32] và v4[32] trong đó v3 được khai báo trước và ta chỉ có lệnh read vào v4.Tuy nhiên có 1 điều kiện kiểm tra 3 phần tử đầu của v3 nếu nó lần lượt là 0x13371337 ;0xdeadbeef;0xcafebabe thì nó sẽ nhảy vào win và thực hiện system('/bin/sh') -Bài này không có canary nên ta chỉ có thể ghi đè vào 2 byte cuối của save rbp.Và ta chỉ thể nhập vào v4 nhưng dữ liệu ta cần thay đổi là v3 cơ -Vậy giờ ta dùng kĩ thuật stack pivot để fake stack sao cho ở stack cũ mình nhập vào v4 nhưng trong stack mới thì dữ liệu đó đang nằm ở v3.Và khi kiểm tra ở main thì nó lại thấy v3 đúng là mấy dữ liệu nó muốn => Thỏa mãn điều kiện và có được flag :::