[Plaid CTF 2016] quite quixotic quest (Re300)

Hãy chuẩn bị tinh thần, vì đây là bài 300 điểm của một giải có rating 90.

2016fd1c9ed8-0e7f-40f3-b2b9-a23104c6361e

quite quixotic quest

Reversing (300 pts)

Well yes, it certainly is quite quixotic. (Yes, the flag format is PCTF{} )

Link dự phòng: quixotic

Đề bài là một file ELF 32 bit. Mặc dù đã có trong tay IDA phiên bản hịn (mà cả thế giới đều có), mình vẫn ưa 32 bit. Cười trong ô tô thì vẫn thích hơn là khóc trên xe đạp, right?

Xem danh sách string, dễ thấy nó chính là curl cải trang:

Mình đã định dùng BinDiff, nhưng thật thô bỉ khi đồng đội của mình chỉ cho mình cái này:

Tức là flag sẽ được nhập qua option –pctfkey. No fun.

Still no fun, chuỗi Validating key… kia đáng ra cần được che giấu kỹ càng hơn. Mình hoàn toàn cảm thấy bị xúc phạm khi mọi thứ cứ thẳng tuồn tuột như thế.

Vì EAX = magic_buf, rồi ESP = EAX, nên ESP = magic_buf. Sau khi debug một chút, chúng ta thấy rằng (có vẻ như) chương trình thực thi một rop chain (trỏ bởi magic_buf). Sau khi step over một hồi, chúng ta sẽ gặp badboy, được build từng char một (‘w’, ‘r’, ‘o’, ‘n’, ‘g’) bằng câu lệnh:

F8 không văn minh lắm, hãy chuyển qua chức năng trace của IDA. Đặt BP ở 0x08052AC7 và chạy lại, bật Instruction Tracing -> F9 -> Badboy -> Mở Trace window (mình quy ước rằng trace log mình post chỉ chứa những câu lệnh mà các bạn cần quan tâm):

Ta thấy EBX = len(key) (mình đang nhập key PCTF{123456}), sau đó EDX -= EBX, tiếp theo EAX = 0x94 và EBX = 0. Nếu cờ ZF = 0 thì lệnh CMOVNZ làm cho EAX = EBX = 0, ngược lại EAX giữ nguyên = 0x94. Sau đó EDI = EAX và ESP += EDI.

Do cờ ZF được quyết định bởi lệnh SUB EDX, EBX, nên kết quả của phép trừ EDX (0x35) cho EBX (len(key)) sẽ ảnh hưởng trực tiếp đến luồng thực thi của chương trình (hoặc ESP += 0, hoặc ESP += 0x94). Như vậy, len(key) = 0x35.

yOy5OI1

Nhập key mới (PCTF{1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJK}) và trace lại:

Đoạn code này làm nhiệm vụ cộng từng ký tự của key vào [EDX]. Khi đã nắm được ý tưởng, ta có thể filter log trace, hoặc đặt BP và F9 để qua nhanh nhưng vẫn đảm bảo việc theo dõi được vòng lặp. Thực tế thì chương trình chỉ tính sum(key[1:]) mà thôi.

Giá trị 01F9933D khi đổi qua DEC sẽ là 33133373, không có gì đặc biệt, mình chỉ nhắc thế thôi. Hãy chuyển sang debug để quan sát stack và các vùng nhớ liên quan. Tại hàm Curl_md5it, tham số thứ 2 trỏ đến 081CCF6C, đúng bằng [eax+4Ch], và chuỗi MD5 kết quả sẽ lưu ở 081CE9C0. Tổng kết lại, đến thời điểm này chúng ta có giả mã sau:

Tiếp tục:

Mã giả tương ứng:

Đoạn code sau sẽ cho ta biết giá trị của s:

Input lại một key phù hợp (PCTF{eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed}).

