Join the discord

Battelle: Cyber Challenge

01 Dec, 2017 13:32
I was again lurking for sweet, sweet reverse engineering challenges and stumbled upon these, released by Battelle.

They have a set of challenges - one for reverse engineering, one for shellcode coding and one about forensics.

So, without further adieu, let's see what these challenges have to offer...



Reverse Engineering Challenge


Reverse engineering of application is my thing, and since this challenge was first in the list I started with it.

The story goes like this:
"Feed the Magical Goat"Once upon a time, there was a little reverse engineer who found a special bell. When the bell was struck, they say a magical billy goat appeared looking for food. Everyone knows billy goats will eat anything, but this is all the little reverse engineer had lying around.

Figure out how to feed and please the billy goat using the 16 items listed here. You only have one of each.

And it comes with the following legend:
.Inorganic.Magical objects.Organic.Sources of infinite energy
Bottle: PNewt: nGrass: ,Bunny with a drum: B
Broom: hOld Wise Owl: WLeaf: uCapillary bowl: c
Can: @Potion: pPlant: ;Dipping bird: r
Rug: _Wizard’s Staff: |Seed: .Perpetual motion device: H

Of course, I had no idea what the hell this means.
In matter of fact when I first read it I started wonder what kind of new drugs these guys have in possession.

So I downloaded the executable (that is an ELF image) and executed it:

After executing it, the ELF image deleted itself, so that will be annoying, unless I patch it (wont, because lazy).

So, let's see it in IDA:
billygoat main(), IDA decompileint __cdecl main(int argc, const char **argv, const char **envp) {
    // trimmed variable declarations

    v12 = &argc;
    v11 = *MK_FP(__GS__, 20);
    binName = (char *)*argv;
    v3 = time(0);
    srand(v3);
    puts("You ring the chow bell...");
    sleep(2u);
    puts("OH NO, here comes Billygoat!!");
    sleep(2u);
    print_intro();
    sleep(2u);
    ptr = give_offering("chow.down");

    v7 = *(_DWORD *)ptr;
    v8 = *((_DWORD *)ptr + 1);
    v9 = *((_DWORD *)ptr + 2);
    v10 = *((_DWORD *)ptr + 3);
    if (fill_rumen((int)&v7) && fill_reticulum((int)&v8) && fill_omasum((int)&v9) && fill_abomasum((int)&v10)) {
        puts("Billygoat looks pleased. He bows to you. Congratulations, you are now the goat master!\n");
        printf("flag{%c%c%c%c_%c%c%c%c_%c%c%c%c_%c%c%c%c}\n",
           (char)v7, SBYTE1(v7), SBYTE2(v7), SBYTE3(v7),
           (char)v8, SBYTE1(v8), SBYTE2(v8), SBYTE3(v8),
           (char)v9, SBYTE1(v9), SBYTE2(v9), SBYTE3(v9),
           (char)v10, SBYTE1(v10), SBYTE2(v10), SBYTE3(v10));
    } else {
        puts("Your offering does not sit well with Billygoat!");
        vomit("chow.down");
        print_outro();
        unlink(binName);    // Delete the executable on exit
    }
    free(ptr);
    result = 0;
    v5 = *MK_FP(__GS__, 20) ^ v11;
    return result;
}

Fortunately for me, the symbols aren't stripped and all the functions, even the user defined ones, are there with their original names.
That automatically makes the code hundred times easer to read.

I see how the flag is getting build, and that happens if all the functions - fill_rumen(), fill_reticulum(), fill_omasum() and fill_abomasum() return TRUE.
These functions take the variables v7, v8, v9 and v19 as parameters - a set of variables that is constructed by the ptr variable, that is returned by give_offering().

So give_offering() is my next target:
give_offering(), IDA decompilevoid *__cdecl give_offering(char *file) {
    // trimmed variable declarations

    fd = open(file, 0);
    if (fd == -1) {
        puts("Billygoat looks around... Angr fills his eyes.");
        close(-1);
        unlink(binName);
        print_outro();
        exit(1);
    }
    buf = malloc(0x11u);
    v4 = read(fd, buf, 0x10u);
    close(fd);
    puts("Billygoat eats your offering...");
    if (*(_BYTE *)buf == '@') {
        puts("\nThe Ancient Goat Master appears before you and blesses you with a hint.\n"\
             "(unless you're viewing strings, then this is a lie.)\n");
        totally_not_a_hint_printer();
        unlink(file);
        unlink(binName);
        free(buf);
        print_outro();
        exit(0);
    }
    if (v4 <= 0xF) {
        puts("\nBillygoat doesn't seem to like what you fed him! *RUGRUUAARRGGGG*");
        vomit(file);
        unlink(binName);
        free(buf);
        print_outro();
        exit(1);
    }
    unlink(file);
    return buf;
}
Pretty simple.

