# Nahamcon 2022 Writeup các bài Reverse, Mobile trong giải Nahamcon CTF 2022. # Reverse Engineer ## babyrev ![](https://i.imgur.com/pNdGnPe.png) Nhìn sơ qua, chương trình yêu cầu nhập username và password. Có thể thấy username là `bossbaby`, nhưng password được kiểm tra qua hàm `sub_12B2`. Chúng ta xem thử hàm này có gì. ![](https://i.imgur.com/Enk9Zdk.png) > Note: Thông thường mình sẽ không bao giờ để code như thế này mà phân tích. Một trong những quá trình mình thường làm trước khi tiếp tục phân tích những đoạn code thế này là sửa lại kiểu dữ liệu hoặc thêm sử dụng một chút static analysis để thêm comment vào những chỗ khó hiểu. ![](https://i.imgur.com/nqHXPro.png) Code nhìn đã đẹp hơn nhiều. Mình có comment ngay hàm `sub_1209`. Vì khi static analyze mình nhận thấy đoạn code trên không quan trọng nên mình sẽ không quan tâm tới. Vậy ta cần xem thử `sub_1209` làm gì với password được nhập vào. ![](https://i.imgur.com/cYwKmsa.png) Như vậy có thể thấy chương trình sẽ lấy password của ta, biến đổi thành một mảng mới dựa trên hàm `sub_1209` và sẽ so sánh với mảng `cmpBytes`. Tới đây mình viết script dịch ngược lại quá trình chuyển đổi kí tự để lấy flag. ```python= a = [102, 217, 392, 833, 1984, 1785, 6308, 149, 266, 469, 892, 937, 1968, 6505, 295, 419, 452, 697, 1876, 2185, 3920, 496, 596, 729, 1368, 1393, 2340, 4121, 834, 941, 1288, 1769, 2608, 4321, 4740, 1280, 1490, 1869] for i, c in enumerate(a): print(chr((c - i*i) >> (i % 7)), end = '') ``` `flag{7bdeac39cca13a97782c04522aece87a}` ## Kamikaze Như thường lệ mình sẽ xài IDA để phân tích. Nhưng chúng ta sẽ gặp lỗi đầu tiên: ![](https://i.imgur.com/l2kWnEE.png) Nếu chúng ta search lỗi này trên google thì sẽ hiện ra trang chủ IDA có kết quả như sau: ![](https://i.imgur.com/34TXVnE.png) Kiểm tra lại hàm `d_run_main`: ![](https://i.imgur.com/Fy70v3W.png) Có thể thấy tới một lúc nào đó size của stack frame sẽ lên tới 0x1421A8, đã vượt quá mức mà IDA cho phép để decompile. Lúc này chúng ta có 2 lựa chọn: Đọc code Assembly trên IDA hoặc xài decompiler khác có khả năng decompile hàm này. Trong quá trình thi mình đã chọn cách thứ 2, tool mình sử dụng sẽ là Ghidra: ![](https://i.imgur.com/0RlcKl7.png) Lúc này mình liền thấy chương trình sẽ gọi tới hàm `_d_run_main2`. Không nghĩ nhiều, mình liền bật IDA và bắt đầu debug. Nhưng khi chạy chương trình thì mình lại gặp lỗi thứ 2: ![](https://i.imgur.com/tKJU46i.png) Hiện tại mình đang chạy chương trình trên Ubuntu 20.04, phiên bản này đang xài Glibc 2.31 nên không tương thích với chương trình. Rất may mình đã cài bản Ubuntu 21.10 nên mình sẽ xài bản Ubuntu này để debug. Tuy nhiên nếu bạn không muốn tải Ubuntu nhưng vẫn muốn debug chương trình thì các bạn có thể dựng Docker của Ubuntu 21.10 để chạy (Kĩ thuật này mình sẽ nói ở phần tiếp của writeup). ![](https://i.imgur.com/wVh7BoN.png) Sau khi debug một hồi, mình nhận thấy đây mới thực sự là hàm chính của chương trình: `Dmain`. Logic của chương trình khá ngắn. Sau đó mình lại đi vào các hàm nhỏ để tìm xem có đoạn code nào liên quan đến flag không. ![](https://i.imgur.com/FJie0iv.png) ![](https://i.imgur.com/CurYqox.png) ![](https://i.imgur.com/oe9D3Ss.png) Mình thấy hàm `D3app6stage1FZAya` và `D3app6stage2FAyaZQe` nhìn rất "CTF". Nên mình liền đặt breakpoint ở hàm này và chạy tiếp chương trình với hi vọng sẽ tìm được flag. ![](https://i.imgur.com/SCLFcUr.png) `flag{88021cd97183363ab4b3c6bf45199107}` ## Time Machine Khi mình file chương trình thì nhận thấy đây không phải là một file ELF bình thường: ![](https://i.imgur.com/vnTOi0R.png) Sử dụng lệnh file, chúng ta thấy chương trình sử dụng kiến trúc S/390. Với việc kiến trúc khác cũng đồng nghĩa với việc ngôn ngữ Assembly sẽ khác. Như vậy để có thể dịch ngược chúng ta cần phải có disassembler hỗ trợ kiến trúc ấy. Vì thế thông thường, khi gặp những bài như này mình sẽ tìm các plugin có thể diassemble được kiến trúc này trên IDA hoặc các tool disassemble khác. Nhưng ở bài này mình không tìm được mặc dù [Release Note bản 7.4 của IDA](https://www.hex-rays.com/products/ida/news/7_4/) có ghi sẽ hỗ trợ kiến trúc S/390x. Tới đây mình sẽ xài cách tà đạo: Dựng môi trường để debug, cụ thể hơn mình sẽ dựng docker image là một con Ubuntu chạy trên nền kiến trúc S/390x. Dựa trên [link này](https://docs.gitlab.com/omnibus/development/s390x.html#vm-provisioning) thì mình sẽ pull về 2 image là `multiarch/qemu-user-static` và `s390x/ubuntu` để dựng. Tuy nhiên trong lúc thi mình không pull `s390x/ubuntu` mà mình pull `s390x/fedora`. ```bash= docker pull s390x/fedora docker pull multiarch/qemu-user-static docker run --rm --privileged multiarch/qemu-user-static --reset -p yes docker run --rm -it s390x/fedora bash ``` ![](https://i.imgur.com/WipfX2O.png) Thế là chúng ta đã có được chiếc Ubuntu chạy trên kiến trúc S/390x. Tới đây mình chỉ việc đẩy chương trình vào docker và dịch ngược. File Assembly có thể lấy [tại đây](https://drive.google.com/file/d/13ilDZAPczCSNlZzzGkSJpX1-toCcXxy2/view?usp=sharing). Do luồng chương trình khá đơn giản nên mình quyết định phân tích tĩnh chương trình. ![](https://i.imgur.com/kyh1osZ.png) Tại đoạn chương trình này có rất nhiều hàm `memcmp`. Mình liền suy nghĩ đến việc đây là nơi để check flag. Như vậy thì `<b_54.4939>` là input của mình, và các giá trị như `<b_37.4933>` sẽ là nơi lưu trữ flag. Mình liền tìm tới những nơi có `<b_37.4933>`: ![](https://i.imgur.com/G9HT9fW.png) Tới đây mình liên nhận ra các giá trị đó chính là các phần của flag. Mình liền viết đoạn script nhỏ để ghép các phần của flag lại với nhau: ```python= a = [b''] * 8 a[0] = bytes.fromhex(hex(828650081)[2:]) a[(33-5) // 4] = bytes.fromhex(hex(842020965)[2:]) a[(25-5) // 4] = bytes.fromhex(hex(943142969)[2:]) a[(21-5) // 4] = bytes.fromhex(hex(926036325)[2:]) a[(9-5) // 4] = bytes.fromhex(hex(1697855329)[2:]) a[(29-5) // 4] = bytes.fromhex(hex(1631008357)[2:]) a[(13-5) // 4] = bytes.fromhex(hex(1664364643)[2:]) a[(17-5) // 4] = bytes.fromhex(hex(1650667618)[2:]) print(b''.join(a)) ``` `flag{1d2ae37ac40cbc0b721e8789a76e208e}` # Mobile ## Mobilize Chúng ta được cho một file apk, viết tắt cho cụm từ `Android Package`. Ta có thể chắc được rằng file này sẽ chạy trên điện thoại Android nên trong bài này mình sẽ sử dụng Android Emulator để chạy file apk, cụ thể hơn mình sẽ xài Android Studio. > Thông thường mình sẽ check sdk version của file apk trước vì chúng ta cần phải có android có sdk version bằng hoặc cao hơn của file apk thì mới có thể install được ![](https://i.imgur.com/vOSrGpI.png) Trong đây thông số duy nhất mình quan tâm chính là `minSdkVersion`, có giá trị là 0x1d (29). Vậy mình sẽ cài Android 10 vào để có thể chạy bài này. ![](https://i.imgur.com/ceQvLHC.png) Giao diện hầu như chỉ có nhiêu đây. Lúc này mình sẽ sử dụng jadx để decompile và đọc code. ![](https://i.imgur.com/dzaxDuk.png) Code hầu như không có gì cả. Nhưng nếu ta để ý phần cấu trúc của file: ![](https://i.imgur.com/Mvao3pO.png) Có thư mục `Resources/lib`. Điều này có nghĩa file apk này có Native Library. Các bạn có thể đọc [ở đây](https://www.ragingrock.com/AndroidAppRE/reversing_native_libs.html#:~:text=Android%20applications%20can%20contain%20compiled,in%20C%20or%20C%2B%2B.) để hiểu rõ, nhưng nói tóm tắt sơ thì một phần của chương trình này được vận hành bằng các thư viện trong thư mục `Resources/lib` đó. Đa số các code này được viết bằng ngôn ngữ C/C++. Như vậy mình sẽ lấy các file trong thư mục này ra, đưa vào IDA và phân tích. Một trong những cách thông thường để lấy được thư mục này chính là đổi đuôi file từ `.apk` thành `.zip`, sau đó extract file vừa đổi tên và vào thư mục `lib`. Tuy nhiên trong bài này các file trong thư mục `lib` có vẻ không quan trọng, mình sẽ nói vấn đề này ở phần tiếp theo của writeup. Vậy flag có thể giấu ở đâu? Một trong những cách tà đạo khác để giấu flag chính là giấu dưới dạng string. Vào thư mục `res/values/strings.xml` và tìm chuỗi `flag{` ![](https://i.imgur.com/kGBMUBT.png) `flag{e2e7fd4a43e93ea679d38561fa982682}` ## OTP Vault Install APK vào Android và chạy thử app. ![](https://i.imgur.com/t8rqQ1Z.png) Có vẻ đã quá rõ ràng, chúng ta cần nhập đúng OTP thì khả năng cao sẽ có flag. Chuyển qua jadx để đọc code. ![](https://i.imgur.com/NNNTHI7.png) Đọc sơ code một hồi, mình biết ngay chúng ta đang đụng tới React Native. Đa số các dòng code logic chính của React Native sẽ nằm ở thư mục `Resources/asset`, đây là thư mục chứa file Javascript và cũng có thể sẽ chứa đoạn code check flag của chúng ta. ![](https://i.imgur.com/26PPeNV.png) Mình dùng JS Beautify để giúp chúng ta có thể đọc code dễ hơn. Vì có quá nhiều dòng code, nên mình tìm thử tìm chuỗi `Insert your OTP` trong file javascript để có thể đi thẳng tới đoạn code check flag. ![](https://i.imgur.com/Qisz8zx.png) Nhìn sơ code thì mình thấy khả năng cao khi nhập đúng OTP, chương trình sẽ tự động kết nối với `http://congon4tor.com:7777/flag` với header`Authorization: Bearer KMGQ0YTYgIMTk5Mjc2NzZY4OMjJlNzAC0WU2DgiYzE41ZDwN`. Thay vì mình tìm cách nhập đúng OTP, mình sẽ kết nối thẳng đển URL trên để lấy flag luôn. Để làm điều này, mình sẽ xài Burp Suite: ![](https://i.imgur.com/0fCPKSL.png) `flag{5450384e093a0444e6d3d39795dd7ddd}` ## Click Me! ![](https://i.imgur.com/vWLQBjp.png) Cứ mỗi lần click vào hình cái bánh, biến đếm sẽ tăng lên một. Đem vào jadx để xem thử xem chương trình có gì. ![](https://i.imgur.com/e7GrPf2.png) Đây là 2 đoạn code chính của chương trình. `cookieViewClick` sẽ tăng biến đếm cho chúng ta mỗi lần click vào cái bánh. Tuy nhiên biến đếm chỉ có thể tăng tới tối đa đến 13371337. `getFlagButtonClick` sẽ kiểm tra xem nếu ta click đủ 99999999 lần chưa, nểu đủ chúng ta sẽ có flag. Flag sẽ được lấy từ hàm `getFlag()` trong Native Library. Bật IDA phân tích Native Library và tìm đến hàm `Java_com_example_clickme_MainActivity_getFlag`, chúng ta có thể thấy chương trình phải mất một lúc decrypt flag, sau đó mới trả flag về cho chúng ta. Tới đây mình sẽ xài Frida để hook thẳng vào code Java, sửa lại biến Click đến 99999999 lần. ```python= # python template import frida, sys def on_message(message, data): if message['type'] == 'send': print("[*] {0}".format(message['payload'])) elif message['type'] == 'error': print("[-] {0}".format(message['description'])) else: print(message) jscode = open('script.js', 'r').read() ## spawn process device = frida.get_usb_device(1) pid = device.spawn(["com.example.clickme"]) process = device.attach(pid) device.resume(pid) script = process.create_script(jscode) script.on('message', on_message) print('[*] Running CTF') script.load() sys.stdin.read() ``` Script.js ```javascript= // Hooking Java function Java.perform(function () { var MainActivity = Java.use('com.example.clickme.MainActivity'); var onClick = MainActivity.getFlagButtonClick; onClick.implementation = function (v) { send('onClick'); this.CLICKS.value = 99999999; onClick.call(this, v); }; }); ``` Kết quả: ![](https://i.imgur.com/zPqQqeR.png) `flag{849d9e5421c59358ee4d568adebc5a70}` ## Secure Notes ![](https://i.imgur.com/R5z49WM.png) Có vẻ giống bài OTP, bài này cũng yêu cầu mình nhập passcode, nếu đúng sẽ trả về flag. Nhưng khi mở jadx lên thì mình thấy code nhìn rất rối: ![](https://i.imgur.com/zaQNbu2.png) Tuy nhiên trong đoạn code chính này mình thấy chương trình có đọc một file tên `notes.db`. Mình liền kiểm cây thư mục thì có một file khá khả nghi. ![](https://i.imgur.com/dn0cvKy.png) Trong lúc thi mình đã cố gắng tìm xem hàm nó liên quan tới việc nhập passcode hoặc decrypt file này, nhưng không mang về kết quả gì nhiều cả. Lúc này mình xài `fridump` để dump hết tất cả memory của cái process này và đọc xem có hint nào về passcode mình nhập không. Đọc một lúc mình phát hiện chương trình có xài đến `AES`. Mình quay lại jadx và tìm những đoạn code liên quan đến `AES`: ![](https://i.imgur.com/dpA9Brd.png) ![](https://i.imgur.com/Iw8lvfd.png) Tìm các reference đến hàm này: ![](https://i.imgur.com/fhhamnF.png) Ngon cơm, như vậy đã quá rõ: Mình sẽ nhập vào passcode, chương trình sẽ nối passcode này lại 4 lần để tạo thành key, từ đó decrypt file `db.encrypted` của mình bằng `AES ECB`. Tới đây mình chỉ viêc bruteforce cái key và tìm flag: ```python= from Crypto.Cipher import AES from Crypto.Util.Padding import pad from string import digits data = open('db.encrypted', 'rb').read() for i1 in digits: for i2 in digits: for i3 in digits: for i4 in digits: key = i1 + i2 + i3 + i4 key = key * 4 key = key.encode() cipher = AES.new(key, AES.MODE_ECB) # Create a AES cipher object with the key using the mode CBC pt = cipher.decrypt(data) # Pad the input data and then encrypt if b'flag' in pt: print(pt) exit(0) ``` `flag{a5f6f2f861cb52b98ebedcc7c7094354}`