Đoạn trên là một vòng lặp, với EDX sẽ trỏ đến key và EBX+5E5B30C4 trỏ đến chuỗi MD5.
Sắp xong rồi, cố lên >:(

36

Debug để thấy giá trị các biến, ta thấy rằng chương trình thực hiện việc lấy từng dword trong key (sau khi đã xor với chuỗi MD5), rồi trừ cho ECX, và OR vào EDX. Khi bắt đầu, EDX = 0. Các giá trị của ECX lần lượt như sau:

Ở đây lại là một trick tương tự mà mình đã nhắc đến phần đầu bài, khi mà cờ ZF sẽ ảnh hưởng đến luồng thực thi của chương trình. Cờ ZF được quyết định ở câu lệnh AND EAX, EDX, và vì EAX = 0xFFFFFFFF, EDX cần bằng 0 để cờ này được bật.
Ta nhớ rằng EDX là phép OR của mọi giá trị EAX – ECX, do đó để EDX = 0 thì mọi hiệu EAX – ECX phải bằng 0, hay nói cách khác, EAX phải bằng ECX. Đoạn code sau, như mọi khi, mang lại cho chúng ta flag:

Nếu bạn thắc mắc tại sao với cách làm hư cấu thế này, mà mình lại có thể trở thành người giải ra thứ hai (:”>), thì bạn thắc mắc đúng rồi đấy. Trong lúc thi mình không hề dùng cách này. Hihi.

32

[Plaid CTF 2016] quick (Re175)

Tôi cảm thấy mình cần phải thức tỉnh, khi mà hàng ngày vẫn có biết bao lượt truy cập vào blog này, trong khi last post của nó đã cách đây hơn 1 năm. Tôi thật tệ.

quick

Reversing (175 pts)

Why be slow when you can be quick?

File dự phòng: quick

Người lười như tôi thường sau khi down đề về sẽ mở nó bằng notepad, xem mấy byte đầu thấy ELF thì tôi sẽ kéo nó vào IDA 32 bit, nếu sai tôi lại kéo nó sang IDA x64. Dạo trước có đợt tôi quyết tâm thoát lười, sau khi xem vài byte đầu bằng notepad tôi vẫn ráng xem thêm vài dòng bên dưới, nếu thấy x86-64 thì chỉ phải kéo một phát vào IDA là đúng luôn. Lâu không tập, tôi mất cmn luôn kỹ năng ấy.

Ok, bài này là ELF x64. Xem danh sách hàm trong IDA bạn sẽ thấy nó dùng Swift, để chạy nó chúng ta cần file bị thiếu bên dưới (hãy dùng Ubuntu 14.04 nếu muốn thành người văn minh):

Hàm main khá dài, tôi không biết nó làm trò con bò gì, nhưng hàm check key nằm ở 0x403660. Nó cũng không ngắn gì cho cam. Chúng ta có 2 mảng 33 phần tử, kiểu thế này (tôi không paste code IDA ở đây vì tôi không thích):

Hàm check này cần trả về 1, và chúng ta thấy nó trả về 0 ở đây:

Bạn sẽ thắc mắc vì sao tôi không rename mấy cái biến trên. Câu hỏi rất hay, tôi xin phép không trả lời.

Dễ thấy rằng nó so sánh v16 với v69. Mà v16 = v70, v70 lấy từ câu lệnh (google “subscript swift” nếu bạn muốn):

v66, thật tình cờ, được quyết định ngay ở đầu hàm check – là kết quả trả về của hàm 0x403600. Trong hàm này có 2 hàm con, hãy để ý tên các hàm import từ Swift để suy ra chức năng của nó. Tôi tin bạn sẽ làm được, chúng ta đã đọc Conan cả chục năm rồi. Cụ thể: 0x402810 làm nhiệm vụ sort input của ta (bé đến lớn, dùng …ComparablerS_4sortfT_GSaWxS0_S1___, tên dài lắm), còn 0x403050 sẽ tính tổng từng cặp phần tử trong *input của ta* và *input của ta_đã sort*, kết quả chính là v66.

Tương tự như vậy, v69 lấy từ v65, v65 chính là arr1. Và tóm lại, 2 mảng arr1 và mảng v66 phải bằng nhau.

Để vượt qua bước check này, bạn chỉ cần 5 phút. Để bỏ cuộc. Hãy sang phần check thứ hai.

12057133_687329614735151_682897352_n

Vẫn với skill dò ngược thần thánh, bạn sẽ thấy rằng nó so sánh một mảng x với arr2. Tất nhiên, quan trọng là mảng x được tính thế nào.

Ở đoạn code trên, v112 chính là mảng x, v57 là *input của ta*, 0x403510 là hàm ror, 0x402AD0 tôi quên cmn rồi (hình như nó chả làm cái khỉ gì). Chuyển sang python:

Nhìn qua thì chúng ta cảm giác rằng check2 dễ hơn check1, nhưng nếu thử code thì bạn sẽ thấy là nó không sai. Đoạn code này sẽ cho ta flag:

Kiểm tra lại flag với check1, thấy đúng luôn. Kỳ lạ.

20163c25a8d8-c48c-411b-b324-f59ab1f26fd1

[30C3 CTF] fourier (Numbers200)

Heres one for those that like numbers and mathematics…

fourier.tar.gz

File dự phòng

Mở bài

Chúng ta được cung cấp cho 1 file fourier (elf) và 1 file flag.four (data). File fourier dùng để mã hóa, còn file flag.four, dĩ nhiên, là flag đã được mã hóa. feel_good

Thân bài

Sau một chút thời gian (rất dài) bỡ ngỡ khi thấy các thím ấy sử dụng GMP cho việc tính toán, mình cũng thể hiện được phẩm chất vượt trội trong thao tác mò code để mô phỏng lại thuật toán mã hóa như sau (nhìn chung thì khá dễ, mỗi tội hơi khó) emo_popo_shame:

Ngoại trừ các thao tác mà mình đã đối chiếu và thấy khá tương đồng, vấn đề duy nhất tồn đọng là hàm random.

Target sử dụng hàm ___gmp_randinit_default và hơi lạ là khi mình debug lại thấy các kết quả random bị lặp lại. Mình cũng mất hơi nhiều thời gian cho việc cài đặt và chạy thử cái GMP này, rồi nhờ luật nhân quả nên sau đó điểm sáng xuất hiện: emo_popo_haha

Ở đây ta thấy rằng chương trình làm một việc, đó là ghi ra giá trị sau khi random (mỗi đơn vị ứng với một byte ‘4‘). Và tổng quan thì cái thuật toán mã hóa khi hiểu đơn giản sẽ như sau:

  1. Nếu secret còn lớn hơn 24, thì chạy một vòng lặp, mỗi lượt ghi ra 2 giá trị:
    1. random_number.
    2. Số lần lặp có đụng chạm đến number_5 (mật danh là X_LOOP). Lưu ý rằng number_5 là một giá trị then chốt, vì nó là yếu tố duy nhất gây ra sự thay đổi giá trị của secret. emo_popo_choler
  2. Khi secret nhỏ hơn 24, ghi ra secret.

Như thế, nếu ta chia file flag.four ra các phần ngăn cách bởi dấu cách (0x20), giá trị mỗi phần là độ dài của phần đó (vd ‘444‘ = 3), thì hàm giải mã có nhiệm vụ:

  1. Đọc ngược từ phần cuối về phần đầu (tức phần đầu tiên được đọc sẽ là phần cuối của file).
  2. Gán secret = phần đầu.
  3. Chạy vòng lặp cho đến khi hết nội dung:
    1. Đọc phần tiếp theo, đó chính là số lần lặp của X_LOOP.
    2. Đọc phần tiếp theo, đó chính là random_number.
    3. Tất cả giá trị đều đã đủ, ta có thể tính toán được number_5, và thêm vào secret.
  4. Chuyển đổi secret từ DEC qua ASCII.

(lý do vì sao lại đọc từ cuối trở về mà không đọc từ đầu trở đi trong khi 2 hướng nó như nhau, thì là vì, mình thích thế đấy sexy_girl
Code minh họa:

Kết bài

emo_popo_smile

 

 

[SVATTT CTF 2013] Điệp vụ bất khả thi (ACM200)

Trong lúc các đồng đội của mình đang thực hiện thao tác trên các hệ thống nội bộ, Võ bằng kinh nghiệm truy vết của mình tìm ra 1 dịch vụ lạ trên một máy chủ đặt tại nhà riêng của Snowden. Tuy nhiên dịch vụ được bảo vệ bởi một số đoạn mã với yêu cầu vô cùng phức tạp mà chỉ có Anonymous mới nắm giữ bí quyết khởi tạo. Công việc tưởng bất khả thi nhưng chưa đầy 1 nốt nhạc sau, Võ đã nắm trong tay kết quả…
Challenge: nc 138.91.39.179 45456

Không đội nào giải được? Ok, đây là lúc để mình tỏa sáng still_dreaming

Nội dung chính

Kết nối đến server, ta nhận được lời chào:

    Prove that you are one of us! This is the new algorithm we have developed for our VenaSploit tool
We give you a set of character, you need to give me back a long enough string such that every 4 consecutive bytes is unique
All characters in the charset should be included for it to be valid. A charset is given between the tag <pre>
Example: <pre>01</pre> length: 8 , Answer: 00010111

Charset: <pre>01</pre>
Give me a pattern of size at least 16 using the given charset:

Như vậy, nhiệm vụ của chúng ta là tìm một chuỗi có kích thước n, được tạo bởi một tập các ký tự cho trước, với điều kiện là 4 ký tự bất kỳ liền kề không được trùng nhau (mình gọi là tính chất UEY). Ví dụ chuỗi:

0001110001100

là không hợp lệ, do 1100 xuất hiện 2 lần. Còn một điều kiện nữa là tất cả các ký tự trong charset đều phải được dùng, tuy nhiên điều này không mấy quan trọng.

Thuật toán

Kể từ ngày tham gia một cuộc thi chính thức đầu tiên (DEFCON thì phải) và giải được 2 bài thuộc thể loại coding tương tự, mình đã hiểu rằng mình sẽ yêu nó. Ít nhất thì đến bây giờ, mình vẫn còn cảm thấy hứng thú và bình yên với cái thể loại nhí nhảnh và nhộn nhịp này emo_popo_smile

Bắt đầu từ thử thách thứ nhất, tạo chuỗi dài 16 ký tự, charset là 01. Ta thấy kích thước charset là 2, và 2^4 = 16 (4 chính là kích thước nhóm ký tự không lặp lại). Người thì bảo là trùng hợp, nhưng mình thì, cũng nghĩ thế emo_popo_shame

Lúc đầu, mình cho rằng bài này cần đến thuật toán quay lui, nhưng sau đó, mình không chứng minh, nhưng đoán rằng có thể chứng minh được, là tại mỗi bước, luôn tìm được ký tự tiếp theo để chuỗi mới vẫn là chuỗi hợp lệ (thỏa mãn tính chất UEY). Lý do thì rất đơn giản: mình đã thử, và hình như đã thành công.

Nhưng như vậy vẫn là chưa đủ, điều quan trọng nằm ở việc tối ưu code, nhất là việc kiểm tra tính hợp lệ của chuỗi mới sau mỗi bước. Ok, việc chạy một vòng lặp đi suốt chiều dài chuỗi vài nghìn ký tự tốn không đáng là bao nhiêu công sức, tuy nhiên nếu tưởng tượng rằng cái công sức đó bị nhân lên vài nghìn lần, mọi thứ sẽ khác. Thêm nữa, mỗi khi thêm mới một ký tự, tại sao chúng ta lại phải kiểm tra lại cái phần đã được kiểm tra trước đó? Máy yếu thực sự không thích điều này emo_popo_choler

Giải pháp mình đưa ra sau vài tiếng đứng chôn chân trên xe bus, đó là lên danh sách sẵn các pattern (nhóm 4 ký tự), gọi là available_patterns dựa trên charset bài cho, khởi tạo giá trị 1 cho mỗi pattern (cũng chính là mỗi phần tử của mảng: key => value), và mỗi khi thêm một ký tự, ta kiểm tra pattern mới nhất (4 ký tự cuối của chuỗi) có nằm trong available_patterns hay không (giá trị của phần tử = 1), nếu có, ta coi ký tự vừa thêm hợp lệ (chưa được dùng), ta sẽ sử dụng nó, đồng thời gán phần tử ứng với pattern đó bằng 0; ngược lại, nếu giá trị của phần tử đã bằng 0, tức pattern đó đã được sử dụng, ta bỏ qua ký tự vừa thêm và thay bằng ký tự khác. Việc truy xuất giá trị một phần tử của mảng là nhanh vô cùng, nên ta khỏi lo bị chậm emo_popo_big_smile

Cơ mà một vấn đề mới lại phát sinh sau khi mình cài đặt thử, đó là đối với charset quá rộng, thì kích thước các pattern hợp lệ là rất lớn, chiếc máy nhỏ xinh của mình bị báo MemoryError ngay sau vài nốt nhạc. Hướng giải quyết là gì?

Là thôi không lập chỉ mục sẵn nữa, nhưng vẫn phải tìm cách để biết được pattern nào đã dùng rồi, pattern nào chưa. Điều này tưởng như đơn giản, nhưng hóa ra lại… đơn giản thật. Đó là với đối tượng dictionary (python), khi ta truy xuất một phần tử không có trong từ điển, exception sẽ nhảy ra (mà chắc ngôn ngữ nào cũng vậy thôi). Như thế, ta sử dụng một dictionary để lưu những pattern đã được dùng, thì khi kiểm tra, nếu có exception, tức là pattern đó chưa có trong từ điển (nghĩa là nó hợp lệ), và ngược lại. Chỉ có vậy thôi confuse

Sau một thời gian chạy khá nhanh (có lâu thì cũng là do đề bài cho charset quá phức tạp và length quá dài thôi, chứ thuật toán thì generate ra flag nhanh lắm emo_popo_amazed), ta có cờ:

Your Flag: SVATTT_US3_PEDA_D0NT_Y0U?

[SVATTT CTF 2013] Vết tích tại hiện trường (Web200)

Anonymous đã upload thành công một script backdoor của bọn chúng lên server ngân hàng Trung Ương với mục đích sẽ quay lại kiếm lợi sau này. Nhưng để truy xuất file, bạn cần phải biết password của chúng đã qui ước với nhau từ trước. Chỉ mất khoảng 10 phút, Chiến đã bypass được. Thế còn các bạn thì sao? :)
Challenge: Link
Hint: hint.txt

Chúng ta có một vùng nhập séc rét, và file hint.txt có nội dung xinh xinh:

Nội dung chính

Ta thấy rằng, yêu cầu của bài này là tìm một password thỏa mãn 2 điều kiện:

  1. Có chứa Anonymous.
  2. CRC32(password) = deadc0de.

Sau đó, (có lẽ là) flag sẽ được lấy từ database qua câu query:

Như vậy, nhiệm vụ của chúng ta, ngoài việc tìm password thỏa mãn 2 điều kiện vừa nói ở trên, còn có thêm một yếu tố nữa, đó là bypass được câu query SQL cuối cùng.

Với 2 nhiệm vụ đầu, công việc khá đơn giản, do giá trị CRC32 có thể được chỉnh sửa tùy ý bằng cách thay đổi 4 byte trong password. Một vài tool trên mạng cho phép chúng ta làm điều này, thay vì tự code, ví dụ như:

Giờ nếu ta tạo password ban đầu là:

Anonymousabcd

và dùng tool trên, kết quả cho ra:

Anonymous[q>_

Đây là một password khá đẹp, nó có chứa Anonymous, và CRC32 của nó đúng bằng deadc0de. Tuy nhiên khi submit, ta nhận thông báo sau:

Anonymous: Nice try! But You are doing it wrong :)

Ok, đây là điều chúng ta đã dự đoán từ trước, do vẫn chưa giải quyết được phần SQL.

Tuy vậy, việc inject câu query này xem chừng khá đơn giản, chúng ta sẽ thử với một password khá gia giáo và truyền thống:

Anonymous' OR 1=1--aaaa

Submit sau khi đã đưa nó qua forcecrc32.py, thông báo trả về xấu hơn một chút:

Bad input :)!

