Pwn/Exploit - Lỗi Tràn Bộ Đệm (Phần 1)

Thảo luận trong 'Exploitation' bắt đầu bởi minhslyfox, 31/12/16, 08:12 AM.

  1. minhslyfox

    minhslyfox W-------

    Tham gia: 11/03/14, 06:03 PM
    Bài viết: 9
    Đã được thích: 13
    Điểm thành tích:
    18
    Xin chào các bạn, hôm nay mình xin trình bày 1 chuỗi các bài chia sẻ và hướng dẫn về khai thác lỗi phần mềm (exploit/pwn). Trong nội dung bài viết mình có tham khảo từ cuốn sách Nghệ thuật tận dụng lỗi phần mềm – Tác giả Nguyễn Thành Nam và nhiều bài viết khác. Mình sẽ cố gắng diễn tả thật rõ ràng và cặn kẽ với mục đích giúp đỡ các bạn học sinh, sinh viên đến với pwn 1 cách dễ hiểu nhất mà mình có thể.

    Đầu tiên ta cần nắm về stack

    Stack: là một vùng bộ nhớ được hệ điều hành cấp phát sẵn cho chương trình khi nạp. Chương trình sẽ sử dụng vùng nhớ này để chứa các biến cục bộ ( local variable) và lưu lại quá trình gọi hàm, thực thi của chương trình.

    Stack hoạt động theo nguyên tác vào sau ra trước ( Last In First Out). Thanh ghi ESP ( Stack Pointer) lưu địa chỉ đỉnh của stack. Trong kiến trúc Intel x86, mỗi một ô trong stack là 4 byte, tương ứng với 32 bit. Stack có 2 thao tác là PUSH và POP. PUSH sẽ đẩy 1 giá trị ô nhớ vào đỉnh của stack, đồng thời esp sẽ tự động tăng lên 1 ô nhớ. POP sẽ lấy ra 1 ô nhớ của đỉnh stack ra, đồng thời esp sẽ giảm giá trị xuống 1 ô nhớ.

    Ví dụ:

    Mã:
    08048449 PUSH 0x08048580
    
    0804844E CALL printf
    
    08048453 ADD ESP, 0x10 
    Trước khi gọi hàm printf thì ta phải truyền vào tham số địa chỉ chuỗi cần in (PUSH 0x08048580) .

    Cấu trúc một hàm: Một hàm ( Function ) khi được biên dịch thì trình biên dịch sẽ tạo ra một hàm tương ứng gồm 3 phần:

    - Phần dẫn nhập ( prolog): là phần đầu của một hàm, có nhiệm vụ lưu trữ thông tin về vùng nhớ của hàm gọi và cấp phát bộ nhớ cho các biến nội bộ trước khi thân hàm được thực thi.

    + Thông thường phần dẫn nhập gồm 2 dòng lệnh chính là 1 dòng lệnh phụ như sau:

    Mã:
    PUSH        EBP
    
    MOV        EBP, ESP
    
    SUB        ESP, 0x20
    + Hai dòng lệnh đầu lưu giá trị của thanh ghi EBP vào stack, sau đó gán giá trị của ESP vào EBP. Lệnh thứ 3 dùng để cấp phát bộ nhớ cho stack, ở đây là 0x20 byte. Đây là phần bộ nhớ được cấp phát cho tất cả các biến nội bộ trong hàm. Như vậy giá trị của vùng nhớ này ( 0x20) phải lớn hơn hoặc bằng tổng độ lớn của các biến nội bộ. Biến nội bộ kiểu int thì sẽ được cấp phát 4 byte, kiểu char thì 1 byte. Kiểu short là 2 byte nhưng sẽ được cấp 4 byte để tối ưu hóa tốc độ truy cập ( mổi ô stack là 4 byte).

    + Trong vùng nhớ được cấp phát thì vùng nhớ của các biến nội bộ sẽ được cấp phát theo thứ tự các biến nội bộ xuất hiện trong code nguồn C.
    - Phần thân: là phần chính của hàm, bao gồm các lệnh thực hiện nhiệm vụ của hàm. Giá trị trả về của 1 hàm sẽ được lưu trong thanh ghi EAX. Trong thân hàm giá trị của thanh ghi EBP không thay đổi.

    - Phần kết thúc ( epilog): là phần cuối của hàm, có nhiệm vụ hủy bỏ vùng nhớ đã được cấp phát ở phần dẫn nhập đồng thời chuyển lại vùng nhớ của hàm gọi.

    + Cũng như phần dẫn nhập, phần kết thúc gồm 2 lệnh chính và 1 lệnh phụ:

    Mã:
    MOV     ESP, EBP
    
    POP         EBP
    
    RET
    
    + Hai lệnh đầu tiên thực hiện tác vụ đảo của phần dẫn nhập. Gán giá trị EBP vào ESP. Sau đó khôi phục giá trị EBP đã được PUSH ở phần dẫn nhập về ( POP EBP). Hai lệnh này có thể thay bằng lệnh LEAVE. Lệnh RET sẽ thay đổi luồng điều khiển quay về hàm gọi.
    Cấu trúc stack của 1 hàm:
    [​IMG]


    Trong 1 chương trình thì sẽ có nhiều hàm. Hàm lớn gọi hàm nhỏ. Giả sử ta đang ở trong 1 hàm lớn là hàm main, thì khi gọi 1 hàm nhỏ ở hàm lớn sẽ có các bước như sau:
    • Gán các biến đầu vào argv0, argv1 vào đỉnh của stack. Chương trình sẽ dùng các lệnh PUSH các biến vào đỉnh của stack hoặc dùng lệnh MOV esp, giá trị biến để gán vào stack.
    • Gán giá trị địa chỉ câu lệnh tiếp theo sau khi gọi hàm con vào stack. (saved eip)
    Trong hàm nhỏ phần prolog sẽ lưu trữ giá trị ebp của hàm lớn vào stack (saved ebp) bằng lệnh PUSH ebp. Sau đó là lưu lại giá trị esp của hàm lớn thành ebp của hàm nhỏ bằng lệnh MOV ebp, esp. Và cấp phát vùng nhớ cho stack cho hàm con sử dụng bằng lệnh SUB esp, <độ dài vủng nhớ cấp phát>.

    Phần epilog (phần kết thúc) thường kết thúc bằng 2 lệnh là leave và retn. Lệnh leave thực hiện 2 việc là MOV esp, ebp (gán giá trị esp về giá trị ebp ban đầu, thao tác này là thao tác lấy lại vùng nhớ đã cấp phát ở phần đầu hàm con) và việc thứ 2 là POP ebp ( lúc này esp đã bị gán về vị trí lưu saved ebp, nên lệnh này sẽ gán giá trị saved ebp vào thanh ghi ebp). Lệnh retn sẽ thực hiện POP eip, gán giá trị saved eip trong stack vào thanh ghi eip.
    Tràn Bộ Đệm

    Tràn bộ đệm là lỗi xảy ra khi dữ liệu xử lý (thường là dữ liệu nhập) dài quá giới hạn của vùng nhớ chứa nó. Tuy nhiên, nếu phía sau vùng nhớ này có chứa những dữ liệu quan trọng tới quá trình thực thi của chương trình thì dữ liệu dư có thể sẽ làm hỏng các dữ liệu quan trọng này. Tùy thuộc vào cách xử lý của chương trình đối với các dữ liệu quan trọng mà người tận dụng lỗi có thể điều khiển chương trình thực hiện tác vụ mong muốn.

    – Thay đổi giá trị của biến:
    [​IMG]

    Hình stack1.c​


    Dễ thấy chương trình trên khai báo 2 biến cookie và buf. Biến buf 16 byte và biến cookie kiểu int 4 byte. Giá trị biến buf được người dùng nhập vào. Chương trình in ra giá trị biến cookie lúc đầu khai báo, in ra địa chỉ vùng nhớ của biến buf và biến cookie. Sau đó so sánh giá trị biến cookie với 0x41424344. Nếu bằng nhau sẽ in ra “You win!!”. Và nhiệm vụ của chúng ta là phải in ra dòng chữ đó.

    Biên dịch chương trình : gcc –o stack1 stack1.c

    Chạy thử chương trình:
    [​IMG]
    Hình: run stack1​


    Chúng ta cùng phân tích quá trình nạp bộ nhớ của chương trình bằng edb:
    [​IMG]
    Hình: stack1 memory map​


    Địa chỉ của biến buf là 0xbffff47c và địa chỉ của biến cookie là 0xbfff48c. Chúng ta theo dõi trong bảng bộ nhớ kế bên thì sẽ thấy được theo sơ đồ bộ nhớ thì khi ta nhập giá trị “aaaaaaaa” với độ dài 8 byte vào biến buf thì sẽ được ghi vào bộ nhớ từ trên xuống dưới. Do ta khai báo trong source code biến buff độ dài 16 byte, biến cookie kiểu int độ dài 4 byte. Do chúng ta không kiểm tra dữ liệu nhập vào nên nếu cố tình nhập vượt quá giá trị khai báo thì chúng ta dễ dàng ghi đè lên giá trị của biến cookie.

    Như vậy chúng ta chỉ cần ghi đè lên biến cookie đúng giá trị 0x41424344 là sẽ thỏa mãn yêu cầu bài toán. Mã 0x41424344 khi chuyển qua string sẽ là DCBA. Cụ thể ta nhập vào 16 byte cho biến buf và 4 byte ghi đè lên biến cookie.

    [​IMG]
    Hình: stack1 exploited memory map

    [​IMG]
    Hình: stack1 exploited​


    – Thay đổi luồng thực thi của chương trình

    Khi một chương trình mắc phải lỗi tràn bộ đệm thì ngoài việc có thể thay đổi giá trị của các biến người ta còn có thể thay đổi được luồng thực thi của chương trình.

    Chúng ta cùng xét đoạn code sau:

    [​IMG]
    Hình: stack2​


    Đoạn code trên cũng tương tự như đoạn code của ví dụ trước. Tuy nhiên ta không thể giải như ví dụ 1 bởi vì giá trị 0x41000041 khi chuyển qua string là những ký tự không in được nên không thể nhập từ bàn phím.

    Dùng lệnh objdump để phân tích mã hợp ngữ của chương trình. Ta chú ý hàm main:

    [​IMG]
    Hình: stack2 asm code​


    Như ta đã biết thì khi gọi 1 hàm thì đầu tiên chương trình sẽ tạo 1 stack và lưu địa chỉ trả về vào như hình dưới đây :

    [​IMG]
    Hình: Stack contruct​


    Như vậy ý tưởng của ta sẽ là dùng lỗi tràn bộ đệm để thay đổi giá trị EIP trả về sao cho giá trị con trỏ EIP trỏ về nơi ta mong muốn. Theo dõi hàm main ta thấy được ở địa chỉ 0804849a là lệnh compare so sánh với giá trị 1. Và địa chỉ 080484a8 là lệnh gọi hàm in ra màn hình. Tuy nhiên trước khi gọi hàm in ra màn hình ta phải truyền tham số cho hàm, ở đây là giá trị chuỗi ‘You win !!’. Như vậy ta cần trỏ con trỏ EIP về giá trị 080484a1.

    Lời gọi hàm check có địa chỉ là 08048491. Như vậy EIP trả về của hàm check sẽ là 080484a6.

    Chúng ta theo dõi bảng stack của hàm check :

    [​IMG]
    Hình 2-9: Stack check func​


    Đếm từ nơi bắt đầu biến buf ta dễ thấy cần 8*4=32 byte và 4 byte ghi đè giá trị EIP trả về. Như vậy chúng ta khai thác như sau:

    [​IMG]
    Hình: stack2 exploited​


    Một số lưu ý:

    - Hiện nay các trình biên dịch đều có các tính năng bảo mật như: ASLR (ngẫu nhiên hóa sơ đồ không gian địa chỉ - Address space layout randomization), NX ( No eXcute: không cho thực thi shellcode lưu trong stack), canary (gán 1 giá trị random vào trước saved ebp, nếu giá trị này thay đổi khi ta tràn bộ đệm thì chương trình sẽ stop), …

    - Disable ASLR: echo 0 > /proc/sys/kernel/randomize_va_space

    + 0 : Disable ASLR. This setting is applied if the kernel is booted with the norandmaps boot parameter.

    + 1 : Randomize the positions of the stack, virtual dynamic shared object (VDSO) page, and shared memory regions. The base address of the data segment is located immediately after the end of the executable code segment.

    + 2 : Randomize the positions of the stack, VDSO page, shared memory regions, and the data segment. This is the default setting.

    - gcc -fno-stack-protector -z execstack classic.c -o classic : đây là câu lệnh biên dịch chương trình để tắt canary và tắt cờ NX (No eXcute)

    - socat TCP-LISTEN:2323,reuseaddr,fork EXEC:"./sftp" : Lệnh này sẽ chạy binary ./sftp dưới mô hình client-server. Như vậy ta có thể tương tác với chương trình từ các máy khác bằng lệnh nc ip port hoặc lập trình socket.

    - ndisasm -b 32 filename > filename.asm : Lệnh này sẽ chuyển các mã trong filename thành code asm.

    - msfvenom -p linux/x86/exec CMD="/bin/cat flag.txt" -a x86 -b '\x00' -f python : Lệnh này sẽ tạo ra 1 shellcode thực thi lệnh CMD, tham số -b là các bad character cần tránh xuất hiện trong shellcode

    - Một số shellcode /bin/sh mình thường dùng:

    + shell linux 64:
    Mã:
    27 code = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
    + shell linux 32:
    Mã:
    code32 = '\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80'
    - payload = shellcode.ljust(200,'A') : đây là 1 lệnh trong python, nó sẽ tự động tạo 1 chuỗi độ dài 200, với phần shellcode được đặt ở bên trái của chuỗi, những giá trị còn lại trong chuỗi sẽ là những ký tự A
     
    Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
    Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
    Cord thích bài này.
  2. nkimtuan

    nkimtuan W-------

    Tham gia: 01/12/13, 03:12 PM
    Bài viết: 11
    Đã được thích: 4
    Điểm thành tích:
    8
    Phần lý thuyết về stack, function và buffer over error bạn viết sai và thiếu nhiều quá. Hình như bạn chỉ biết làm một cách mà mẩn mà không hiểu gì hết.
    Nhưng không sao, làm được là tốt rồi :)
     
    Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
    Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
  3. whf

    whf Super Moderator Thành viên BQT

    Tham gia: 06/07/13, 03:07 AM
    Bài viết: 1,057
    Đã được thích: 700
    Điểm thành tích:
    113
    Nếu được thì bạn có thể viết một bài chia sẻ để mọi người cùng nhau học hỏi nhé aaaa
     
    Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
    Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
  4. reutdpwn

    reutdpwn New Member

    Tham gia: 29/04/17, 11:04 PM
    Bài viết: 2
    Đã được thích: 0
    Điểm thành tích:
    1
    Theo mình thì ngoài hình ảnh bị lỗi (do mình thấy ko liên quan nên đoán vậy) thì bài này chỉnh ảnh lại lỗi là ổn. Mình cũng mong bạn nkimtuan chia sẻ bài viết về pwn/exploit cơ bản
     
    Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
    Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
  5. nkimtuan

    nkimtuan W-------

    Tham gia: 01/12/13, 03:12 PM
    Bài viết: 11
    Đã được thích: 4
    Điểm thành tích:
    8
    Vâng, cảm ơn các bạn whf và ReUTD..., mình sẽ cố gắng có các bài viết liên quan đến vấn đề này trong một thời gian sớm nhất.
     
    Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
    Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan
    whf and sunny like this.
  6. Happy_Clown

    Happy_Clown New Member

    Tham gia: 06/07/19, 02:07 PM
    Bài viết: 1
    Đã được thích: 0
    Điểm thành tích:
    1
    Ở phần tràn bộ đệm em cũng thực hành tương tự như ad hướng dẫn mà sao của em nó không hiện lên dòng chữ "You Win" ạ?
    Có phải vì máy em sử dụng Linux 64bit nên mới như vậy ạ(Em có thử trên máy chạy Win32 bit thì nó mới hiện lên)? Mò cả ngày rồi mà vẫn ko sửa được :((
     
    Mời các bạn tham gia Group WhiteHat để thảo luận và cập nhật tin tức an ninh mạng hàng ngày.
    Lưu ý từ WhiteHat: Kiến thức an ninh mạng để phòng chống, không làm điều xấu. Luật pháp liên quan