Sunday, November 25, 2018

TUCTF 2018 - Reversing

Table of Contents

Danger Zone (112)

Description:
Difficulty: easy

Legend says, this program was written by Kenny Loggins himself.

In the first challenge we're given a dangerzone.pyc file.

First we can extract the original python file using uncompyle6:

$ uncompyle6 dangerzone.pyc > dangerzone.py

This gives us the original source:

import base64

def reverse(s):
    return s[::-1]


def b32decode(s):
    return base64.b32decode(s)


def reversePigLatin(s):
    return s[-1] + s[:-1]


def rot13(s):
    return s.decode('rot13')


def main():
    print 'Something Something Danger Zone'
    return '=YR2XYRGQJ6KWZENQZXGTQFGZ3XCXZUM33UOEIBJ'


if __name__ == '__main__':
    s = main()
    print s

Looks like we have a lot of unused dead code functions and a return value (s) which is is shown when running the pyc normally:

Something Something Danger Zone
=YR2XYRGQJ6KWZENQZXGTQFGZ3XCXZUM33UOEIBJ

We probably didn't need to recover the python file to solve it, but we'll use the given functions to make our lives easier.

First this looks like base64, so we can use the provided b32decode after reverse, because = always goes at the end of a base64 encoded string:

print b32decode(reverse(s))

This gives us:

Something Something Danger Zone
HPGS{e3q_y1a3_0i3ey04q}G

Looks very close to a flag! : )

Now we have reversePigLatin (rotates misplaced end character to the front) & rot13 remaining:

print rot13(reversePigLatin(b32decode(reverse(s))))

And that was it!

TUCTF{r3d_l1n3_0v3rl04d}

yeahright (149):

Description:
Difficulty: very easy

What an insensitive little program.
Show it who's boss!

nc 18.224.3.130 12345

This challenge went quickly after looking at the strings:

$ r2 yeahright

[0x00000810]> iz
[Strings]
Num Paddr      Vaddr      Len Size Section  Type  String
000 0x00000a98 0x00000a98  40  41 (.rodata) ascii 7h3_m057_53cr37357_p455w0rd_y0u_3v3r_54w
001 0x00000ac1 0x00000ac1  20  21 (.rodata) ascii *Ahem*... password?
002 0x00000ad6 0x00000ad6  10  11 (.rodata) ascii yeahright!
003 0x00000ae1 0x00000ae1  15  16 (.rodata) ascii /bin/cat ./flag

Looks like it checks a password (hard-coded) and cats the flag if it's correct, trying it locally we get the dummy flag with that 'secret' string:

$ ./yeahright
*Ahem*... password? 7h3_m057_53cr37357_p455w0rd_y0u_3v3r_54w
flag{test-flag-here}

Trying it remotely we get the real flag:

$ echo '7h3_m057_53cr37357_p455w0rd_y0u_3v3r_54w' | nc 18.224.3.130 12345
TUCTF{n07_my_fl46_n07_my_pr0bl3m}

Shoop (370):

Description:
Difficulty: easy

Black Hole Sun, won't you come
and put sunshine in my bag
I'm useless, but not for long
so whatcha whatcha whatcha want?

nc 18.220.56.147 12345

This challenge also had a cat ./flag function with an interesting secret value:

$ rabin2 -z shoop
vaddr=0x00000c24 paddr=0x00000c24 ordinal=000 sz=24 len=23 section=.rodata type=ascii string=Gimme that good stuff:
vaddr=0x00000c3c paddr=0x00000c3c ordinal=001 sz=17 len=16 section=.rodata type=ascii string=Survey Says! %s\n
vaddr=0x00000c4d paddr=0x00000c4d ordinal=002 sz=22 len=21 section=.rodata type=ascii string=jmt_j]tm`q`t_j]mpjtf^   <-
vaddr=0x00000c63 paddr=0x00000c63 ordinal=003 sz=14 len=13 section=.rodata type=ascii string=That's right!
vaddr=0x00000c71 paddr=0x00000c71 ordinal=004 sz=16 len=15 section=.rodata type=ascii string=/bin/cat ./flag
vaddr=0x00000c81 paddr=0x00000c81 ordinal=005 sz=18 len=17 section=.rodata type=ascii string=Close... probably

If we look at an ltrace run of the binary, we can see the expected input buffer size:

$ ltrace -fi ./shoop