Vậy là có dính phải các ký tự bị cấm rồi too_sad

Thử thêm chút nữa, ta thấy các ký tự bị cấm dường như là: <space>, <comment> blah blah… tuy nhiên các từ khóa and, or, dấu nháy đơn () và dấu chấm phẩy (;) thì không bị. Hơ, thế cũng là đủ rồi emo_popo_look_down

Password cuối cùng mà ta lựa chọn sẽ là:

Anonymous'or'1'='1';aaaa

Makeup một chút với CRC32, ta được password xịn:

Anonymous'or'1'='1';nx9Dxx92

Submit:

Anonymous: Not bad! Here is your flag

SVATTT_crc_0r_n0t_CRC

emo_popo_misdoubt

[WarGameVN CTF] SecretKeeper (Web300)

Đây là nơi trao đổi bí mật giữa các tên trùm.

Source: ~

 

Rất cảm ơn BTC vì đã gìn giữ để cái URL nó sống đến tận bây giờ sweet_kiss

Đây là một bài mà hồi đó, mình nhìn nó với một sự ngưỡng mộ sâu sắc, và thậm chí, ngay cả khi có một vài member kiệt xuất trong team mình miên man bình luận về nó thì mình vẫn như một thằng lơ ngơ không hiểu gì.

Mình thích cái cảm giác lâu lâu quay lại một bài nào đó, một bài mà trước đây mình không làm được, thậm chí không biết nó hỏi cái gì, nó cần cái gì, rồi sau đó nó hét lên với mình: “Cờ của mày đây!”, cảm giác thật thanh thản và bình yên sexy_girl

