ALLES! CTF 2020

ALLES! CTF 2020 with Kurisutina - 93rd

Really fun ctf. I ended up spend my time on crypto challs as my teammates already done all the web :(

[crypto] Actual ASLR 1

I looked at this challenge first because it was from LiveOverflow. The idea of this challenge is reimplementing ASLR using the rand function but in the first part it only requires us to do some guess stuff, nothing to do with the ASLR :v

The challenge give us a binary aaslr which provide 4 options

Welcome To The Actual-ASLR (AASLR) Demo
  1. throw dice
  2. create entry
  3. read entry
  4. take a guess

Select menu Item:
1
[>] Threw dice: 4
Welcome To The Actual-ASLR (AASLR) Demo
  1. throw dice
  2. create entry
  3. read entry
  4. take a guess

Select menu Item:
2
Enter your data (max length: 100):
aaaa
[>] Created new entry at index 0
Welcome To The Actual-ASLR (AASLR) Demo
  1. throw dice
  2. create entry
  3. read entry
  4. take a guess

Select menu Item:
3
Enter entry index (max: 0):
1
[>] 1. (null)
Welcome To The Actual-ASLR (AASLR) Demo
  1. throw dice
  2. create entry
  3. read entry
  4. take a guess

Select menu Item:
4
(1/16) guess next dice roll:
1
(2/16) guess next dice roll:
2
(3/16) guess next dice roll:
3
(4/16) guess next dice roll:
4
(5/16) guess next dice roll:
5
(6/16) guess next dice roll:
6
(7/16) guess next dice roll:
1
(8/16) guess next dice roll:
2
(9/16) guess next dice roll:
3
(10/16) guess next dice roll:
4
(11/16) guess next dice roll:
5
(12/16) guess next dice roll:
6
(13/16) guess next dice roll:
7
(14/16) guess next dice roll:
1
(15/16) guess next dice roll:
2
[!] WRONG!

Open the binary in IDA, I found a take_guess function which give us the flag.

We have to correctly guess 16 times, each time it calls throw_dice to get a number. The throw_dice function calls ranval and force the number to be from 1 to 6.

ranval does some magic bit-shifting with a global variable called RANCTX and return an integer value.

RANCTX is initialized in raninit function and then ranval is called 0x14 times.

ran_init is called once in init_heap. This is where the ASLR implemented but as I said above we don’t need it for the first part. init_heap initialize the random seed, which is RANCTX[3], with time(0).

At this point the idea is simple. We have to sync our time with the server so that the rand() calls later we will return the same value as the server. This is what I came up with in C.

#include <stdio.h>
#include <time.h>

unsigned long RANCTX[4] = {};

unsigned long ranval()
{
    long v0; // ST08_8
    v0 = RANCTX[0] - ((RANCTX[1] << 27) | (RANCTX[1] >> 5));
    RANCTX[0] = ((RANCTX[2] << 17) | (RANCTX[2] >> 15)) ^ RANCTX[1];
    RANCTX[1] = RANCTX[3] + RANCTX[2];
    RANCTX[2] = v0 + RANCTX[3];
    RANCTX[3] = v0 + RANCTX[0];
    return RANCTX[3];
}

void raninit(long time_seed)
{
    RANCTX[0] = 0xF1EA5EEDLL;
    RANCTX[3] = time_seed;
    RANCTX[2] = RANCTX[3];
    RANCTX[1] = RANCTX[2];
    int i = 0;
    for (i = 0LL; i <= 0x13; ++i)
        ranval();
}

void init_heap()
{
    long v0 = time(0) + 1; // add 1 to sync time with challenge server
    raninit(v0);
}

unsigned long throw_dice()
{
    unsigned long random_val;
    random_val = ranval();
    return (unsigned long)((int)random_val + (int)(random_val / 6) * -6 + 1);
}

int main(int argc, char const *argv[])
{
    init_heap();
    ranval();
    ranval();
    int i = 0;
    for (i = 0; i < 16; i++)
    {
        printf("%lun", throw_dice());
    }

    return 0;
}

Some people might want to use Python ctypes to do this but I was lazy to deal with the type difference between the two languages :/ Implementing in C is just copy pasting from IDA.

Then just connect to the server to get the flag. Easy!

from pwn import *

def get_rands():
    l = process('./rand')
    result = l.recvall().decode('ascii')
    l.close()
    return result.split('n')

if __name__ == '__main__':
    # p = process('./aaslr')
    p = process('/bin/ncat --ssl 7b000000a29cdf5d98f6e81d.challenges.broker4.allesctf.net 1337', shell=True)
    rands = get_rands()
    p.sendlineafter('Select menu Item:n', '4')
    for i in range(15):
        print("sending: " + rands[i])
        p.sendlineafter('roll:', rands[i])
    p.interactive()

Flag: ALLES{ILLEGAL_CARD_COUNTING!_BANNED}

[crypto] Doors of Durin

This challenge must be easier if I was a crypto man. In the end I learned a lot about hash collision :D

We are given 2 files, door.c and gatekeeper.py.

  • door.c takes a string up to 254 chars and strcmp it with "sp3akfr1end4nd3nt3r", if the two strings are equal, we get the flag.
#define MAGIC_WORD "sp3akfr1end4nd3nt3r"
int main() {
    char input[255];
    char flag[255];

    scanf("%254s", input);
	printf("You said: %sn", input);

    if (strcmp(input, MAGIC_WORD) == 0)
    {
        int fd = open("./flag", O_RDONLY);
        if (fd == -1)
        {
            printf("Something went wrong! Thats a bug, please report!n");
            return 1;
        }

        read(fd, flag, 254);
        printf("Flag: %sn", flag);
    }
    else
    {
        printf("Nope :/n");
    }
    
	return 0;
}
  • gatekeeper.py get user input as a string, if its hash in goodHashes, pass through. Else, if the hash contains the secret, exit. Otherwise, add the hash into goodHashes and pass through.
while True:
    try:
        currentInput = input("Give me your input:")

        bytesInput = base64.b64decode(currentInput)
        print("Doing magic, stand by")
        hashed = generateSecretHash(bytesInput)

        if hashed in goodHashes:
            print(passToGate(bytesInput))
        else:
            if b"sp3akfr1end4nd3nt3r" in bytesInput:
                print(
                    "Everybody knows the magic words. I can't hear it anymore! Go away! *smash*")
                exit(0)
            else:
                goodHashes[hashed] = bytesInput
                print(passToGate(bytesInput))

    except Exception as e:
        print(e)

User input get hashed like this

def generateSecretHash(byteInput):
    md5 = hashlib.md5()
    sha1 = hashlib.sha1()
    sha256 = hashlib.sha384()

    blake2b = hashlib.blake2b()

    md5.update(byteInput)
    sha1.update(md5.digest())
    md5.update(sha1.digest())

    for i in range(0, 2938):
        sha256.update(md5.digest())

    for k in range(-8222, 1827, 2):
        sha1.update(sha256.digest())
        sha256.update(sha1.digest())

    for j in range(20, 384):
        blake2b.update(sha256.digest())

    return blake2b.hexdigest()

This seems so freaking secure :v How can one challenge hash so many times :(

The problems I realized so far:

  • Storing input hashes as keys in a dictionary may cause a replacement on same hash.
  • It check for occurence of the secret after finding the hash in goodHashes. If we can somehow get our secret’s hash into goodHashes then next time we can just paste in the secret.
  • generateSecretHash calculate MD5 first, which voids the meaning of later hashing. Same MD5 results in same hash after all.
  • Hash collision can occur in MD5. I know it exist but haven’t tried yet.

Everything leads to hash collision :D Let’s try it! Our target is to find a string that have the same hash as our secret "sp3akfr1end4nd3nt3r".

After some quick googling, I found this repository. It indicates that what we are looking for is called MD5 Preimage Attack which is not possible at the moment. But there is still something interesting to try, notice the 2 later options.

In case you don’t know, the author of the repository keep mentioning hash of “files”, mean the same as “strings” because calculating MD5 checksum of a file is actually calculating MD5 hash of its content.

So take a look at the options we have:

  • get two different files with the same MD5: instant.
    This probably not what we want because we are looking to have some control over the file content so we can put the secret in.
  • make two arbitrary files get the same MD5: a few hours.
    Arbitrary indicates that we have some control over the file content. <– Let’s try this.

According to the repo above, one technique used hashclash to calculate the collision. Basically we have to provide 2 files with the prefixes we need and just let it run. This took hours to complete and the CTF ended while it was running :'( I even got a bsod running it haha

In the morning I checked out the writeups. Turns out, people use another technique called “Unicoll” which is much faster. This technique is included in the hashclash repository and use another script. The reason why I didn’t choose it was because it’s labeled “identical-prefix collision” but I need my two strings to be different to bypass the check.

I didn’t read carefully enough. The prefixes are not identical. There are differences, in the 10th byte is +1, 73rd byte is -1, the other are similar. Anyway, I eventually managed to solve it. Unicoll tooks about 36 minutes to run and produced 2 files with the same hash.

Also notice the null byte in the end of the secret. That makes strcmp ignore the gibberish after.

Flag: ALLES{st0p_us1ng_md5_alr34dy_BabyRage_1836d}

wildcat
wildcat
Cat lover, CTF player with u0K++

I play CTF

Next

Related