Đăng trong Pwnable.tw

[Pwnable.tw] – applestore

Challenge

Screenshot from 2019-04-16 16-47-41.png

file applestore

Screenshot from 2019-04-16 16-57-40.png

checksec

Screenshot from 2019-04-16 16-58-46.png

run binary

Screenshot from 2019-04-16 17-00-58.png


Ghidra

main()

Screenshot from 2019-04-16 17-06-23.png

menu()

Screenshot from 2019-04-16 17-05-45.png

handler()

Screenshot from 2019-04-16 17-07-01Screenshot from 2019-04-16 17-07-19

list()

Screenshot from 2019-04-16 17-08-41.png

add()

Screenshot from 2019-04-16 17-09-25.pngScreenshot from 2019-04-16 17-09-37

create()

Screenshot from 2019-04-16 17-27-39.png

insert()

Screenshot from 2019-04-16 17-29-13.png

delete()

Screenshot from 2019-04-16 17-10-44.pngScreenshot from 2019-04-16 17-11-22

cart()

Screenshot from 2019-04-16 17-12-28.png

checkout()

Screenshot from 2019-04-16 17-12-58.png

struct PRODUCT

Screenshot from 2019-04-16 17-32-26.png

 


Analysis

Chương trình mô phỏng 1 cửa hàng bán điện thoại iphone, cho ta các lựa chọn tương ứng với các hàm:

  • list(): liệt kê các mặt hàng trong shop.
  • add(): thêm 1 sản phẩm vào giỏ hàng.
  • delete(): xóa 1 sản phẩm khỏi giỏ hàng.
  • cart(): kiểm tra giỏ hàng hiện tại, in ra cho người dùng, trả về tổng số tiền.
  • checkout(): kiểm tra nếu tổng tiền = 7174 $ thì sẽ tặng thưởng 1 iphone 8 với giá chỉ 1 $.

Phân tích pseudocode ta có 1 số nhận xét:

  • Các sản phẩm trong giỏ hàng được lưu trên heap dưới dạng 1 double linked list, mỗi node có kiểu là struct PRODUCT. Trong đó: PRODUCT.fd là con trỏ trỏ đến node tiếp theo trong list, PRODUCT.bk là con trỏ trỏ đến node phía trước.
  • Mỗi khi người dùng thêm sản phẩm mới, hàm create() được gọi để tạo 1 node mới và đưa vào hàm insert() để chèn node vào cuối danh sách.
  • Hàm delete() thực hiện xóa bỏ 1 node bằng cách thay đổi con trỏ .fd.bk của 2 node phía trước và phía sau nó. Hoàn toàn không có câu lệnh free() nào để giải phóng bộ nhớ cho node. Do đó không thể khai thác lỗi double freeUAF (use after free).
  • Trong hàm checkout(), khi kiểm tra điều kiện total = 7174 thỏa mãn, chương trình tạo 1 node mới để add vào cuối danh sách. Tuy nhiên node này lại không nằm trong heap mà nằm trên stack, do khai báo: PRODUCT product (lẽ ra phải là PRODUCT *product). Vì stack dễ bị thay đổi khi chương trình nhảy đến các hàm khác nên dữ liệu trên node mới này có thể điều khiển được.

 

 


Exploit

1. bypass checkout

Trước hết ta phải bypass điều kiện của checkout() để nhận được phần thưởng là iphone 8 giá 1$. Tta cần add 20 cái iphone 6 plus (index 2) và 6 cái iphone 6 (index 1) để đạt được điều kiện total = 20*299 + 6*199 = 7174 $. Cấu trúc stack khi load stack frame của hàm checkout() và khi load stack frame của hàm delete() tương ứng như sau:

Screenshot from 2019-04-16 19-01-22.png

Ta nhận thấy vị trí của biến product trong hàm checkout() nằm sau biến choose() trong hàm delete() 2 bytes. Như vậy, sau khi bypass checkout()  để tạo node product. Chương trình trở về hàm handler(). Lúc này nếu ta gọi hàm delete(), chương trình sẽ yêu cầu nhập lựa chọn và lưu vào biến choose, đây là lúc ta chèn payload để overwrite node product.

Vậy ta sẽ overwrite gì vào product? Đầu tiên ta cần leak libc address.


2. leak libc

Trong hàm delete(), sau khi đã delete node, có câu lệnh printf() để in P.name:

Screenshot from 2019-04-16 19-11-33.png

Do đó nếu ta ghi đè lên vị trí của P.name bằng địa chỉ của 1 hàm nào đó trong libc, câu lệnh trên sẽ in ra địa chỉ thực của hàm đó. payload như sau:

Screenshot from 2019-04-16 19-16-24.png

Có được atoi_address ta tính được libc_base_address, và từ đó tính ra system_addressbinsh_address:

(libc_base_address) = (atoi_address) – (offset_of_atoi_in_libc)

(system_address) = (libc_base_address) + (offset_of_system_in_libc)

Bước thứ 2 là ta cần leak được địa chỉ của stack.


3. leak stack

Ta sử dụng payload tương tự bên trên để leak địa chỉ của environ.

Screenshot from 2019-04-16 20-21-05.png

Vì environ nằm trên stack nên ta có thể tính được địa chỉ ebp của hàm Delete() dựa vào khoảng cách giữa ebp và environ:

Screenshot from 2019-04-16 19-34-44.pngScreenshot from 2019-04-16 19-32-22

(ebp_address) = (environ_address) – 0x104

 


4. overwrite .GOT and call system(“/bin/sh”)

challenge này được set Partial RELRO, nên ta có thể overwrite vùng .GOT. Vùng .GOT – Global offset table chứa bảng tra cứu địa chỉ thực của các hàm trong libc khi được load cùng với binary. Ta sẽ ghi đè vị trí của hàm atoi() thành hàm system(). Như thế mỗi khi gọi hàm atoi(), chương trình sẽ tra bảng .GOT và lấy địa chỉ của hàm system() ra mà execute.

Trong hàm delete(), để xóa 1 node khỏi danh sách chương trình thực hiện đoạn lệnh

Screenshot from 2019-04-16 19-46-06.png

Vì ta control được product.fd và product.bk nên có thể lợi dụng lệnh BK->fd = FD để thay đổi giá trị của ebp thành atoi_address + 0x22 bằng payload.

Screenshot from 2019-04-16 19-54-46.png

Như thế, sau khi trở về hàm handler(), ta truyền payload “system_address” + “;/bin/sh\x00”

Screenshot from 2019-04-16 20-08-00.png

Khi chương trình gọi hàm my_read() sẽ overwrite system_address vào vị trí của atoi_address trên vùng .GOT. Ngay sau đó là lời gọi hàm atoi() thực chất sẽ gọi hàm system() và ta có được shell.

Screenshot from 2019-04-16 19-51-53.png

 


Run exploit

Screenshot from 2019-04-16 20-09-43.pngScreenshot from 2019-04-16 20-10-01

 

Screenshot from 2019-04-16 20-11-19.jpg

 

 

Bình luận về bài viết này