Ngày còn bé (cụ thể là cấp 3), mình cực kỳ yếu tích phân, và dù cho đã cố lăn lộn với nó trong suốt 3 năm học, cộng với vài ngày sát kỳ thi ĐH, mình vẫn ra về tay trắng. Mình nhớ như in cái cảm giác đó, cái cảm giác mà mình thực sự đã sinh ra vì một mục đích khác, một mục đích mà không liên quan khỉ gì đến tích phân cả, rằng thì là cả đời này mình cũng vẫn nấp dưới danh nghĩa của một thằng không thể giải được bài tích phân nào trong đề thi ĐH emo_popo_cry

Ấy thế mà lên ĐH, mình lại bá đạo vô cùng về mấy cái thứ gớm ghiếc này, tất cả thay đổi, cuộc sống thay đổi, lòng người cũng thay đổi. Mình từng ghét Vật Lý, rồi mình yêu Vật Lý, mình từng ghét Console, rồi mình yêu Console, mình từng ghét Tích phân, và rồi mình yêu Tích phân. Lý do cũng chỉ có một, đó là nếu ta yêu một ai đó, ta sẽ… không còn ghét họ nữa (đệch after_boom). Thôi nói tóm lại, là một khi mình đã thấy mặt lợi ích của cái gì đó mang lại, thì mình sẽ không còn thấy nó vô bổ nữa…