Only 0x10 bytes are read from the "chow.down" file, that I must create.

If the first byte is '@', I'll get a hint.
Alright then, I'll play by the rules and get the hint:


Nice.

Since I already know there's 0x10 bytes of data, that is split in four parts of four bytes each, that are processed by the functions fill_rumen(), fill_reticulum(), fill_omasum() and fill_abomasum(), plus using the last hint, I can map the food types against the functions:
StomachFoodProcedure
RumenOrganicfill_rumen()
ReticulumInorganicfill_reticulum()
OmasumMagical objectsfill_omasum()
AbomasumSources of infinite energyfill_abomasum()

Starting with fill_rumen():
fill_rumen(), IDA decompilesigned int __cdecl fill_rumen(int a1) {
    signed int result; // eax@2

    if ( *(_BYTE *)a1 == ',' ) {
        if ( *(_BYTE *)(a1 + 1) == ';' ) {
            if ( *(_BYTE *)(a1 + 2) == '.' ) {
                if ( *(_BYTE *)(a1 + 3) == 'u' ) {
                    *(_BYTE *)a1 += 0x40;
                    *(_BYTE *)(a1 + 1) -= 0xA;
                    *(_BYTE *)(a1 + 2) += 0x48;
                    *(_BYTE *)(a1 + 3) -= 7;
                    result = 1;
                } else {
                    result = 0;
                }
            } else {
                result = 0;
            }
        } else {
            result = 0;
        }
    } else {
        result = 0;
    }
    return result;
}

And that reviews the order for the first four bytes in the "chow.down" to ",;.u".
This function also modifies these bytes by adding and subtracting values, to construct the flag I'm looking for.

The other three procedures looks pretty much the same, so I got the orders from them and constructed the key ",;.uP@h_|pnWHBcr".

Trying it was a success:

There was a small detail in the last fill_abomasum() procedure, where the first byte is checked after first multiplying it by 2.

Finally, the flag value is constructed, by following these rules:
offsetfile valuemodifierresult
0x000x2C ','+ 0x400x6C 'l'
0x010x3B ';'- 0x0A0x31 '1'
0x020x2E '.'+ 0x480x76 'v'
0x030x75 'u'- 0x070x6E 'n'
0x040x50 'P'+ 0x240x74 't'
0x050x40 '@'+ 0x280x68 'h'
0x060x68 'h'- 0x340x34 '4'
0x070x5F '_'+ 0x150x74 't'
0x080x7C '|'- 0x150x67 'g'
0x090x70 'p'- 0x010x6F 'o'
0x0A0x6E 'n'- 0x0D0x61 'a'
0x0B0x57 'W'+ 0x1D0x74 't'
0x0C0x48 'H'+ 0x240x6C 'l'
0x0D0x42 'B'- 0x110x31 '1'
0x0E0x63 'c'+ 0x030x66 'f'
0x0F0x72 'r'- 0x3F0x33 '3'

And the reverse engineering challenge is completed!



Multi-Architecture Shellcode Challenge


We had a magic goat in the previous challenge, and now we have unicorns.
The story is:
Unicorns UndercoverYou arrive at the meeting location. It’s dark. The handoff is scheduled to take place at 10 p.m. The several unicorns arrive, but you don’t see the flag. They approach you and the first one says, “push eax, call joke.” The second says, “jalr blankstare.” They stare at each other. They stare at you. They aren’t speaking the same language.

Connect via nc 40.84.31.45 4567 and figure out how to talk to the unicorns and reveal the flag.

Shellcode constructing is usually fun. The authors provided us with a test server written in Python, that we can run locally during the testing:
unicorn_server.py#!/usr/bin/env python

from __future__ import print_function
from unicorn import * # Install unicorn from source https://github.com/unicorn-engine/unicorn
from unicorn.unicorn_const import *
from unicorn.x86_const import *
from unicorn.arm_const import *
from unicorn.mips_const import *
from pwn import * # pip install pwntools
import SocketServer
import md5
import binascii
import random

