-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e98eb9f
Showing
79 changed files
with
1,383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# HKCERT CTF 2024 Training | ||
|
||
這個儲存庫記載了 HKCERT CTF 2024 所有訓練題目的題解。 | ||
|
||
This repository contains all writeup of training questions HKCERT CTF 2024 training workshop. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Solution | ||
|
||
## Tools | ||
|
||
* [Wireshark](https://www.wireshark.org/download.html) | ||
|
||
## Train of thought | ||
|
||
Two files are provided in this challenge: | ||
1. PCAP | ||
2. SSL session key | ||
|
||
We may decude that this challenge will require both files for TLS decrypt. | ||
|
||
### Open PCAP | ||
|
||
Once we downloaded Wireshark and open it up, we may see it contains the TLS traffic. These traffic are encrypted and we cannot read the flow directly. | ||
|
||
![](./img/001.PNG) | ||
|
||
### Key Import | ||
|
||
![](./img/002.PNG) | ||
![](./img/003.PNG) | ||
|
||
By doing so, the data will be decrypted. Note the change in PCAP. | ||
|
||
![](./img/004.PNG) | ||
|
||
### Read Flag | ||
|
||
From the description, the flag is hide in `/flag.php`. Row 13 and 15 shows an error 401 and no data is returned. | ||
However, row 27 shows an success login attempt and flag is obtained. | ||
|
||
![](./img/005.PNG) | ||
|
||
## Flag | ||
`hkcert22{r34d1ng_pcap_1s_fun}` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# Scratch passcode - Solution | ||
|
||
The scratch passcode is a scratch game that require user to input a 4-digits passcode to the keypad, and if it is correct it will return the flag. | ||
|
||
![](./images/1.png) | ||
|
||
So how can we crack the passcode? | ||
|
||
The scratch program won't know what is a correct passcode, unless it is programmed to do so. Therefore, we can view its source code and find out how it knows what is a correct passcode, and we can hack in to it! | ||
|
||
Click "View Inside" button of the project page. | ||
|
||
![](./images/2.png) | ||
|
||
The program is short but could be complex for beginners. What to look for? Well, if you tried entering a few wrong passcodes you would know it will error and have a special sound and graphic effect. We could start from the success / failure indication, then find the PIN code checking logic from that. | ||
|
||
You should discover this code snippet: | ||
|
||
![](./images/3.png) | ||
|
||
When `userinput`, it will do some calculation, and `if <decrypted> contains "hkcert22{", then success`. Sounds like it performs 'decryption' on the flag based on the PIN code? | ||
|
||
Look carefully on the line 5 (`set decrypted to ...`), it is trying to divide each item in `memory` by `input`. What are they? Let's show the variables: | ||
|
||
![](./images/4.png) | ||
|
||
So `memory` contains some large numbers, and the input is the passcode inputted. | ||
|
||
![](./images/5.png) | ||
|
||
Let's right-click and export them out to Excel for easier processing. | ||
|
||
## Encoding | ||
|
||
In this scratch program, it is representing characters in numbers. We had the characters table which looks like this: | ||
|
||
``` | ||
1 a | ||
2 b | ||
3 c | ||
4 d | ||
5 e | ||
6 f | ||
7 g | ||
8 h | ||
... | ||
34 8 | ||
35 9 | ||
36 0 | ||
37 { | ||
38 } | ||
39 - | ||
40 _ | ||
``` | ||
|
||
Which in line 5 `item {...} of <characters>`, it will get the corresponding character by the number ("index"). | ||
|
||
In real life, computer works in numbers too. We had a [ASCII encoding table](https://en.wikipedia.org/wiki/ASCII) to map characters into numbers, it looks like: | ||
|
||
``` | ||
141 a | ||
142 b | ||
143 c | ||
... | ||
``` | ||
|
||
## Multiplication | ||
|
||
So in line 5, the program divide the encrypted numbers (`memory`) by our PIN input (`input`), one by one, then map it into alphabets base on the `characters` table and check if it contains `hkcert22`. | ||
|
||
A educated guess would be the `memory` contains the flag. Let's look at the first three number in `memory`: | ||
|
||
``` | ||
57992 | ||
79739 | ||
21747 | ||
``` | ||
|
||
In our assumption, the `memory` contains the flag, it would start by `hkcert22`, that means `57992 = 'h'` and `79739 = 'k'`, and so on. | ||
|
||
How to get the PIN code? | ||
|
||
Well, the encryption process is just multiplication, and `'h' = 8`, so to get the PIN code you can just: | ||
|
||
``` | ||
57992 / 8 = 7249 | ||
``` | ||
|
||
For second letter: `79739 / 7249 = 11 = 'k'`. | ||
|
||
So we verified our assumption and you got the passcode. Congrats! | ||
|
||
## Excel | ||
|
||
We just cracked the PIN code by hand, and you can just input the number into the keypad to get the flag, but you can also use [Excel](./passcode.xlsx) to help on your calculation. | ||
|
||
![](./images/6.png) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# Write-up | ||
|
||
## Challenge Background | ||
|
||
We are given a link to a web blog and its source code. In the blog, there is a locked blog post, which can be viewed only if the user is signed in as _admin_. | ||
|
||
![](./001.png) | ||
|
||
Moreover, there are multiple features on the site: | ||
|
||
- view blog posts | ||
- log in as a user | ||
- reset password on behalf on a user | ||
|
||
### Getting Started | ||
|
||
The source code for the login endpoint is simple, as given in `app.js`: | ||
|
||
```javascript | ||
app.post('/login', function (req, res) { | ||
const currentUser = req.user | ||
const { username, password } = req.body | ||
|
||
const user = getUserByUsernameAndPassword(username, password) | ||
if (!user) { | ||
return res.render('login', { | ||
currentUser, | ||
errorMessage: 'Your username or password is incorrect.' | ||
}) | ||
} | ||
|
||
const token = signToken(user) | ||
res.cookie('elb2_token', token) | ||
|
||
return res.render('login', { | ||
currentUser, | ||
successMessage: 'Logged in successfully.' | ||
}) | ||
}) | ||
``` | ||
|
||
However, `getUserByUsernameAndPassword` uses `safeEqual`. This function is a bit suspicious... | ||
|
||
```javascript | ||
/** | ||
* safeEqual checks if the given strings are identical. Runs in constant time, | ||
* which should be invulnerable from timing attacks. | ||
* References: | ||
* - https://securitypitfalls.wordpress.com/2018/08/03/constant-time-compare-in-python/ | ||
* - https://www.chosenplaintext.ca/articles/beginners-guide-constant-time-cryptography.html | ||
* | ||
* @param {string} a | ||
* @param {string} b | ||
* @returns a boolean indicating whether the two strings are equal. | ||
*/ | ||
function safeEqual (a, b) { | ||
let isEqual = true | ||
|
||
if (a.length !== b.length) return false | ||
|
||
for (let i = 0; i < a.length; i++) isEqual &&= a.indexOf(i) == b.indexOf(i) | ||
return isEqual | ||
} | ||
``` | ||
|
||
Ideally `safeEqual` should be implemented such that the check is performed in constant time (not in $\mathcal{O}(1)$ as suggested in complexity theory, but the running time should be "consistent" such that attackers cannot recover the password from the running time of the function). | ||
|
||
### Fantastic `safeEqual` and How to Exploit It | ||
|
||
In short, it checks if `a[0] == b[0]` and `a[1] == b[1]` and so on; and it does _not_ return early when it finds a mismatch. In that way, the number of checks would be the same... Unfortunately the check is different from what I mentioned. | ||
|
||
`a.indexOf(i)` returns `-1` if `i` is not a substring of `a`, and the index of the first occurrance of `i` otherwise. For example: | ||
|
||
```javascript | ||
// * 7th character, zero-indexed | ||
'9RC+xEH3AfCfHnLojEmHLkpQ6kM='.indexOf(3) == 7 | ||
|
||
// '5' is not its substring | ||
'9RC+xEH3AfCfHnLojEmHLkpQ6kM='.indexOf(5) == -1 | ||
``` | ||
|
||
Recall that the `safeEqual` function takes two base64-encoded SHA1 digests, which composes of 27 characters from `A-Z`, `a-z`, `0-9`, `+` and `/`, followed by one `=`. It is easy to generate passwords with its hash without `0-9`, the probability would be $(54/64)^{27} \approx 1.018\%$, which is around 1 in 100. | ||
|
||
### Signing in as the Admin | ||
|
||
We can write a Python snippet to generate such passwords: | ||
|
||
```python | ||
import os, base64, hashlib | ||
|
||
while True: | ||
# Generates a 16-character password, where each letter being 0-9 or a-f. | ||
password = os.urandom(8).hex() | ||
# Computes the SHA-1 hash of the password and encode it in base64. | ||
h = base64.b64encode(hashlib.sha1(password.encode()).digest()) | ||
# Checks if the digest has any of the 0, 1, ..., 9. | ||
# If not, then we will use that as the password. | ||
if len(set(h) & set(b'0123456789')) == 0: break | ||
|
||
print(f'{password = }') | ||
``` | ||
|
||
One password generated would be `b2180010404f4d2c` and it's SHA-1 digest is `oR+kxuWBMBia+ndR+SWXbRJbw/o=`. It does not contain `0`, `1`, ..., nor `9`! | ||
|
||
On the other hand, we will need to reset the admin's password until their hash does not contain 0-9 as well. This is the approach to attack, after all: | ||
|
||
1. Generate a password $p$ with its SHA-1 digest not having any of the numeral digits. | ||
2. Reset the admin's password and sign in to admin's account with the password $p$... Repeat until it works (this takes around 100 times). |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Write-up | ||
|
||
## Prologue | ||
This chal is aiming ask participants to decompile and use z3 to recover the flag. | ||
|
||
## Replay | ||
Use `dex-tool` to convert APK to jar. You can download the tool [here](https://github.com/pxb1988/dex2jar/releases/tag/v2.2-SNAPSHOT-2021-10-31). | ||
|
||
```cmd | ||
.\d2j-dex2jar.bat ..\CrackMe.apk | ||
``` | ||
![](./001.PNG) | ||
|
||
Use `jd-gui` for decompiling jar (`EnterPasswordActivity.class`), you may see the logic handling flag checking: | ||
![](./002.PNG) | ||
|
||
Use `z3` to solve the SMT, or it's simple enough to do it by handling. Check out `sol.py` for z3 usage. | ||
![](./003.PNG) | ||
|
||
However, the inverse modular does not include the flag format. The inverse modular only handle strings within the bracket. Add the flag format as mentioned in `EnterPasswordActivity.class` (note the `substring` function). | ||
|
||
## Flag | ||
|
||
`hkcert22{w31c0m3_t0_w0r1d_0f_d1scr3t3_m4th_4nd_1t_1s_st4rt_0f_t0rtur}` | ||
|
||
## Epilogue | ||
Andriod Studio sucks. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from z3 import * | ||
s=[Int('serial%d' % i) for i in range(60)] | ||
solver = Solver() | ||
|
||
for num in s: | ||
solver.add(num > 32) | ||
solver.add(num < 128) | ||
|
||
mod = [[0, 9, 2], [2, 7, 12], [0, 5, 10], [1, 0, 8], [6, 4, 9], [4, 10, 5], [2, 7, 12], [4, 7, 4], [4, 6, 12], [6, 4, 9], [4, 7, 4], [0, 9, 2], [6, 4, 9], [2, 4, 10], [0, 5, 10], [2, 1, 9], [4, 7, 4], [6, 4, 9], [4, 3, 11], [4, 7, 4], [2, 1, 9], [0, 5, 10], [3, 5, 11], [1, 0, 8], [2, 4, 10], [2, 7, 12], [4, 6, 12], [2, 7, 12], [4, 7, 4], [4, 10, 5], [3, 8, 0], [4, 6, 12], [6, 5, 0], [4, 7, 4], [3, 8, 0], [5, 0, 6], [2, 1, 9], [4, 7, 4], [0, 5, 10], [4, 6, 12], [4, 7, 4], [0, 5, 10], [3, 5, 11], [4, 7, 4], [3, 5, 11], [4, 6, 12], [3, 8, 0], [2, 4, 10], [4, 6, 12], [4, 7, 4], [6, 4, 9], [4, 3, 11], [4, 7, 4], [4, 6, 12], [6, 4, 9], [2, 4, 10], [4, 6, 12], [5, 7, 0], [2, 4, 10], [2, 7, 12]] | ||
|
||
for i in range(60): | ||
solver.add(s[i] % 7 == mod[i][0]) | ||
solver.add(s[i] % 11 == mod[i][1]) | ||
solver.add(s[i] % 13 == mod[i][2]) | ||
|
||
print(solver.check()) | ||
answer=solver.model() | ||
print(answer) | ||
|
||
tidy_answer = "" | ||
for each in s : | ||
tidy_answer += str(chr(int(str(answer[each])))) | ||
|
||
print("hkcert22{"+tidy_answer+"}") | ||
|
||
#flag: hkcert22{w31c0m3_t0_w0r1d_0f_d1scr3t3_m4th_4nd_1t_1s_st4rt_0f_t0rtur3} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package org.blackb6a.crackme; | ||
|
||
import androidx.appcompat.app.AppCompatActivity; | ||
|
||
import android.content.Intent; | ||
import android.content.SharedPreferences; | ||
import android.os.Bundle; | ||
import android.view.View; | ||
import android.widget.Button; | ||
import android.widget.EditText; | ||
import android.widget.Toast; | ||
|
||
import java.io.UnsupportedEncodingException; | ||
|
||
public class EnterPasswordActivity extends AppCompatActivity { | ||
EditText editText; | ||
Button button; | ||
|
||
String password; | ||
|
||
protected boolean verySecureCheck(String text){ | ||
try { | ||
byte[] s = text.substring(9, text.length()-1).getBytes("US-ASCII"); | ||
int[][] mod = {{0, 9, 2}, {2, 7, 12}, {0, 5, 10}, {1, 0, 8}, {6, 4, 9}, {4, 10, 5}, {2, 7, 12}, {4, 7, 4}, {4, 6, 12}, {6, 4, 9}, {4, 7, 4}, {0, 9, 2}, {6, 4, 9}, {2, 4, 10}, {0, 5, 10}, {2, 1, 9}, {4, 7, 4}, {6, 4, 9}, {4, 3, 11}, {4, 7, 4}, {2, 1, 9}, {0, 5, 10}, {3, 5, 11}, {1, 0, 8}, {2, 4, 10}, {2, 7, 12}, {4, 6, 12}, {2, 7, 12}, {4, 7, 4}, {4, 10, 5}, {3, 8, 0}, {4, 6, 12}, {6, 5, 0}, {4, 7, 4}, {3, 8, 0}, {5, 0, 6}, {2, 1, 9}, {4, 7, 4}, {0, 5, 10}, {4, 6, 12}, {4, 7, 4}, {0, 5, 10}, {3, 5, 11}, {4, 7, 4}, {3, 5, 11}, {4, 6, 12}, {3, 8, 0}, {2, 4, 10}, {4, 6, 12}, {4, 7, 4}, {6, 4, 9}, {4, 3, 11}, {4, 7, 4}, {4, 6, 12}, {6, 4, 9}, {2, 4, 10}, {4, 6, 12}, {5, 7, 0}, {2, 4, 10}, {2, 7, 12}}; | ||
|
||
for (int i=0; i<=60; i++) | ||
if (s[i]%7 != mod[i][0] || s[i]%11 != mod[i][1] || s[i]%13 != mod[i][2]) | ||
return false; | ||
|
||
return true; | ||
|
||
} catch (UnsupportedEncodingException e) { | ||
e.printStackTrace(); | ||
return false; | ||
} | ||
} | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
setContentView(R.layout.activity_enter_password); | ||
|
||
editText = (EditText) findViewById(R.id.editText); | ||
button = (Button) findViewById(R.id.button); | ||
|
||
button.setOnClickListener(new View.OnClickListener(){ | ||
@Override | ||
public void onClick(View v) { | ||
String text = editText.getText().toString(); | ||
|
||
if((text.startsWith("hkcert22{")) && | ||
(text.endsWith("}")) && | ||
(verySecureCheck(text))){ | ||
Intent intent = new Intent(getApplicationContext(), MainActivity.class); | ||
startActivity(intent); | ||
finish(); | ||
} else { | ||
Toast.makeText(EnterPasswordActivity.this, "Wrong Password!", Toast.LENGTH_SHORT).show(); | ||
} | ||
|
||
|
||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package org.blackb6a.crackme; | ||
|
||
import androidx.appcompat.app.AppCompatActivity; | ||
|
||
import android.os.Bundle; | ||
|
||
public class MainActivity extends AppCompatActivity { | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
setContentView(R.layout.activity_main); | ||
} | ||
} |
Oops, something went wrong.