ops emo_popo_waaaht emo_popo_sweat

Nội dung chính

Ở bài này, chúng ta có một vùng để nhập séc rét, điền thử:

^_^

và nhấn Save, chúng ta có một cái link:

http://challenges.wargame.vn:1337/web300_c4d7c1d9c925b4021adf5e192315ecb9/?file=dbf8adc50a348b9e808d204825c7aed2d4430f900ed7b96daec2a3c1d6665a2792cdb3104d2666e64e069d87cd286765d29b0c16ca1ab2d26249735974d6fb50&sign=730646501b5d8c9a234686a9bcc580bb

Khá dài và khá hãi… Xem source thì có được:

Như đề bài đưa, source nằm ở index.phps, xem nó thôi:

Ta sẽ chia code làm 2 phần chính: Mã hóaGiải mã, còn nhiệm vụ của chúng ta là đọc file ./secret/flag.php.

Mã hóa

$secret_hmac, $secret_filename được tạo ngẫu nhiên, sau đó ghép vào với ký tự ngăn cách là ‘|‘, cuối cùng mã hóa thành tham số file cho url. Tham số còn lại, sign, dùng cho mục đích kiểm tra tính hợp lệ của $secret_filename (mình sẽ trình bày sau).

Nói chung phần này không có gì nhiều, ta kệ nó, cho nó chơi một mình.

Giải mã

Trình tự thực hiện là:

  1. Giải mã tham số file.
  2. Chia làm 2 phần, ngăn cách bởi ký tự ‘|‘.
  3. Phần 1 là $secret_filename, phần 2 là $secret_hmac.
  4. Dùng tham số sign để kiểm tra tính hợp lệ của $secret_filename, nếu ok thì in ra nội dung của nó.

Như thế, chúng ta thấy được 2 việc chính cần làm:

  1. Xác định tham số file để $secret_filename trỏ đến file ./secret/flag.php.
  2. Xác định tham số sign ứng với tham số file để pass bước 4.

Xác định tham số file

Thuật toán mã hóa được sử dụng là AES 128bit (16 byte cho mỗi block), mode CBC, nên chúng ta có thể nghĩ ngay đến Byte Flipping emo_popo_smile

Do $secret_filename là MD5, kích thước 32 byte, chúng ta sẽ can thiệp vào block đầu tiên của cipher, để thay đổi block thứ hai của plain text. Nhưng cũng cần lưu ý rằng block đầu của plain text sẽ thành rác, và chúng ta cần chỉnh sửa đường dẫn của file flag cho phù hợp. Ví dụ, ta có một đường dẫn hợp lệ là:

^#%&$%$@@$#%%^$abcde/../flag.php

Bắt đầu từ url nhận được lúc đầu, ta dùng đoạn code sau để thu được tham số file mong muốn:

Khá ổn emo_popo_nosebleed

Xác định tham số sign

Dù rằng $secret_filename xem chừng đã hợp lệ, ta vẫn chưa đọc được nội dung của nó, do bị bước 4 chặn lại. Để ý rằng, sign được tính như sau:

mặt khác, cả $secret_filename $secret_hmac đều là các thành phần trong tham số file, được ngăn cách bởi ký tự ‘|‘, nhưng task chỉ hiển thị $secret_filename cho ta thấy.

Để có $secret_hmac, điều duy nhất mà ta làm được, đó là Flipping thêm một Byte nữa, mục đích là thay đổi ký tự ‘|‘ thành ký tự khác, khi đó câu lệnh:

sẽ trả về $secret_filename chứa cả $secret_hmac (do không còn ký tự ngăn cách), và bằng cách lấy 16 ký tự cuối, ta có thứ mình cần emo_popo_big_smile

Ta biết rằng ký tự ‘|‘ sẽ ở block 3 ($secret_filename là MD5, kích thước 32 byte = 2 block), nên để thay đổi nó, ta sẽ can thiệp vào block 2 của cipher, mà cụ thể là byte thứ 17. Tất nhiên điều này sẽ làm hỏng $secret_filename, nhưng hiện tại nó không còn quan trọng nữa.

Giờ thì chúng ta đã có thể thoải mái tính sign, like a boss ah adore:

Tổng kết

Flag = mario_CBCm0d3_is_swag_huh_;)

Ghi chú: Chúng ta cần để ý giá trị trả về của mỗi bước Byte Flipping, tránh để xuất hiện ‘|’, do điều này sẽ dẫn đến secret_hmac thu được bị sai.

[WhiteHat CTF 2013] I love Sherlock Holmes

Mình không có ý định sẽ writeup cho giải lần này, do không biết là có được phép public đề bài hay không. Tuy nhiên do có một số bạn quan tâm và muốn làm quen với CTF, mình xin phép dùng bài này để minh họa, do nó tương đối cơ bản và dễ tiếp cận.

Ghi chú: Toàn bộ nội dung được ghi theo cách hiểu của mình, và mình cũng chỉ là người mới của lĩnh vực này, nếu có gì chưa phù hợp, các bạn cứ báo lại và mình sẽ chỉnh sửa ngay.