MEM_SIZE = 0x001000

unicorn_types = [
    (UC_ARCH_X86, UC_MODE_32, UC_X86_REG_ECX),
    (UC_ARCH_X86, UC_MODE_64, UC_X86_REG_RDX),
    (UC_ARCH_ARM, UC_MODE_ARM, UC_ARM_REG_R2),
    (UC_ARCH_ARM, UC_MODE_THUMB, UC_ARM_REG_R4),
    (UC_ARCH_MIPS, UC_MODE_MIPS32, UC_MIPS_REG_3),
    (UC_ARCH_MIPS, UC_MODE_MIPS64, UC_MIPS_REG_6),
]

class UnicornsHandler(SocketServer.BaseRequestHandler):
    
    def handle(self):
        try:
            t = remote.fromsocket(self.request)
            t.sendline('What is a group of unicorn called?')
            if (md5.new(t.recvline()).digest() != binascii.unhexlify('fdb2efc47ec9498b576cad5f707e433e')):
                t.sendline('Wrong!')
                t.close()
                return
            t.sendline('How many unicorns do you have?')
            num = int(t.recvline())
            
            shared = '\x00'*MEM_SIZE
            code_addr = random.randint(1,255) * 0x1000
            data_addr = random.randint(1,255) * 0x100000
            
            for x in range(num):
                t.sendline('Running unicorn %d'%x)
                foundunicorn = unicorn_types[x%len(unicorn_types)]
                mu = Uc(foundunicorn[0], foundunicorn[1])
                mu.mem_map(code_addr, MEM_SIZE)
                mu.mem_write(code_addr, t.recvn(MEM_SIZE))
                mu.mem_map(data_addr, MEM_SIZE)
                mu.mem_write(data_addr, shared)
                mu.reg_write(foundunicorn[2], data_addr)
                try:
                    mu.emu_start(code_addr, code_addr+MEM_SIZE, timeout=10, count=4)
                except:
                    t.sendline('Encountered an error running unicorn %d. Exiting.'%x)
                    t.close()
                    return
                shared = str(mu.mem_read(data_addr, MEM_SIZE))
                
            foundunicorn = unicorn_types[num%len(unicorn_types)]
            mu = Uc(foundunicorn[0], foundunicorn[1])
            mu.mem_map(code_addr, MEM_SIZE)
            mu.mem_write(code_addr, shared)
            mu.mem_map(data_addr, MEM_SIZE)
            mu.mem_write(data_addr, flag)
            mu.reg_write(foundunicorn[2], data_addr)
            try:
                mu.emu_start(code_addr, code_addr+MEM_SIZE, timeout=10)
            except:
                t.sendline('Encountered an error running final unicorn. Exiting.')
                t.close()
                return
            shared = str(mu.mem_read(data_addr, MEM_SIZE))
            if (shared.rstrip('\x00')[::-1] == flag):
                t.sendline("flag:{%s}"%flag)
            else:
                t.sendline('The last unicorn did not return the right flag.')
        except:
            print('Unexpected exception caught. Closing this socket.')
        finally:
            t.close()

if __name__ == "__main__":
    flag = open('flag','r').read().rstrip('\n')
    server = SocketServer.TCPServer(('0.0.0.0', 4567), UnicornsHandler, bind_and_activate=False)
    server.allow_reuse_address=True
    server.server_bind()
    server.server_activate()
    server.serve_forever()

It's pretty simple server, that executes a client provided shellcodes under 6 different architectures.

The client-server communication is done using socket on port 4567 and each client response is capped to 0x1000 bytes, that is more than enough for a simple shellcode.
There's a handshake routine consisting of replying with a passphrase and the number of unicorns the client is about to use.

The passphrase is a MD5 hash "fdb2efc47ec9498b576cad5f707e433e", that a simple googling was able to uncover as "blessing".

The requirement is to send N amount of shellcodes that get emulated by Unicorn engine and their job is to write data to a shared memory space.
After the client sent shellcodes are all executed, the shared memory is executed by one last unicorn, that is basically N+1.

So, the client shellcodes must all together construct another shellcode that the last unicorn executes and this last shellcode have to reverse a string coming from a locally stored file named "flag".

