[Writeup-RE] KAF CTF 2020 (SSE_KEYGENME) x HTB Uni 2020 (my_name_is, Patch_of_the_Ninja)(Bootcamp CTF WannaGame Winter Season Ep. 2-3 )

PHAPHA_JIàN
20:23 12/12/2020

Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com

Tác giả: phuonghoang

SSE_KEYGENME (Mã nguồn)

1. Khởi đầu

Bước đầu tiên mình làm là cứ thử kiểm tra file đó là kiểu gì( ELF, bin, hay PE32, PE32+ ... ), sử dụng command file trong Linux:

$fileSSE_KEYGENME
2 SSE_KEYGENME:ELF64-bitLSBsharedobject,x86-64,version1(SYSV),
3 dynamicallylinked,interpreter/lib64/ld-linux-x86-64.so.2,
4 forGNU/Linux3.2.0,BuildID[sha1]=1e5c1dd325c6f4680a3e72d96aba4b57b0da321 5 notstripped

  • File này là file ELF - 64 bit, với mình đó là lợi thế, vì mình có thể debug để xem hoạt động của nó trên Linux
  • Cấp quyền thực thi cho nó và thử chạy xem có gì nào?

1 $chmod+xSSE_KEYGENME
2 $./SSE_KEYGENME
3 ###############################
4 ###WELCOMETOSSE_KEYGENME###
5 ### ENJOY YOUR STAY ###
6 ###############################
7 Enterkey:
8 >0123456789abcdefabcdef0123456789
9 Wrongkey,tryagain...

  • Mở file này bằng IDA và nhìn tổng quan(các biến mặc định đã được đổi tên, cũng như thêm vài comment để dễ phân tích hơn) vào hàm main của SSE_KEYGENME, ta có thể rút ra vài kết luận như sau:
    • Chúng ta sẽ gửi đến cho chương trình thực thi một key
    • Vượt qua được challenge này hay không quyết định bởi hàm check_login

SSE( Streaming SIMD Extensions) là phần mở rộng. bổ sung cho kiến trúc tập lệnh x86 được Intel giới thiệu phiên bản đầu tiên vào năm 1999 thao tác trên độ dài dữ liệu lớn hơn, chỉ bằng một câu lệnh duy nhất, lần đầu tiên ra mắt extension này gồm 70 tập lệnh mới hỗ trợ cho xử lý đồ họa và âm thanh

Sở dĩ cần phải hiểu và biết rõ về extension này vì đến hàm login ta sẽ thấy khi decompile file trên về ngôn ngữ bậc cao sẽ xuất hiện một đoạn code assembly rất khó đọc, đòi hỏi chúng ta có kiến thức cơ bản về extension này, cũng như các thanh ghi đi kèm với nó.

Link Wiki: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions 

2. Phân tích( static analysis)

2.1 pad()

Đầu tiên trước khi kiểm tra hàm check_login bằng IDA, lướt xem pad trước, dùng IDA Pro, mình có thể đươc pseudo code của hàm này, và rút ra nhận xét như sau:

  • Nó nhận vào 3 tham số: key, độ dài key, và hằng số có giá trị bằng 32
  • Hàm sẽ kiểm tra xem: well, key mày nhập đủ 32 ký tự ko đó, ko có thì nó sẽ padding vào với tất cả giá trị bằng (32 - len_key)
  • Chẳng hạn: key nhập vào có 21 ký tự thôi( "012345678901234567890"), thì key sau khi pad nó sẽ : ('012345678901234567890\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b')

 

  • Để ý, các câu lệnh trên đây chia làm 3 phần và đi theo một mô-típ giống nhau: load các giá trị của mảng p_box, và s_box vào stack của hàm tại địa chỉ lần lượt là (rbp + var_B0), (rbp + var_D0); rbp+var_C0 là địa chỉ mảng 16 ký tự vừa được khởi tạo.
  • Các bước tiếp theo, hàm sẽ làm như sau:

các phần tử p_box sẽ được trừ đi giá trị vừa được khởi tạo( 16 giá trị 1 ở trên).

p_box, hay x_box là một mảng gặp nhiều trong mật mã học, đặc biệt p_box được sử dụng trong mã hóa đối xứng khối(DES...)

16 phần tử đầu của key sẽ được hoán vị dựa vào p_box

Sau khi hoán vị, các phần tử sẽ được xor với x_box

Lưu kết quả xử lý 16 ký tự đầu tiên của key vào trong mảng ptr được cấp phát động trước đó

  • Sau đó, 16 ký tự còn lại của key cũng sẽ được thực hiện qua các bước tương tự để hoàn thành mảng ptr, check_login sẽ trả về 1 nếu như ptr và mảng flag giống nhau.

==> Như vậy để tìm key ban đầu, ta chỉ việc coi mảng flag như là một kết quả và đi ngược lại các bước làm trên để thu được key:

  • chia làm 2 phần
  • xor với x_box
  • dựa vào p_box( các giá trị p_box đã được trừ đi 1) để khôi phục lại vị trí ban đầu:
 