CTF là gì?

CTF là viết tắt của Capture The Flag, và bằng vốn tiếng Anh kinh dị của mình thì nó có nghĩa kiểu kiểu như là Lấy cờ, Tìm cờ (giống trò chơi hồi bé, nhắm mắt đếm 5, 10, 15, 20, 25…, cho đến 100 thì bắt đầu đi kiếm mấy đứa đang ẩn nấp khắp mọi nơi ấy). Cờ ở đây thường là một đoạn văn bản, mà có thể được kiểm tra tự động. Ví dụ:

  • DayLaCo
  • yeuchimse_hinh_nhu_rat_dang_yeu
  • c41_n4y_cung_g01_l4_c0

Tức là, nếu quy ra cuộc sống tự nhiên, CTF tương tự như việc chúng ta được thả vào một căn phòng, và nhận yêu cầu: “Hãy tìm cho tôi một cái gì đó”. Cái gì đó ở đây (tức cờ), có thể được quy định sẵn về hình dáng của nó (như flag{abcdef}, ctf_this_is_flag chẳng hạn), hoặc không có thông tin gì kèm theo, khi đó chúng ta sẽ phải dựa theo cảm giác, rằng khi thấy một cái gì đó lạ lạ, thì nó rất có thể là cờ.

CTF có 2 dạng chính, là Jeopardy, và Attack-Defense, mình chỉ tham gia ở dạng đầu tiên. Jeopardy là hình thức chơi mà mỗi cuộc thi sẽ bao gồm các bài nhỏ, mỗi bài nhỏ lại có chứa một cờ để cho ta tìm. Khi tìm được cờ, chúng ta sẽ nhận được số điểm tương ứng. Các bài được đặt trong khá nhiều thể loại chuyên môn khác nhau, nhưng thường thì là:

  • Reverse: Dịch ngược phần mềm
  • Web: Bảo mật web
  • Crypto: Mật mã, mã hóa
  • Forensic: Điều tra số
  • Stegano: Ẩn giấu thông tin
  • Exploit: Khai thác lỗi

Cụ thể với WhiteHat CTF 2013, cuộc thi chia ra các mảng là Web, Crypto, Reverse, Forensic và Exploit. Mình sẽ minh họa với bài I love Sherlock Holmes, thuộc mảng Crypto.

Nội dung chính

Đề bài, chúng ta được cung cấp 1 file hình như sau:

Không có gì khác, tất cả chỉ vậy thôi, cũng không nói rằng chúng ta phải làm gì.

Đó chính là điều các bạn cần lưu ý, vì trừ trường hợp đặc biệt, còn lại thì chúng ta hiển nhiên cần hiểu rằng mọi bài sẽ có một đòi hỏi duy nhất, đó là tìm ra cờ, và submit kiếm điểm. Thay vì viết là: “Tìm x sao cho: x + 1 = 2”, chúng ta chỉ cần một cái đề: “x + 1 = 2” cũng đã coi như quá đủ!

Trở lại với công việc, nếu bạn đã từng đọc Holmes (mình thì khỏi nói, mình vô cùng cuồng Holmes và mỗi ngày hầu như đều đọc đi đọc lại mấy mẩu chuyện đã thuộc nằm lòng), thì bạn sẽ nhận ra ngay đây là phương pháp mã hóa xuất hiện trong vụ án “Những hình nhân nhảy múa”, tiếng Anh là Dancing Man. Ở vụ án này, Holmes dựa theo tần suất xuất hiện của từng chữ cái để dần dần suy ra bảng mã tham chiếu hoàn chỉnh. Chúng ta may mắn hơn Holmes, vì chúng ta có Google. Google biết mọi thứ, đó chính là niềm tin của mình sweet_kiss

Search một lúc, ta có bảng mã cho Dancing Man như sau:

Đơn giản là đối chiếu đề bài với bảng mã tìm được, chúng ta có dãy ký tự:

RHNTKXTMTEXGMXWWXVMBOX

Nó có phải là cờ không?

Tất nhiên là bạn có quyền thắc mắc như vậy, dù rằng thông thường cờ sẽ không vô nghĩa và xấu xí đến thế  emo_popo_sweat

Khỏi cần thắc mắc dài dòng làm gì, submit thử thì biết chứ có gì đâu.

Wrong!

Đổi sang chữ thường và submit lại.

Wrong!

emo_popo_pudency Đây có lẽ chính là điểm dừng của một số đội chơi khác, vì một lý do quá kinh khủng: không biết phải làm gì nữa emo_popo_beat_plaster

RHNTKXTMTEXGMXWWXVMBOX

Sau khi rà soát lại để chắc chắn rằng không đối chiếu nhầm ở đâu đó, chúng ta sẽ quên hết mọi thứ trong quá khứ, và coi như đề bài chỉ bắt đầu từ cái dòng ký tự xấu xí này mà thôi.

Có khá nhiều hướng khi giải quyết một bài crypto, như XOR các byte với một giá trị x nào đó, cộng thêm một giá trị y nào đó vào từng ký tự, xoay vòng các ký tự theo độ lệch z (cũng nào đó), hoặc phổ biến không kém, đó là hoán vị các ký tự (A thành S, B thành E, C thành X…), và nhiều rất nhiều thuật toán từ cơ bản đến nâng cao khác.

Hướng XOR ít khả thi, do chuỗi ta đang có rất đẹp (đẹp ở đây tức nó đều bao gồm các ký tự chữ cái), mà XOR thì không có đặc tính giữ lại độ đẹp của mật mã sau khi được giải mã (dù rằng một vài giá trị khi dùng làm khóa cũng mang lại điều này). Tuy nhiên, chúng ta vẫn sẽ giữ lại nó trong đầu như một phương án dự phòng.