To sum it up, here's what I have to do:
- Create a "flag" file holding some string and use it on my test server
- Create a strrev() implementation shellcode
- Create a client that can send valid assembly opcode that stores the final shellcode to a shared memory location (pointed by a different register for each architecture)
- Cross fingers and get the flag

Sounds easy. However, there's one detail. Each client sent shellcode is capped to four instructions only, while the rest will be ignored.
At first, my master plan was to write my strrev() implementation in Intel x64 assembly, populate that to the shared block by a single Intel x86 client sent shellcode, grab the flag and victoriously drink my beer afterwards.
Having this cap of course was a bummer that only delays my beer of victory ritual.

So, what should I do now? First thing is to decide in which architecture I would like to write my strrev() implementation under.
I'm a simple man (a really simple one, actually). I like my Intel assembler, so I picked x86 as strrev() target, and I wrote the final shellcode:
strrev() implementation shellcode, IDA disassemblyseg000:00000000                 jmp     short loc_32
seg000:00000002 aHelloBattelleI db 'Hello Battelle! I',27h,'m here for the juicy flag. ;)',0
seg000:00000032 loc_32:
seg000:00000032                 mov     eax, ecx
seg000:00000034                 xor     ecx, ecx
seg000:00000036                 jmp     short loc_39
seg000:00000038 loc_38:
seg000:00000038                 inc     ecx
seg000:00000039 loc_39:
seg000:00000039                 cmp     byte ptr [eax+ecx], 0
seg000:0000003D                 jnz     short loc_38
seg000:0000003F loc_3F:
seg000:0000003F                 xor     edx, edx
seg000:00000041                 dec     ecx
seg000:00000042 loc_42:
seg000:00000042                 mov     bl, [eax+edx]
seg000:00000045                 xchg    bl, [eax+edx+1]
seg000:00000049                 mov     [eax+edx], bl
seg000:0000004C                 inc     edx
seg000:0000004D                 cmp     edx, ecx
seg000:0000004F                 jl      short loc_42
seg000:00000051                 cmp     ecx, 0
seg000:00000054                 jge     short loc_3F
Of course, like the dog that piss on your car tyres to mark his territory, I had to leave a small message to Battelle, just in case they log the shellcodes users send to their live server.

I now have the final shellcode and its opcode, but to be able to execute it using Ix86 I have to time the client shellcodes that will write it.
That's not a big deal, because I can cheat a bit by sending shellcode chunks to be executed by Intel's architectures and feed NOPs to ARM and MIPS one.

In the end, I used Ix86, Ix64 and both ARM architectures to send my shellcode writer, while ignoring the MIPS.

Called my client "horse.py", because who other than a common horse can crash at a unicorn party?
horse.pyimport socket
import sys
import binascii

HOST, PORT = "40.84.31.45", 4567

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# this is the final shellcode, that the last unicorn executes
shellcode =  b"\xEB\x30\x48\x65\x6C\x6C\x6F\x20\x42\x61\x74\x74\x65\x6C\x6C\x65" # Intel x86
shellcode += b"\x21\x20\x49\x27\x6D\x20\x68\x65\x72\x65\x20\x66\x6F\x72\x20\x74" # Intel x64
shellcode += b"\x68\x65\x20\x6A\x75\x69\x63\x79\x20\x66\x6C\x61\x67\x2E\x20\x3B" # ARM & ARM THUMB
shellcode += b""                                                                 # MIPS32 & MIPS64
shellcode += b"\x29\x00\x8B\xC1\x33\xC9\xEB\x01\x41\x80\x3C\x08\x00\x75\xF9\x33" # Intel x86
shellcode += b"\xD2\x49\x8A\x1C\x10\x86\x5C\x10\x01\x88\x1C\x10\x42\x3B\xD1\x7C" # Intel x64
shellcode += b"\xF1\x83\xF9\x00\x7D\xE9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # ARM & ARM THUMB
shellcode += b""                                                                 # MIPS32 & MIPS64

unicorn_architectures = [
    0x10, 0x10, # Intel x86, Intel x64
    0x08, 0x08, # ARM, ARM THUMB
    0x00, 0x00, # MIPS x86, MIPS x64
]
num_architectures = len(unicorn_architectures)

# calculate the number of needed unicorns to send the whole shellcode
shellcode_length = 0
number_of_unicorns = 0
while True:
    shellcode_length += unicorn_architectures[number_of_unicorns % num_architectures]
    if shellcode_length > len(shellcode):
        break
    number_of_unicorns += 1

