calc — pwnable.tw

Nguyễn Tín
8 min readMay 2, 2020

--

###phân tích rõ các hàm trong challenge

hàm main

đầu tiên chương trình thực hiện gọi hàm ssignalalarm

The function ssignal() defines the action to take when the software  signal  with  number  signum  is raised  using  the  function gsignal(), and returns the previous such action or SIG_DFL.

nom na là chương trình sẽ gọi signal-handler với 2 args được pass là 14 và timeout. trong man 7 signal ta sẽ thấy 14 chính là SIGALRM . SIGALRM này dùng cho hàm kế tiếp đó chính là alarm để ngăn chương trình nếu không có tác động gì thì sẽ tự ngắt kết nối (arg timeout) nếu quá 60 giây.

puts thì write vào stdout sau đó fflush stdout.

— — — — — — — — — — — — — — — — —

bên trong hàm calc

vào trong hàm calc

  • khởi tạo biến count (với địa chỉ là ebp — 5A0h)
  • khởi tạo chuỗi int string[100] (với địa chỉ là ebp-59Ch chú ý là string nằm sau biến count 4 bytes)
  • khởi tạo chuỗi s
  • khởi tạo unsigned int v4

v4 = __readgsdword(0x14)

v4 đọc giá trị từ thanh ghiGS với số lượng là 0x14

— — — — — — — — — — — — — — —

hàm bzero

dùng để null các giá trị trong chuỗi s

The  bzero()  function sets the first n bytes of the area starting at s to zero (bytes containing '\0').

— — — — — — — — — —

hàm get_expr

pass vào 2 args là chuỗi s và 1024

  • khởi tạo biến s với kiểu int
  • khởi tạo ký tự v4
  • khởi tạo biến v5 = 0 với kiểu int

###

giải thích các điều kiện trong while

  • kiểm tra xem v5 có nhỏ hơn a2 (1024)
  • đọc 1 byte từ địa chỉ v4 kiểm tra xem ret value của read có khác -1(trả về -1 khi bị interupt bởi 1 signal) hay là kí tự newline hay không

###

vào trong hàm if thì nó chỉ đơn giản check các kí tự v4 có phải là các phép toán thông thường và là số≤ 9 hay không rồi sau đó gán cho chuỗi s là v4

###

phép toán gán cuối cùng chỉ là null phần tử cuối của dãy

###

tóm lại hàm get_expr sẽ đọc input từ user từng byte một, cho phép trong list [ +, -, *, \, %, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] lưu vào biến s. chuỗi s sẽ có length tối đa là 1024 được khởi tạo từ hàm bzero trước đó.

— — — — — — — — — — — — — —

hàm init_pool

nếu ta để ý thì bên dưới biến count chính là chuỗi string, hàm này công việc chính là khởi tạo giá trị cho chuỗi string[100] hay rõ hơn là string[i] = count[i+1]

— — — — — — — — — — — — — —

bây giờ đến hàm quan trọng nhất parse_expr

  • khởi tạo các biến kiểu int: v2, v4, v5, i, v7, v9 và unsigned int v11
  • khởi tạo kiểu char *s1, s_func[100]

kiểm tra trong chuỗi s[i] có kí tự [ +, -, *, /, %] hay không, nếu có thì sẽ lưu con số sau biểu thức vào s1. Sau đó thực hiện hàm if, nếu s1 == 0 thì sẽ báo lỗi nên input sau được cho là lỗi:

tiếp đến chuyển chuỗi s1 thành số lưu vào v9. Kiểm tra xem nếu v9 > 0 thì thêm v9 vào mảng s (vì s nằm sau biến count) và tăng biến count lên 1.
dễ hiểu hơn là string[count] = v9; count += 1 (ở đây ta hiểu v4 là 1 biến đếm)

tiếp sau đó sẽ check tiếp nếu trong chuỗi s[i+1] có thêm 1 ký tự nào trong [ +, -, *, /, %], nếu có báo ‘expression error!’ và v5 sẽ cập nhật là cắt bỏ chuỗi số ra khỏi s[i+1] sau đó count +=1