Cộng thêm giá trị vào các byte, đây là một hướng tốt, có thể thử. Nhưng do mật mã có cả X và B, nên cộng thì cũng dở mà trừ thì cũng dở (vì chắc là nó vượt ra ngoài 2 biên của bảng chữ cái), xếp nó sau XOR theo độ ưu tiên vậy.

Xoay vòng các ký tự (còn gọi là Caesar): Hướng này vô cùng khả thi, do là xoay vòng, nên X + 3 = Z + 1 sẽ quay về A, nên đảm bảo rằng kết quả mã hóa cũng đẹp y như mật mã vậy. Hãy quan tâm đến nó một chút.

Hoán vị các ký tự, hướng này có thể loại ngay, do mật mã quá ngắn, không đủ cơ sở để suy luận.

Caesar

Vấn đề tiếp theo cần đối mặt, đó là nếu chọn Caesar, thì xoay với độ lệch bao nhiêu? Phổ biến thì chúng ta biết đến Rot13 (A sẽ chuyển thành N, B sẽ chuyển thành O, C sẽ chuyển thành P, và ở chiều ngược lại, N sẽ chuyển thành A, O sẽ chuyển thành B, P sẽ chuyển thành C).

Với bài này, Rot13 cho ra kết quả không đẹp:

Rot13(RHNTKXTMTEXGMXWWXVMBOX) = EUAGXKGZGRKTZKJJKIZOBK
Xấu như gấu emo_popo_beat_shot
Nhưng Rot13 chỉ là cái người ta hay dùng, và thực tế thì mình đã gặp không ít bài mà người ta Rot16 phút, Rot69, thậm chí Rot linh tinh loạn cả lên. Mà cũng chẳng sao cả, chúng ta có thể dùng một vài tool trên mạng, hoặc tự code vài dòng để có được kết quả của tất cả các phép Rot từ 1 đến 26, và xem xem có kết quả nào khả quan không:
Rot1: SIOULYUNUFYHNYXXYWNCPY
Rot2: TJPVMZVOVGZIOZYYZXODQZ
Rot3: UKQWNAWPWHAJPAZZAYPERA
Rot4: VLRXOBXQXIBKQBAABZQFSB
Rot5: WMSYPCYRYJCLRCBBCARGTC
Rot6: XNTZQDZSZKDMSDCCDBSHUD
Rot7: YOUAREATALENTEDDECTIVE
Rot8: ZPVBSFBUBMFOUFEEFDUJWF
Rot9: AQWCTGCVCNGPVGFFGEVKXG
Rot10: BRXDUHDWDOHQWHGGHFWLYH
Rot11: CSYEVIEXEPIRXIHHIGXMZI
Rot12: DTZFWJFYFQJSYJIIJHYNAJ
Rot13: EUAGXKGZGRKTZKJJKIZOBK
Rot14: FVBHYLHAHSLUALKKLJAPCL
Rot15: GWCIZMIBITMVBMLLMKBQDM
Rot16: HXDJANJCJUNWCNMMNLCREN
Rot17: IYEKBOKDKVOXDONNOMDSFO
Rot18: JZFLCPLELWPYEPOOPNETGP
Rot19: KAGMDQMFMXQZFQPPQOFUHQ
Rot20: LBHNERNGNYRAGRQQRPGVIR
Rot21: MCIOFSOHOZSBHSRRSQHWJS
Rot22: NDJPGTPIPATCITSSTRIXKT
Rot23: OEKQHUQJQBUDJUTTUSJYLU
Rot24: PFLRIVRKRCVEKVUUVTKZMV
Rot25: QGMSJWSLSDWFLWVVWULANW

Nếu tiếng Anh của bạn cũng siêu đẳng như tiếng Anh của mình, bạn sẽ thấy ngay Rot7 là cái mà ta đang tìm, kết quả thu được thực sự quá đẹp:

YOUAREATALENTEDDECTIVE

Nó đẹp đến nỗi mà nó đã có thể là cờ rồi, mình đặt niềm tin với tỉ lệ đặt 6 ăn 9 emo_popo_sure

Wrong!

Wrong!

Wrong!

 

emo_popo_waaaht  surrender

A du kít đinh mi ops

À hông có sao, còn chữ thường, vẫn còn chữ thường nữa mà emo_popo_cry

youareatalenteddective

Correct :)

Tổng kết

Như vậy, là mình đã vừa giới thiệu một vài điểm sơ qua về CTF, cũng như trình tự suy luận của mình đối với một bài thi cụ thể. Kiến thức là rất rộng (và sâu), nên chắc chắn rằng chúng ta sẽ luôn phải học hỏi hăng say nếu không muốn bị bỏ lại đằng sau và mau mau rớt hạng. Nếu bạn có gì đó không hiểu, hoặc bực mình vì chẳng biết XOR, Caesar nó là cái khỉ gì cả, thì hãy yên tâm, không ai sinh ra đã biết cộng trừ nhân chia đâu. Bạn còn rất nhiều cơ hội để tìm hiểu và tích lũy kinh nghiệm. Bạn có thể là người xuất phát muộn hơn, nhưng không ai có thể ngăn cản việc bạn về đích sớm hơn cả.

Chào mừng bạn đến với CTF! emo_popo_beauty

[Sharif University CTF Quals 2013] one Line encoder For Specialist pRogrammer (crypto200)

 

Có thể để ý ngay những chữ viết hoa trong tên bài, nó là LFSR, nhưng kệ boss

Nội dung chính