# connect to server
try:
    # open connection
    sock.connect((HOST, PORT))

    # server handshake
    received = sock.recv(1024)
    print("Serv:> %s"%received.rstrip(b"\n").decode('ascii'))
    sock.sendall(b"blessing\n")

    # init unicorns
    received = sock.recv(1024)
    print("Serv:> %s"%received.rstrip(b"\n").decode('ascii'))
    sock.sendall(b"%d\n"%number_of_unicorns)
    received = sock.recv(1024)

    offset = 0
    sc_offset = 0
    for i_unicorn in range(0, number_of_unicorns):

        unicorn_arch = i_unicorn % num_architectures

        packet = b""
        if unicorn_arch == 0:
            packet += b"\xC7\x81"+bytearray.fromhex("%08X"%(offset+0x00))[::-1]+shellcode[offset+0x00:offset+0x00+4]
            packet += b"\xC7\x81"+bytearray.fromhex("%08X"%(offset+0x04))[::-1]+shellcode[offset+0x04:offset+0x04+4]
            packet += b"\xC7\x81"+bytearray.fromhex("%08X"%(offset+0x08))[::-1]+shellcode[offset+0x08:offset+0x08+4]
            packet += b"\xC7\x81"+bytearray.fromhex("%08X"%(offset+0x0C))[::-1]+shellcode[offset+0x0C:offset+0x0C+4]
        elif unicorn_arch == 1:
            packet += b"\xC7\x82"+bytearray.fromhex("%08X"%(offset+0x00))[::-1]+shellcode[offset+0x00:offset+0x00+4]
            packet += b"\xC7\x82"+bytearray.fromhex("%08X"%(offset+0x04))[::-1]+shellcode[offset+0x04:offset+0x04+4]
            packet += b"\xC7\x82"+bytearray.fromhex("%08X"%(offset+0x08))[::-1]+shellcode[offset+0x08:offset+0x08+4]
            packet += b"\xC7\x82"+bytearray.fromhex("%08X"%(offset+0x0C))[::-1]+shellcode[offset+0x0C:offset+0x0C+4]
        elif unicorn_arch == 2:
            packet += b"\x08\x00\x9F\xE5"+bytearray.fromhex("%04X"%offset)[::-1]+b"\x82\xE5"
            packet += b"\x04\x00\x9F\xE5"+bytearray.fromhex("%04X"%(offset+4))[::-1]+b"\x82\xE5"
            packet += shellcode[offset:offset+4]
            packet += shellcode[offset+0x04:offset+0x04+4]
        elif unicorn_arch == 3:
            packet += b"\x08\x00\x9F\xE5"+bytearray.fromhex("%04X"%offset)[::-1]+b"\x84\xE5"
            packet += b"\x04\x00\x9F\xE5"+bytearray.fromhex("%04X"%(offset+4))[::-1]+b"\x84\xE5"
            packet += shellcode[offset:offset+4]
            packet += shellcode[offset+0x04:offset+0x04+4]
        elif unicorn_arch == 4:
            pass
        elif unicorn_arch == 5:
            pass

        offset += unicorn_architectures[unicorn_arch]

        # pad the packet to 0x1000 bytes
        packet = packet.ljust(0x1000, b'\x00')

        #send packet
        sock.sendall(packet)
        received = sock.recv(1024)
        print("Serv:> %s"%received.rstrip(b"\n").decode('ascii'))

finally:
    sock.close()

Aaaaaand sacked the flag.

It's ugly, it's shitty and it works, so let me enjoy my beer of shame now.



Forensics and Crypto Challenge


This is the last challenge and it's about cryptography... well, sort of.

Here's the story:
Dragons and DwarvesA wise dragon decided that dwarves were too easily stealing his treasure while he slept. To thwart these villains he has placed his prized possessions inside a magic portal that transmutes the valuables into worthless junk unless one knew the magic pass phrase. The dragon is so confident of his new scheme that he taunts dwarves daring them to try to steal his treasure.

The clever dwarves figured out that there were two obstacles the dragon used to prevent access to his treasure. The first one was easily determined because every time the dwarves tried to throw the worthless junk into the magic portal it spit them back out with a flash of a lightning bolt. The dwarves, being masters of ores, gems, and precious things figured out how to fix the junk. Once the magic portal accepted the junk they then realized there was a fundamental flaw in the ancient magic portal the dragon used and leveraged knowledge and wisdom of their great oracle Goggle to crack the pass phrase and recover the treasure.

