Post

Nullcon HackIM 2025 - Writeups

Reverse the seeded number in number generator by given generated numbers + overflow in integer processing in PHP

Nullcon HackIM 2025 - Writeups

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():

  1. 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))]
    
  2. Then it convert the flag part to the hexadecimal representation by
    1
    
    bin2hex()
    
  3. Calculate the decimal representation of the hex
    1
    
    intval()
    
  4. 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)];
    }

sesswriteup

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:

sesssyntax

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]

So our syntax is: sessphp

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.

number 1

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:

number 2 This function have its limit, and if exceed the maximun => becomes negative as below: number 3

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!

This post is licensed under CC BY 4.0 by the author.

Trending Tags