Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
EricLung committed Sep 26, 2024
0 parents commit e98eb9f
Show file tree
Hide file tree
Showing 79 changed files with 1,383 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
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.
38 changes: 38 additions & 0 deletions t01-leaked-secret/writeup/README.md
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}`
Binary file added t01-leaked-secret/writeup/img/001.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t01-leaked-secret/writeup/img/002.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t01-leaked-secret/writeup/img/003.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t01-leaked-secret/writeup/img/004.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t01-leaked-secret/writeup/img/005.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
97 changes: 97 additions & 0 deletions t02-scratch-passcode/writeup/README.md
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)
Binary file added t02-scratch-passcode/writeup/images/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t02-scratch-passcode/writeup/images/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t02-scratch-passcode/writeup/images/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t02-scratch-passcode/writeup/images/4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t02-scratch-passcode/writeup/images/5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t02-scratch-passcode/writeup/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.
Binary file added t02-scratch-passcode/writeup/passcode.xlsx
Binary file not shown.
Binary file added t03-elb-ii/writeup/001.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions t03-elb-ii/writeup/README.md
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).
Binary file added t04-crackme/writeup/001.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t04-crackme/writeup/002.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added t04-crackme/writeup/003.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions t04-crackme/writeup/README.md
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.
26 changes: 26 additions & 0 deletions t04-crackme/writeup/sol.py
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}
65 changes: 65 additions & 0 deletions t04-crackme/writeup/src/EnterPasswordActivity.java
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();
}


}
});
}
}
14 changes: 14 additions & 0 deletions t04-crackme/writeup/src/MainActivity.java
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);
}
}
Loading

0 comments on commit e98eb9f

Please sign in to comment.