Ở bài này, chúng ta có một hàm encode, đầu vào là một xâu text và một khóa key. Dựa vào kết quả trả về của hàm len(key) và set(key), ta biết được 2 thông tin:

  • Key dài 9 ký tự.
  • Key chỉ chứa ‘0‘ và ‘1‘.

Như vậy, mình tin chắc rằng mình sẽ làm được, với lựa chọn cho bước đường cùng là brute key để submit. Kế hoạch cụ thể mình đã có, đó là submit từng key cách nhau 10s, tức 1 phút được 6 key, một giờ được 360 key, khoảng 2h sẽ xong. Nếu vẫn bị BTC phát hiện thì đẩy interval lên và cắm máy qua đêm sexy_girl

Tà đạo đã xong, giờ là chính đạo. Tiến hành viết lại cái hàm encode của họ như sau (nói thật là mình mất không hề ít thời gian để đọc về lambda, map và reduce, vì dù nó đơn giản thật nhưng khi áp dụng vào bài cho thì mình lại hơi bị loạn, khá ức chế):

Có thể thấy thuật toán mã hóa là rất đơn giản, byte-to-byte bằng phép toán XOR huyền thoại. Chúng ta viết hàm decode như sau:

và tiến hành brute để tìm key:

Output chỉ cho ra một giá trị duy nhất khả quan (thì tất nhiên là vậy rồi emo_popo_sweat):

flag = md5(key) = d2fa68bb166dde1c720bd0c8fd665bae.

[Sharif University CTF Quals 2013] too hard? (crypto300)

Chúng ta cần tìm một số x sao cho:

(11111111111111111111112 ^ x) % 123456790123456790123454320987654320987654321 = 1173805180904286755555543817503746512689

Nội dung chính

Đây là một bài mà ngay khi đọc đề, trong trái tim nhân hậu và đầy thiện lương của mình sớm phát sinh hai luồng suy nghĩ trái chiều:

  • Một là mình có thể làm được nó emo_popo_sure
  • Hai là, nếu mình có thể làm được nó chỉ sau ánh mắt nhìn đầu tiên, thì sao nó lại có giá 300 điểm?

Một lần nữa lại buộc phải sống chậm lại để thấy được rằng, chẳng có gì là công bằng, chẳng có gì là hoàn hảo cả. Một bài 300 điểm có thể dễ, một bài 100 điểm có thể khó, sống tử tế thì thường ế mà sống khốn nạn thì nhiều người ngó…

Sau vài ba phút lang thang trên Google, đọc qua biết bao là bí kíp Toán học trên trời dưới bể, Féc Ma rồi Ơ Clít, ghi ghi chép chép trên cuốn vở ô ly chi chít chữ, đi đến một phát hiện quan trọng như đang mộng:

Output:

Tính hiệu của chúng theo từng cặp, lạ kỳ thay khi tất cả đều bằng 11111111111111111111111 surrender

Ok, case closed emo_popo_look_down

Chúng ta xác định x bằng cách:

y = (1173805180904286755555543817503746512689 - 11111111111111111111112) / 11111111111111111111111
  = 105642466281385807

x = y + 1
  = 105642466281385808

flag = md5(x) = d0cacf9701b449aae0b236f1626d5688.

[No cON Name Facebook CTF Quals 2013] Writeups

ACCESS LEVEL 1

Open crypto.js file, copy the parameter of eval() function and execute it in firebug, we will get:

So, to get the goodboy, we must find a number res such that:

((((res * (3 + 1 + 3 + 3 + 7)) >>> 6) / 4) ^ 4153) == 0

We will run the following command in firebug to get res:

((4153 * 4) << 6) / (3 + 1 + 3 + 3 + 7);

It returns res = 62539.294117647056, and 62540 is the value we are looking for. Because the function numberical_value() is very simple, and we can modify every unit of the return value, so we can easily get the valid input to make numberical_value() returns 62540. For example:

AAAAAAAAABAAAAAAAAAuAAAAAAAAAAAAAAAAAAAAAAA

Flag:

Congrats! you passed the level! Here is the key:
23f8d1cea8d60c5816700892284809a94bd00fe7347645b96a99559749c7b7b8

ACCESS LEVEL 2

After installing and running the .apk file in BlueStacks, we noticed that every time we click on the button, a random image is displayed on the screen.

We can easily see that they are part of a complete QRCode image, so we try to see all of it by extracting the .apk with WinRAR and go to “resraw” folder.

This folder contains 17 images, and one of them is just a troll picture, so we have 16 image, with the same size: 97×97 pixels. 16 = 4*4, so the size of complete QRCode sshould be 388×388 (388 = 97*4). Using Photoshop, set grid size to 97×97, we can easily arrange all 16 images and get the complete QRCode:

Scan it will give us flag:

788f5ff85d370646d4caa9af0a103b338dbe4c4bb9ccbd816b585c69de96d9da

ACCESS LEVEL 3

This challenge requires us to enter each character of the password, if entered correctly, a sign ‘*‘ is displayed, otherwise the program will exit immediately.

Open it in IDA, follow the string “Type to win, only what I want to read…” and we will be here:

Very simple! It reads a char from the user, compare it with another hardcoded char, if they differ, then we get badboy. var_8 is a counting variable, which will be increased here:

Seeing the line at 0x40114E, we know that the length of password is 9, and we can easily get it by reading the value of facebookctf_rocks:

So it must be “x20x53x55x52x50x52x49x53x45x21”, or “ SURPRISE!” in plain text.

Just enter this password and we’ll get flag:

-> Congratulations! The key is:
| 9e0d399e83e7c50c615361506a294eca22dc49bfddd90eb7a831e90e9e1bf2fb