tiếp đó ta có thể thấy thì đoạn này chính là đoạn các phép toán logic được kiểm tra nhân chia trước cộng trừ sau nhưng như ta test thì nó sai tùm lum.

ta thấy chuỗi s_func chứa các phép toán được ưu tiên, nếu cái nào được giải quyết rồi thì nó sẽ loại bỏ bằng cách cho lùi biến đếm v7

bên trong hàm eval

để rõ hơn thì ta lấy ví dụ phép toán hiện tại là ‘+’ thì ta có count[*count -1] += count[*count], thì nó chính là string[*count -2] += string[*count -1] (vì v2 nằm sau count 4 byte), sau đó giảm biến count đi 1, như vậy kết quả cuối cùng đó chính là string[*count -1]

kết thúc hàm parse_expr thì nó sẽ in ra giá trị cuối cùng trong string[count -1]

bây giờ chúng ta đã hiểu được flow của chương trình như thế nào, nhưng có 1 điểm đáng chú ý trong quá trình chúng ta test binary

ta lấy ví dụ +13. khi ta thực thi hàm parse_expr , đầu tiên là dấu ‘+’. ta xét các biến của bin

  • i = 0
  • v2 = 0
  • s1 = NULL
  • v9 = 0
  • count = 1

lúc này do là dấu nên nó sẽ được thêm vào s_func

sau khi vòng lặp đi tới 1 và 3 thì lúc này

  • i = 2
  • v2 = 2
  • s1 = 13
  • v9 = 13

vì đã hết kí tự nên sẽ thực hiện hàm eval

sẽ thực hiện phép tính này

lúc này thì

  • count = 1 + 13 -1 = 13
  • string[] = {13}

phép tính trên thực chất là count = count + string[count -1] = 1 + 13 = 14. rồi sau đó chương trình trừ đi count nên còn 13.

kết thúc parse_expr thì chương trình lúc này sẽ in giá trị của vị trí count[13] = string[12] = 0

để rõ ràng hơn ta hãy nhìn trong gdb sau khi input vào +13

như vậy ta có thể thấy được ta đã có thể leak value trên stack
quay lại hàm main ta có thể thấy giá trị v4 này

nó chính là giá trị của canary. nếu ta xét count làm gốc thì canary có offset là +357(lấy 0x5A0–0xC rồi chia cho 4)

vậy nếu ta +357+1 thì sao?

oopsie vậy điều này xảy ra như thế nào? bỏ trước khi thực hiện parse_expr số 1 thì ta có count[357]=string[356] =canary.

với phép tính này thì ta hiểu chính là count[357]= count[357] + count[358] mà count[358] = 1 nên canary+=1 dẫn đến việc stack smashing.

###SUM UP

nói dong nói dài thế này, để mình tóm tắt lại các chức năng chính của chương trình.

  • get_expr: khởi tạo giá trị cho string
  • par_expr: ưu tiên các phép toán nhân chia trước cộng trừ sau
  • eval: nơi magic happens

vậy ta có đủ các thứ bây giờ chỉ cần dùng kĩ thuật gì để vượt qua challenge này. các bạn đoán xem, với NX, canary thì ta bypass bằng cách nào? ROP!!! và mình đã có bài giải thích rõ về ROP nếu ai chưa biết có thể vào xem

vậy ta cần:

  • eax = 0xb
  • ebx: trỏ vào địa chỉ chứa ‘/bin/sh’
  • ecx=edx=0

giờ tôi tìm được các gadget có thể dùng sau

0x0805c34b : pop eax ; ret
0x080701d0 : pop edx ; pop ecx ; pop ebx ; ret
0x08049a21 : int 0x80

tôi sẽ đẩy /bin/sh vào trong stack, tìm stack addr để trỏ ebx vào vì vậy ta cần phải leak được ebp để có thể tính

dùng gdb ta tìm được ret addr tại count[361]

dựa vào gdb ta có thể tìm được ebp của cả main và calc, cách nhau 0x1c bytes, nghĩa là count[361+7]=count[368]

welp vậy stack hiện trạng sẽ như thế này

tks to drx wu that’s i can understand on my own!!!

--

--