We humans want our share of the dragon’s loot, but since relations between dwarves and humans is poor, they are unwilling to share any additional information. You must figure out how the dwarves were able to make use of the magic portal and to find the tool they used to access and replace the dragon’s valuables with real junk.

We obtained a copy of the dragon’s message to the dwarves and have included it in the provided bundle.

Prove that you stole the dragon’s valuables by swapping them with some junk of your own. When done correctly, the dragon will wake up to inspect his valuables using his magic pass phrase (aka password) and find in its place junk (or a taunting message of your own).

The user is provided with a set of three files:
file namefile sizefile CRC32
junk786 b8EA139F7
Notice.txt419 b490E3DA4
Readme.txt1,66 KB98AA0765

The Readme.txt file is just a copy of the story I got from the site, so I can discard that.

The Notice.txt is a copy of the dragon's message, addressed to the crafty dwarves:
Notice.txtTo my next Dwarf victim:

I knew you would be back and my hunch regarding your greed was correct. As you look around you here all you’ll find is junk. My magic portal is protected by a secret password you’ll never discover and my metallurgical skills are unmatched even by you Dwarves!

You might as well give up now before I catch whiff of you and wake up for a Dwarf-kabob.

Sincerely yours,
Bronze Dragon

And finally junk, that is a zip Archive:


The encrypted ZIP file seems interesting, and it was pretty obvious I'll have to figure a way to pull out its password.
Native ZIP encryption is notorious of its flaws and the known plaintext attack is already armed by quite a few commercial application.
I'm a cheap ass, so my weapon of choice is the a bit outdated freeware PkCrack

The way PkCrack works is to apply Eli Biham and Paul Kocher's algorithm.

The command line interface is pretty simple and it's well explained in PkCrack's README.html file.
Basically I need a encrypted zip file (that I want to crack) and a zipped unencrypted copy of a file, contained by the encrypted zip.

I have the encrypted zip and I have a copy of the dwarf's message so I have everything I need to complete that task.
So, i did everything according to PkCrack's documentation like this:

Well, that sucks.

I wondered what went wrong quite some time, until I finally noticed the obvious.
The encrypted Dwarf_Message has a CRC32 of 6800CF05, but the Notice.txt has a CRC32 of 490E3DA4!
Their uncompressed sizes also didn't match, because the encrypted one was 413 bytes, while the unencrypted was 419 bytes long.

I finally figured out what the problem is, when I first loaded Notice.txt in HxD and second, noticed the encrypted junk uses Unix OS format:
Notice.txt, HxDOffset      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