[pid 5129] [0x7f2ed20759b6] setvbuf(0x7f2ed1e4a400, 0, 2, 20)                                    = 0
[pid 5129] [0x7f2ed20759d4] setvbuf(0x7f2ed1e4a640, 0, 2, 20)                                    = 0
[pid 5129] [0x7f2ed20759eb] malloc(22)                                                           = 0x7f2ed2516010
[pid 5129] [0x7f2ed2075a09] memset(0x7f2ed2516010, '\0', 22)                                     = 0x7f2ed2516010
[pid 5129] [0x7f2ed2075a1a] printf("Gimme that good stuff: "Gimme that good stuff: )             = 23
[pid 5129] [0x7f2ed2075a31] read(0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA , "AAAAAAAAAAAAAAAAAAAAA", 21)  = 21
[pid 5129] [0x7f2ed2075a3e] malloc(21)                                                           = 0x7f2ed2516030
[pid 5129] [0x7f2ed2075a95] memcpy(0x7f2ed2516010, "AAAAAAAAAAAAAAAAAAAAA", 21)                  = 0x7f2ed2516010
[pid 5129] [0x7f2ed2075b34] memcpy(0x7f2ed2516010, "<<<<<<<<<<<<<<<<<<<<<", 21)                  = 0x7f2ed2516010
[pid 5129] [0x7f2ed2075b4c] printf("Survey Says! %s\n", "<<<<<<<<<<<<<<<<<<<<<"Survey Says! <<<<<<<<<<<<<<<<<<<<<)  = 35
[pid 5129] [0x7f2ed2075b65] memcmp(0x7f2ed2516010, 0x7f2ed2075c4d, 21, -1)                       = 0xffffffd2
[pid 5129] [0x7f2ed2075b8f] puts("Close... probably"Close... probably)                           = 18
[pid 5129] [0xffffffffffffffff] +++ exited (status 0) +++

It reads 21 bytes using read(...) and malloc's the same length.

If we look at the secret string from the strings output, it's also 21 bytes:

jmt_j]tm`q`t_j]mpjtf^

Seems like it transformed the input character 'A' into '<', this would be a shift of 5 if it's that simple:

>>> print ord('A') - ord('<')
5

Trying a couple other values it seems to keep the pattern:

$ ./shoop
Gimme that good stuff: AAAABBBBCCCCDDDDEEEE
Survey Says! >>>====<<<<@@@@????>
Close... probably

We can verify this with a small python list comprehension:

>>> ''.join([chr(ord(x) + 5) for x in '>>>====<<<<@@@@????>'])
'CCCBBBBAAAAEEEEDDDDC'

Hmmmmm, why is it out of order? Seems like it's a shift and order modification.

Let's see if we can map both at once:

$ python -c 'import string; print string.uppercase[:21]' | ./shoop
Gimme that good stuff: Survey Says! FEDCBA@?>=<PONMLKJIHG
Close... probably

$ python
>>> encoded = ''.join([chr(ord(x) + 5) for x in 'FEDCBA@?>=<PONMLKJIHG'])
>>> encoded # inspect encoding placement
'KJIHGFEDCBAUTSRQPONML'
>>> ''.join(sorted(encoded)) # check for data loss
'ABCDEFGHIJKLMNOPQRSTU'

Looks like we don't lose any characters during the encoding, so we could map each value from the encoded to the plaintext:

dest = 'KJIHGFEDCBAUTSRQPONML'
source = 'ABCDEFGHIJKLMNOPQRSTU'

# dest -> source
mapping = map(lambda ch: source.find(ch), dest)

print mapping

This gives us the index transposition map:

[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11]

Next up is to convert the secret string we found using the two transformations above -- shift & order.

Starting with the shift, it gives us something clean:

>>> ''.join([chr(ord(x) + 5) for x in 'jmt_j]tm`q`t_j]mpjtf^'])
'orydobyreveydobruoykc'

Now we can modify our script to get the correct order:

dest = 'KJIHGFEDCBAUTSRQPONML'
source = 'ABCDEFGHIJKLMNOPQRSTU'
target = 'orydobyreveydobruoykc'

# dest -> source
mapping = map(lambda ch: source.find(ch), dest)
result = ''.join(map(lambda m: target[m], mapping))

print result

The Result:

everybodyrockyourbody

Attempting this as the password locally drops the fake flag:

$ ./shoop

Gimme that good stuff: everybodyrockyourbody
Survey Says! jmt_j]tm`q`t_j]mpjtf^
That's right!
flag{test-flag-here}

Trying the remote, we get the flag!

$ echo 'everybodyrockyourbody' | nc 18.220.56.147 12345

Gimme that good stuff: everybodyrockyourbody
Survey Says! jmt_j]tm`q`t_j]mpjtf^
That's right!
TUCTF{5w337_dr34m5_4r3_m4d3_0f_7h353}
TUCTF{5w337_dr34m5_4r3_m4d3_0f_7h353}