SSE_KEYGENME_sol.py
1 #given_flag_array,p_box,x_box
2 given_flag_array=[0x43,0x51,0x43,0x36,0x40,0x52,0x21,0x55,0x24,
3 p_box=[0x2,0x7,0x5,0xe,0x1,0xc,0x6,0x3,0x10,0x8,0xb,0xf,0xd,
4 x_box=[0x2,0x3,0x5,0x7,0xb,0xd,0x11,0x13,0x17,0x1d,0x1f,0x25,
5
6 #Calculateforp_box[i]-1
7 foriinrange(len(p_box)):
8 p_box[i] -= 1
9 #print(p_box)
10
11 #Calculatetheoriginalkey
12 key=[0]*32
13 foriinrange(0,len(given_flag_array),16):
14 for j in range(0, len(x_box)):
15 key[p_box[j] + i] = chr(given_flag_array[i + j] ^ x_box[j])
16
17 print(''.join(iforiinkey))
18 19
20 #--------------------------------------------
21 #$python3SSE_KEYGENME_sol.py
22 #KAF{flag}

my_name_is (Mã nguồn)

Là mình, quá tệ khi giải challenge này theo cách tồi nhất là đi crack các obfuscate function(decrypt: k(), p()) do tác giả tạo ra - mất thời gian( cực kỳ mất thời gian), trong khi nó có thể giải bằng một số kỹ thuật hay ho hơn nhiều!!!

Examine file and try to run it

  • Kiểm tra file type của nó và chạy thử xem sao nào:

  • Thì ra đầu tiên, chương trình sẽ kiểm tra xem mình có phải là user hợp lệ hay không, nếu đúng thì nó mới cho chạy tiếp

!!! Chú ý: geteuid() sẽ lấy effective uid của user đang chạy tiến trình, và hàm sau đó và getpwuid(uid) sẽ tìm kiếm entry trong user database phù hợp với tham số uid truyền vào. Giá trị trả về của hàm này sẽ trả về một struct passwd được định nghĩa ở đây nếu tìm thấy entry.

  • Rõ ràng username của nó: ~#L-:4;f và s1 của mình khác nhau nên trả về sai. (~#L-:4;f username này không hợp lệ trên linux, vì vậy cũng không thể giải quyết bằng cách adduser với tên như này để chạy chương trình)
  • pass qua được check username chỗ này, thì dường như sẽ get được flag, vì sau đó, chương trình sẽ thực hiện decrypt gì đó( dòng 24), và sau đó in ra màn hình( dòng 26)
  • Làm sao để pass check username?

Solutions

Hướng giải pháp 1:
  • Vì chương trình sử dụng ptrace và exit(1) để anti-debugging, nên đ chạy được debug, ta phải patch file này lại( thay đổi exit(1) thành NOP's(\x90)) hoặc chúng ta có thể sửa giá trị trả về của hàm _ptrace() lúc thực hiện debug( mình debug bằng )

  • Sau khi đã patch file và thực hiện debug ta sẽ chỉnh sửa luồng chương trình theo ý ta muốn để chương trình chạy đến nhánh chứa hàm decrypt ( cụ thể, các cờ ZF(sửa sau hàm strcmp() username), SF( sửa khi debug đến câu lệnh 0x08048958) trong thanh ghi ELFlags)

  • Khi đến hàm decrypt(), hàm này sẽ nhận vào 3 tham số theo thứ tự là username do ta nhập vào, một mảng cho trước có tên là encrypted_flag và một một mảng cấp phát động dùng để lưu trữ output sau khi xử lý trong hàm decrypt, sửa stack để tham số thứ nhất trỏ đến username(~#L-:4;f)

  • Sau khi thực hiện loạt trick trên, ta sẽ nhật được flag sau khi chương trình thực hiện lệnh puts sau hàm decrypt:

Hướng giải pháp 2( Không khuyến khích)

Coi như username là ~#L-:4;f lấy nó cho vào hàm decrypt và đọc xem hai hàm k(), p() làm gì trong đó, sau đó viết một đoạn code py để decrypt theo 2 hàm assembly đó.

Patch_of_the_Ninja (Mã nguồn)

1 $filePatch_of_the_Ninja.gb
2 Patch_of_the_Ninja.gb:GameBoyROMimage:"PATCHOFTHENINJA"(Rev.01)[MBC

Dùng strings để thực hiện kiểm tra xem có string đặc biệt nào xuất hiện trong file không? Và ta nhận được được kết quả bất ngờ:

1 $stringsPatch_of_the_Ninja.gb|grepHTB 2 HTB{flag}

TIN LIÊN QUAN
Federated Learning (FL): Khái niệm Các mô hình ML/DL truyền thống thường yêu cầu dữ liệu gốc của người dùng được gửi lên một server trung tâm để train tập trung, điều này gây ảnh hưởng đến quyền riêng tư (privacy). Do đó, các mô hình hiện nay thường sử...
Chào các bạn yêu thích nghiên cứu và công nghệ thông tin! Sau sự thành công của WannaResearch Episode 06, chúng tôi xin trân trọng thông báo về sự trở lại của chuỗi sự kiện nghiên cứu uy tín với Episode 07: "Audio Deepfake Detection." Buổi seminar này sẽ mang...
💨 SẮP TỚI CNSC CÓ GÌ? 🔥 KHOÁ HỌC LẬP TRÌNH PYTHON đã có mặt tại CNSC !!! 💼 Và đặc biệt hơn những ưu đãi vô cùng hấp dẫn cho “người nhà” các bạn SINH VIÊN UIT, các bạn đã sẵn sàng sắm ngay chứng chỉ này cho mình...