00000000    54 6F 20 6D 79 20 6E 65 78 74 20 44 77 61 72 66    To my next Dwarf
00000010    20 76 69 63 74 69 6D 3A 0D 0A 0D 0A 49 20 6B 6E     victim:....I kn
00000020    65 77 20 79 6F 75 20 77 6F 75 6C 64 20 62 65 20    ew you would be 
00000030    62 61 63 6B 20 61 6E 64 20 6D 79 20 68 75 6E 63    back and my hunc
00000040    68 20 72 65 67 61 72 64 69 6E 67 20 79 6F 75 72    h regarding your
00000050    20 67 72 65 65 64 20 77 61 73 20 63 6F 72 72 65     greed was corre
00000060    63 74 2E 20 20 41 73 20 79 6F 75 20 6C 6F 6F 6B    ct.  As you look
00000070    20 61 72 6F 75 6E 64 20 79 6F 75 20 68 65 72 65     around you here
00000080    20 61 6C 6C 20 79 6F 75 E2 80 99 6C 6C 20 66 69     all you’ll fi
00000090    6E 64 20 69 73 20 6A 75 6E 6B 2E 20 4D 79 20 6D    nd is junk. My m
000000A0    61 67 69 63 20 70 6F 72 74 61 6C 20 69 73 20 70    agic portal is p
000000B0    72 6F 74 65 63 74 65 64 20 62 79 20 61 20 73 65    rotected by a se
000000C0    63 72 65 74 20 70 61 73 73 77 6F 72 64 20 79 6F    cret password yo
000000D0    75 E2 80 99 6C 6C 20 6E 65 76 65 72 20 64 69 73    u’ll never dis
000000E0    63 6F 76 65 72 20 61 6E 64 20 6D 79 20 6D 65 74    cover and my met
000000F0    61 6C 6C 75 72 67 69 63 61 6C 20 73 6B 69 6C 6C    allurgical skill
00000100    73 20 61 72 65 20 75 6E 6D 61 74 63 68 65 64 20    s are unmatched 
00000110    65 76 65 6E 20 62 79 20 79 6F 75 20 44 77 61 72    even by you Dwar
00000120    76 65 73 21 0D 0A 0D 0A 59 6F 75 20 6D 69 67 68    ves!....You migh
00000130    74 20 61 73 20 77 65 6C 6C 20 67 69 76 65 20 75    t as well give u
00000140    70 20 6E 6F 77 20 62 65 66 6F 72 65 20 49 20 63    p now before I c
00000150    61 74 63 68 20 77 68 69 66 66 20 6F 66 20 79 6F    atch whiff of yo
00000160    75 20 61 6E 64 20 77 61 6B 65 20 75 70 20 66 6F    u and wake up fo
00000170    72 20 61 20 44 77 61 72 66 2D 6B 61 62 6F 62 2E    r a Dwarf-kabob.
00000180    0D 0A 0D 0A 53 69 6E 63 65 72 65 6C 79 20 79 6F    ....Sincerely yo
00000190    75 72 73 2C 0D 0A 42 72 6F 6E 7A 65 20 44 72 61    urs,..Bronze Dra
000001A0    67 6F 6E                                           gon

A-ha! The Notice.txt uses Windows's "\r\n" convention for a new line, while the one in the archive, created in Linux presumably, uses the Unix "\n" one.

I've removed the 0x0D ("\r") bytes from Notice.txt but now I'm one byte short, so the CRC32 checksums again didn't match.

It was a total shoot in the dark, but eventually I added a 0x0A ("\n") in the end of the text and surprisingly the checksum was now matching the one in the encrypted junk!

Having the exact copy of Dwarf_Message I run PkCrack one more time, this time using the tweaked Notice.txt:

Alright! I finally got some keys.

So, the same day I had to do some stuff around and just left PkCrack running for a few hours. When I finally came back, it was still running so obviously, cracking the password wasn't the right thing to do.
But I have keys, so I used them with "zipdecrypt.exe":


And that gave me the decrypted junk_d.zip that can I now extract.
The file of course hold the Dwarf_Message, but it also has the dragon's stash file - Stash_BronzeDragon:
Stash_BronzeDragon5000 gold coins
2000kg bronze
10000 silver coins
300 rubies
20 diamonds
Bronze Dragon's secret message: 32FJO!)(G209rdJ(DJ!jF3(!^%
Nice, I got what I was looking for.

Now, I expected to get a "flag{XXXX}" string, and I'm almost sure that the secret message (that is also the encrypted ZIP's password, btw) is cyphered and I'm expected to crack the code.
I thought it's a Ceaser/ROT cipher, but nothing came out of this. It's also not XOR, because that would likely produce non-printable characters.

I tried alphabet shuffle, and knowing the expected format from the previous two challenges, I could map the string like this:
flag32FJO!)(G209rdJ(DJ!jF3(!^%
flag{    l    g  g  af   }
Since the '!' is the first character after the opening bracket '{', I'd expect it to be a start of a new word with uppercase letter.

That gives me possible three words:
Possible wordsword 1: "****l****g**g"
word 2: "**af*"
word 3: "**"

The second word could be "Craft", but that makes the third two characters long one "C*", and that seems unlikely.
Another guess could be "Draft", thus word 3 would be "D*", as possible "Do", but then what is the first word? And is it a complete word or two or more (more likely)?

Since English is not being my native language (that you can clearly guess by yourself, if you read my shit), I wasn't able to guess a matching words.
Problem is, I'm not even sure if that's part of the challenge or I'm after a red herring.

That being said, that's everything i got for these three challenges.

Comments

* You have an opinion? Let us all hear it!

XpoZed 15 Nov, 2018 09:50
which one?
Guest 14 Nov, 2018 16:28
how did you execute the file? I tried to use cmd (dos) but did not work.
© nullsecurity.org 2011-2024 | legal | terms & rules | contacts