[Night St0rm CTF] - WRITE UP PWN

Ban đầu cũng không có ý định viết write up bởi cũng rất lười viết :v . Nhưng nghe phong phanh được tin là có thưởng cho write up hay và thú vị :) thế là lại phải viết rồi :)
Cũng mong BTC đọc được dòng này rồi trao giải cho mình :)) vì để viết được chỗ write up này mình phải ngồi download lại file cũng như ngồi debug lại để chụp ảnh rồi balabala :)) ( Nói chung là kể khổ :v )

* PWN 1 - 50 pts

nc motacoin.nightst0rm.net 1337

Bước đầu tiên khi mình làm pwn là luôn kéo file binary vào IDA để hex-rays đọc code c :))
Ở bài này thì mình có được đoạn code sau :))


Thoạt nhìn qua thì mình đã copy nguyên xâu "KhongGiongTrenServer" để send lên server vì thấy là hàm memcmp chứ cũng chẳng quan tâm xâu đấy ý nghĩa là gì :v
Nhưng sau khi gửi lên thì thấy không thành công mới đọc kĩ lại @@
"KhongGiongTrenServer" bây giờ thì mới hiểu ý nghĩa của nó :v mình cũng nghĩ ngay ra cách khác để nhập vào bài này, tuy nhiên để cho mọi người hiểu rõ hơn mình sẽ phân tích cụ thể chức năng của các hàm được sử dụng trong bài này như sau.

read(0, &s, 0x100);
Hàm trên đọc tối đa 0x100 byte từ con trỏ stdin(value = 0) và sẽ được lưu tại vùng nhớ có địa chỉ là địa chỉ của biến s. Với read chúng ta không cần phải kết thúc bởi '\n' ( '\x0a' ) nên có thể đưa vào bất kì ký tự gì mà mình mong muốn.

strlen(&s);
Hàm trên sử dụng để tính length của một xâu, length của một xâu được tính bằng số các byte khác null liên tiếp nhau kể tử vị trí đầu của byte.
memcmp(&s, "KhongGiongTrenServer", v3);
Hàm trên sử dụng để so sánh v3 byte đầu của xâu được trỏ bởi &s và xâu "KhongGiongTrenServer". Kết quả trả về là 0 nếu v3 byte đầu của hai xâu đó bằng nhau hoặc trả về != 0 nếu không có một byte trong v3 byte đầu khác nhau.
_________________________________
Phần trên là phần phân tích các hàm quan trọng của trường trình, dưới đây sẽ là ý tưởng khai thác của mình.
Trước tiên để vào được system('/bin/sh'); thí kết của của hàm memcmp phải được trả về = 0
Để làm được điều này thì số ký tự mà chúng ta nhập vào phải đúng bằng số ký tự đầu tiên của xâu được mang ra so sánh trên server, điều này có vẻ khó???
Tuy nhiên vẫn còn một cách đơn giản hơn là làm sao cho v3 = 0;
Nếu v3 = 0 thì memcmp luôn luôn = 0 mà ko cần quan tâm đến xâu được đem ra so sánh trên server là gì.
=> Để v3 = 0 thì length của xâu nhập vào cũng phải = 0
=> xâu nhập vào phải bắt đầu bằng byte null.
=> Lời giải của bài này là nhập vào 1 byte null.


File: solve.py

* PWN 2 - 100 pts

nc banana.nightst0rm.net 1337

Ở bài này thì hôm thi chính thức tại KMA có chút vấn đề về việc up file.
Bạn đầu mình chạy server tại địa chỉ "nc banana.nightst0rm.net 1337" thì thấy có in ra dòng chữ 
"intput username/passsword" nhưng mở file bin ra thì không thấy có code puts hay printf xâu như thế mình đã thắc mắc, nhưng lại nghĩ liệu có gì đó troll ở đây :))
Tiếp tục ngồi làm với file binary ban đầu thì mình càng thấy khó và thấy bế tắc..............
Sau 4 tiếng ngồi nhìn code trong vô vọng từ 8h30 sáng đến 12h30 chiều thì cuối cùng mình cũng gặp được anh ra đề, sau một lúc nói chuyện thì anh phát hiện ra là up nhầm file..... và lúc đó mình cũng vừa chuyển sang làm bài pwn 3-150 nên không làm bài này nữa mà về nhà mới làm lại vì theo như anh nói thì mình cũng có hướng làm bài này rồi nên ko đọc lại nữa (vì có làm cũng ko được giải mà :v )
Với file binary mới thì các công đoạn làm như sau.
Cũng như bài trước việc đầu tiên là vất vào IDA:



Nhìn thì thấy chương trình có sẵn một hàm goisystem(int a1) sẽ giúp thực hiện lệnh system('sh').
Tuy nhiên có điều kiện check tham số truyền vào == 0x133700

Quay lại với hàm main() thì ở đây chỉ thực hiện một lệnh quan trọng là call tới hàm check()
==> Đọc hàm check thôi :v

Ở hàm check() cho phép nhập vào chương trình 2 lần, mỗi lần nhập sẽ kết thúc nếu gặp byte null hoặc '\x0a' ('\n') và gán giá trị cho byte cuối được nhập vào là '\x00'
==> Không giới hạn số lượng byte nhập vào
==> Có thể overflow.
==> Bài này mình suy nghĩ đến 2 hướng để làm:
+ Hướng 1:(Có vẻ phức tạp hơn nhưng lại hữu dụng hơn trong các trường hợp overflow)
    - Overflow return về puts để lấy địa chỉ GOT, leak libc rồi tính system với địa chỉ của '/bin/sh' sau đó quay lại check() để overflow lần tiếp theo.
+ Hướng 2:(Có vẻ đơn giản hơn nhưng chỉ dùng được với code này)
    - Bài này có sẵn hàm để call system('sh')
    --> return về goisystem() để call và truyền vào tham số: 0x133700
    --> tuy nhiên vì 0x133700 có byte null nên sẽ phải nhập vào thật khóe léo.
    --> nhập vào 0x1337xx với xx != null --> nhập vào xâu tiếp theo vừa đủ tới vị trí của xx để biến xx = 00

Với những gì phân tích ở trên thì mình có các lời giải như sau: solve.py có đầy đủ  cả 2 cách.
+ Cách 1:


+ Cách 2:


* PWN 3 - 150

nc dancecoin.nightst0rm.net 1337

Chưa cần biết thế nào cứ vất vào IDA đã nào :v 




Nhìn qua thì mình nhận thấy đây là một bài về bộ nhớ heap.
Với heap thì có rất nhiều các kĩ thuật tấn công phụ thuộc vào từng lỗ hỏng của chương trình tuy nhiên để thực hiện được các cuộc tấn công heap thì trước tiên cần phải hiểu được thế nào là heap.
Với những bạn mới tìm hiểu về heap thì mình có 2 link sau đây rất hữu hiệu để mọi người có thể học hỏi. ( mình cũng chưa hiểu hết được những kĩ thuật cũng như những kiến thức này ).
Link 1 :  Understanding glibc malloc
Link 2 : Shellphish

Quay trở lại với nội dung chính là giải quyết bài toán này.
Ban đầu mình dùng gdb dể debug tuy nhiên mình gặp đôi chút khó khăn về "Time up" Vì người ra đề để time rất nhỏ. Chính vì thế mình đã patch lại chương trình bỏ đi phần alarm để có thể debug được lâu hơn.

Ở bài này mình sẽ giới thiệu với các bạn heap newbie như mình một kĩ thuật khá đơn giản nhưng mình cũng phải chờ được thông não thì mới hiểu được :))

Trong cấp phát bộ nhớ heap thì có những thứ sau mà mọi người cần quan tâm.

pwndbg> bin
fastbins
0x10: 0x0
0x18: 0x0
0x20: 0x0
0x28: 0x0
0x30: 0x0
0x38: 0x0
0x40: 0x0
unsortedbin
all: 0x804c418 ◂— 0xf7f907b0
smallbins
empty
largebins
empty
pwndbg> 
Đó là các loại kích thước khi cấp phát sẽ được ưu tiên như thế nào.
Ở bài này, chúng ta thấy mỗi một lần create thì chương trình sẽ malloc một vùng nhớ có size là 8 và malloc một vùng nhớ với kích thước tùy ý do chúng ta chọn.
Như vậy nếu như tôi tạo 2 note với hai lần chọn kích thước là 100 ( Không thuộc fast bin) thì các địa chỉ được cấp phát như sau:

pwndbg> x/10x 0x804b080
0x804b080 <ga>: 0x00000000 0x0804c410 0x0804c488 0x00000000
0x804b090 <ga+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0a0 <ga+32>: 0x00000000 0x00000000
pwndbg> x/4x 0x0804c410
0x804c410: 0x0804c420 0x080487bb 0x00000000 0x00000069
pwndbg> x/4x 0x0804c488
0x804c488: 0x0804c498 0x080487bb 0x00000000 0x00000069
pwndbg> 

Sau đó tôi free 2 note đó thì sẽ có được kết quả như sau:

pwndbg> bins
fastbins
0x10: 0x804c408 —▸ 0x804c480 ◂— 0x0
0x18: 0x0
0x20: 0x0
0x28: 0x0
0x30: 0x0
0x38: 0x0
0x40: 0x0
unsortedbin
all: 0x804c418 ◂— 0xf7f907b0
smallbins
empty
largebins
empty
pwndbg> 
Nhận thấy ở fastbins 0x10 có 2 địa chỉ sẵn sàng được cấp phát, như vậy nếu ta cấp phát 2 vùng nhớ có kích thước là 8 liên tiếp nhau thì sẽ được cấp phát bởi 2 địa chỉ này.
Tôi create một note với kích thước là 8 và nhận được kết quả như sau:

pwndbg> bins
fastbins
0x10: 0x0
0x18: 0x0
0x20: 0x0
0x28: 0x0
0x30: 0x0
0x38: 0x0
0x40: 0x0
unsortedbin
all: 0x804c418 ◂— 0xf7f907b0
smallbins
empty
largebins
empty
pwndbg> x/10x 0x804b080
0x804b080 <ga>: 0x00000000 0x00000000 0x00000000 0x0804c410
0x804b090 <ga+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x804b0a0 <ga+32>: 0x00000000 0x00000000
pwndbg> x/4x 0x0804c410
0x804c410: 0x0804c488 0x080487bb 0x00000000 0x00000069
pwndbg> 
Như vậy là đã có thể ghi đè lên vùng nhớ của một note đã bị xóa trước đó.

Tuy nhiện khi note đã xóa thì lại không có cách nào để có thể sử dụng chức năng view() với note đó.
==> Quay lại hàm view() tôi thấy người ra đề không cho view trực tiếp trên flow chính mà lại được tạo ra với một thread khác, hàm doview() được viết như sau:



Sau khi hàm được gọi địa chỉ của note đã lập tức được lấy sau đó mới sleep chứ không sleep xong mới lấy nên chúng ta có thể tận dụng lỗi này của chương trình. Trong thời gian sleep có thể ghi đè lên vùng nhớ của note đó qua đó thực thi câu lệnh mà mình muốn.

Để thực hiện exploi thì tôi thực hiện các bước như sau:
Bước 1: Tạo 4 note với size = 100
Bước 2: View note 2
Bươc 3: Delete note 2 rồi đến note 1
Bước 4 create note với size = 8 và nội dung là puts got và địa chỉ hàm abcprint.
Bước 5: Chờ nhận về địa chỉ của puts rồi dùng libc tính địa chỉ của system.
Bước 6: Làm lại bước 2 và bước 3.
Bước 7: create note với size = 8 và nội dung là 'sh\x00\x00' + địa chỉ hàm system tính ở bước 5.
Bước 8: Chờ và gửi cat /home/pwn3/flag lên server để nhận về flag.

chung96vn@ubuntu:~/github/nightst0rm/pwn/pwn3-150$ python solve.py 
[*] '/home/chung96vn/github/nightst0rm/pwn/pwn3-150/libc.so'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to dancecoin.nightst0rm.net on port 1337: Done
[*] Add 4 note!
[*] View note 2!
[*] Delete 2 note
[*] Attack note
[*] Waitting...
[*] 0xf7644ca0
[*] 0xf75e5000
[*] 0xf761fda0
[*] View note 2!
[*] Delete 2 note
[*] Waitting...
[*] NightSt0rm{:'(_500k_vnd}
[*] Switching to interactive mode
NightSt0rm{:'(_500k_vnd}Times Up!
[*] Got EOF while reading in interactive
$  

File lời giải: solve.py

Comments

Popular posts from this blog

Exploit deaslr through _dl_runtime_resolve

WriteUp PWN 500pts - PwC Hackaday