Wednesday, 9 November 2016

Flare-on Challenge 2016 Write-up


The Flare-on challenge is an annual CTF style challenge with a focus on reverse engineering. Official solutions have already been published, besides that there are other writeups available too, hence I will just skim through the parts.

Challenge #1

The first was simple. This is base64 encoding with a custom charset. This online tool does the job.
Flag: sh00ting_phish_in_a_barrel@flare-on.com

Fig 1: Challenge 1

Challenge #2 - DudeLocker

This is a file encrypting ransomware. An encrypted file (BusinessPapers.doc) is provided, the task is to decrypt it. As the encryption key is hardcoded in the binary, I simply changed the CryptEncrypt call to CryptDecrypt by modifying the IAT. This decrypts the file giving the following image.
flag: cl0se_t3h_f1le_0n_th1s_One@flare-on.com 

Fig 2: Challenge 2

Challenge #3 - Unknown

The challenge is named unknown, we need to find it proper name. This can be found from the embedded pdb file path debug information. The binary implements a custom md5 hash algorithm which is used to calculate a table from the command line argument and the executable path. Since we already know the proper path, the command line argument can simply be brute forced giving us the flag Ohs0pec1alpwd@flare-on.com 
sss = map(ord, list('__FLARE On!'))

target = [0xEE613E2F, 0xDE79EB45, 0xAF1B2F3D, 0x8747BBD7, 
0x739AC49C, 0xC9A4F5AE, 0x4632C5C1, 0xA0029B24, 0xD6165059, 
0xA6B79451, 0xE79D23BA, 0x8AAE92CE, 0x85991A18, 0xFEE05899, 
0x430C7994, 0x1AB9F36F, 0x70C42481, 0x05BD27CF, 0xC4FF6E6F, 
0x5A77847C, 0xDD9277B3, 0x25843CFF, 0x5FDCA944, 0x8EE42896, 
0x2AE961C7, 0xA77731DA]

def charsprod(li):
 prod = 0
 for i in xrange(len(li)):
  prod = ((prod*37)&0xFFFFFFFF) + li[i] 
 return prod

email = ''

for i in xrange(26):
 sss[1] = ord('`') + i
 for j in xrange(1, 256):
  sss[0] = j
  if charsprod(sss) == target[i]:
   email += chr(j)
   break

print email


Challenge #4 - flareon2016challenge

A dll is provided which exports 51 functions by ordinals. Among them functions 1 to 48 & 51 changes the global state in someway or the other and must be called first. Ordinal 50 makes call to Beep and also tries to decrypt some piece of data. The task is to calls the functions in proper order so that the decryption may succeed. Additionally these  functions return an integer byte value indicating the ordinal of the next function that must be called. We can use the return values to build up the call chain order using the following script.
import ctypes

dll = ctypes.windll.LoadLibrary('flareon2016challenge')
call_chain = {}

for i in range(1, 49):
 retval = dll[i]()
 call_chain[i] = retval

print sorted(call_chain.values())
# missing is ordinal 30, should be called first

The call chain table thus found has no entry for ordinal 30, hence that is the function to be called first. After calling the functions in the correct order, an embedded executable is decrypted which just makes a series of calls to the Beep function. Setting a logging breakpoint on Beep allows us to recover the parameters passed. Calling export 50 using the same parameters gives us the flag: f0ll0w_t3h_3xp0rts@flare-on.com 
import sys
import ctypes
import os

params =[(440, 500), (440, 500), (440, 500), (349, 350) , (523, 150), (440, 500), (349, 350), (523, 150), (440, 1000), (659, 500), 
(659, 500), (659, 500), (698, 350), (523, 150), (415, 500), (349, 350), (523, 150), (440 , 1000)]

dll = ctypes.cdll.LoadLibrary('flareon2016challenge')

# call first function
retval = dll[30]()

# do not call last function
while retval != 51:
 retval = dll[retval]()

# call last func
dll[51]()

for p in params:
 dll[50](p[0], p[1])

Challenge #5 - smokestack

The provided executable is a stack based virtual machine. It takes in an argument, and prints the flag if it is correct. I reimplemented the vm in python and brute forced the flag A_p0p_pu$H_&_a_Jmp@flare-on.com
instructions = [0, 33, 2, 0, 145, 8, 0, 22, 0, 12, 9, 10, 11, 0, 0,
12, 2, 12, 0, 0, 29, 10, 11, 0, 0, 99, 2, 12, 0, 0,
24, 6, 0, 84, 8, 0, 51, 0, 41, 9, 10, 11, 0, 0, 44,
2, 12, 0, 0, 61, 10, 0, 14, 1, 11, 0, 0, 89, 2, 12,
0, 11, 0, 0, 0, 12, 1, 0, 9, 12, 0, 11, 1, 0, 2, 2,
12, 1, 11, 0, 0, 1, 3, 12, 0, 11, 0, 0, 0, 8, 0, 71,
0, 96, 9, 10, 12, 0, 11, 1, 3, 0, 93, 8, 0, 124, 0,
110, 9, 10, 11, 0, 0, 7, 3, 12, 0, 0, 91, 12, 1, 0,
135, 10, 0, 54, 12, 1, 11, 0, 11, 1, 2, 12, 1, 11, 1,
0, 88, 2, 6, 0, 249, 8, 0, 160, 0, 150, 9, 10, 11, 0,
0, 77, 6, 12, 0, 0, 174, 10, 0, 803, 0, 299, 3, 12,
1, 11, 0, 11, 1, 2, 12, 1, 12, 1, 11, 1, 11, 1, 0, 1,
3, 12, 1, 0, 3, 2, 11, 1, 0, 0, 8, 0, 178, 0, 199, 9,
10, 7, 0, 65143, 8, 0, 216, 0, 209, 9, 10, 11, 0, 0,
88, 2, 12, 0, 0, 3, 4, 0, 140, 2, 0, 24724, 8, 0, 238,
0, 231, 9, 10, 11, 0, 0, 231, 2, 12, 0, 11, 1, 2, 0,
12, 6, 0, 116, 8, 0, 263, 0, 253, 9, 10, 11, 0, 0, 9,
3, 12, 0, 0, 285, 10, 0, 10, 12, 1, 11, 1, 0, 1, 3,
12, 1, 11, 1, 0, 0, 8, 0, 267, 0, 285, 9, 10, 0, 6,
5, 0, 7616, 8, 0, 307, 0, 297, 9, 10, 11, 0, 0, 113,
2, 12, 0, 0, 317, 10, 11, 0, 0, 119, 2, 12, 0, 0, 317,
10, 0, 22, 2, 0, 14, 3, 0, 97, 8, 0, 339, 0, 332, 9,
10, 11, 0, 0, 44, 3, 12, 0, 12, 1, 11, 1, 0, 8492, 11,
1, 0, 1, 3, 12, 1, 0, 7, 3, 11, 1, 0, 0, 8, 0, 345,
0, 366, 9, 10, 0, 458, 6, 0, 8181, 8, 0, 385, 0, 378,
9, 10, 11, 0, 0, 18, 2, 12, 0, 13]

stack = []
sp = ctx1 = ctx2 = 0

def push(value):
 global sp
 sp += 1
 stack[sp] = value & 0xFFFF

def pop():
 global sp
 sp -= 1
 return stack[sp + 1]

def init_vm():
 global stack, sp, ctx1, ctx2
 stack = [ord(ch) for ch in 'kYwxCbJoLp']
 stack += [0,0,0,0]
 sp = 9
 ctx1 = ctx2 = 0

def exec_vm():
 global ctx1, ctx2
 ip = 0

 while ip < 386:
  #print 'loc_%d' %ip
  opcode = instructions[ip]
  
  if opcode == 0: # ins_load
   operand = instructions[ip + 1]
   push(operand)
   ip += 2

  elif opcode == 1: # ins_dec_sp
   pop()
   ip += 1

  elif opcode == 2: # ins_add
   v0 = pop()
   v1 = pop()
   push(v0 + v1)
   ip += 1

  elif opcode == 3: # ins_sub
   v0 = pop()
   v1 = pop()
   push(v1 - v0)
   ip += 1  

  elif opcode == 4: # ins_rotr
   v0 = pop()
   v1 = pop();
   push((v1 << (16 - v0)) | (v1 >> v0))
   ip += 1

  elif opcode == 5: # ins_rotl
   v0 = pop()
   v1 = pop();
   push((v1 >> (16 - v0)) | (v1 << v0))
   ip += 1

  elif opcode == 6: # ins_xor
   v0 = pop()
   v1 = pop();
   push(v1 ^ v0)
   ip += 1

  elif opcode == 7: # ins_not
   v0 = pop()
   push(~v0)
   ip += 1  

  elif opcode == 8: # ins_cmp
   v0 = pop()
   if v0 == pop():
    push(1)
   else:
    push(0)
   ip += 1    

  elif opcode == 9: # ins_cload
   v1 = pop()
   v0 = pop()
   if 1 == pop():
    push(v1)
   else:
    push(v0)
   ip += 1 

  elif opcode == 10: # ins_jmp
   ip = pop()

  elif opcode == 11: # ins_load_ctx
   operand = instructions[ip + 1]
   if operand == 0:
    push(ctx1)
   elif operand == 1:
    push(ctx2)
   ip += 2

  elif opcode == 12: # ins_set_ctx
   operand = instructions[ip + 1]
   v1 = pop()
   if operand == 0:
    ctx1 = v1
   elif operand == 1:
    ctx2 = v1
   ip += 2 

  elif opcode == 13: # ins_inc_ip
   ip += 1    


def main():
 global stack
 start = ord('A') - 1
 end = ord('z') + 1

 for pos in range(10):
  ret_vals = [None for i in xrange(start, end)]
  for i in range(start, end):
   init_vm()
   stack[pos] = i
   exec_vm()
   ret_vals[i-start] = tuple([i, ctx1])

  for e in ret_vals:
   if e[1] != ret_vals[0][1]:
    print chr(e[0]),
    break 


if __name__ == '__main__':
 main()

Challenge #6 - khaki

The challenge presents a piece of obfuscated python bytecode and by far this is best challenge. The file provided is a py2exe'd executable which can be easily unpacked to get the embedded pyc file. This pyc is obfuscated and cannot be easily decompiled. The reason for this is it has been sprinkled with NOPs , two POP_TOP, two ROT_TWO, and three ROT_THREE instructions. I developed a peephole optimizer to remove these instructions and make the file decompile-able using the bytecode-graph library developed by fireeye.
import bytecode_graph
import marshal
import opcode

def remove_nops(bcg, nodes):
 for i in xrange(len(nodes) - 1):
  node = nodes[i]
  if node.opcode == opcode.opmap['NOP']:
   bcg.delete_node(node)
   return True
 return False


def peephole_load_const(bcg, nodes):
 for i in xrange(len(nodes) - 1):
  node = nodes[i]
  # Peephole optimization (remove sequence of load and pop instructions)  
  if node.opcode == opcode.opmap['LOAD_CONST'] and nodes[i+1].opcode == opcode.opmap['POP_TOP']:
   bcg.delete_node(node)
   bcg.delete_node(nodes[i+1])
   return True
 return False   

def peephole_rot_two(bcg, nodes):
 for i in xrange(len(nodes) - 1):
  node = nodes[i]

  # Peephole optimization (remove two consecutive ROT_TWO)  
  if node.opcode == opcode.opmap['ROT_TWO'] and nodes[i+1].opcode == opcode.opmap['ROT_TWO']:
   bcg.delete_node(node)
   bcg.delete_node(nodes[i+1])
   return True
 return False

def peephole_rot_three(bcg, nodes):
 for i in xrange(len(nodes) - 2):
  node = nodes[i]

  # Peephole optimization (remove two consecutive ROT_THREE)  
  if node.opcode == opcode.opmap['ROT_THREE'] and nodes[i+1].opcode == opcode.opmap['ROT_THREE'] and nodes[i+2].opcode == opcode.opmap['ROT_THREE']:
   bcg.delete_node(node)
   bcg.delete_node(nodes[i+1]) 
   bcg.delete_node(nodes[i+2]) 
   return True

 return False  


def main():
 pyc_file = open('poc.pyc', 'rb').read()
 pyc = marshal.loads(pyc_file[8:])
 bcg = bytecode_graph.BytecodeGraph(pyc)

 nodes = [x for x in bcg.nodes()]

 while remove_nops(bcg, nodes) == True:
  nodes = [x for x in bcg.nodes()]

 while peephole_load_const(bcg, nodes) == True:
  nodes = [x for x in bcg.nodes()]
 
 while peephole_rot_two(bcg, nodes) == True:
  nodes = [x for x in bcg.nodes()]


 while peephole_rot_three(bcg, nodes) == True:
  nodes = [x for x in bcg.nodes()]

 deobf_code = bcg.get_code()
 f = open('poc-deobf.pyc', 'wb')
 f.write('\x03\xf3\x0d\x0a\0\0\0\0')
 marshal.dump(deobf_code, f)
 f.close()


if __name__ == '__main__':
 main()

Using this we can obtain the following deobfuscated code.
# Embedded file name: poc.py
import sys, random
__version__ = 'Flare-On ultra python obfuscater 2000'
target = random.randint(1, 101)
count = 1
error_input = ''
while True:
    print '(Guesses: %d) Pick a number between 1 and 100:' % count,
    input_num = sys.stdin.readline()
    try:
        input_num = int(input_num, 0)
    except:
        error_input = input_num
        print 'Invalid input: %s' % error_input
        continue

    if target == input_num:
        break
    if input_num < target:
        print 'Too low, try again'
    else:
        print 'Too high, try again'
    count += 1

if target == input_num:
    win_msg = 'Wahoo, you guessed it with %d guesses\n' % count
    sys.stdout.write(win_msg)
if count == 1:
    print 'Status: super guesser %d' % count
    #sys.exit(1)
if count > 25:
    print 'Status: took too long %d' % count
    sys.exit(1)
else:
    print 'Status: %d guesses' % count

if error_input != '':
    tmp = ''.join((chr(ord(x) ^ 66) for x in error_input)).encode('hex')
    if tmp != '312a232f272e27313162322e372548':
        sys.exit(0)
    stuffs = [67,139,119,165,232,86,207,61,
    import hashlib
    stuffer = hashlib.md5(win_msg + tmp).digest()
    for x in range(len(stuffs)):
        print chr(stuffs[x] ^ ord(stuffer[x % len(stuffer)])),

    print

Another python script to brute force the flag.
import hashlib

for i in xrange(100):
 win_msg = 'Wahoo, you guessed it with %d guesses\n' %i
 tmp = '312a232f272e27313162322e372548'

 stuffs = [67,139,119,165,232,86,207,61,79,67,45,58,230,190,181,74,65,148,71,243,246,67,142,60,61,92,58,115,240,226,171]
 stuffer = hashlib.md5(win_msg + tmp).digest()

 s = ''
 for x in range(len(stuffs)):
  s += chr(stuffs[x] ^ ord(stuffer[x % len(stuffer)]))
 if s.endswith('.com'):
  print s
  break
Flag 1mp0rt3d_pygu3ss3r@flare-on.com

Challenge #7 - hashes

The challenge is a x86 ELF (linux binary) developed in the go language. However unlike the standard go compiler gc, this has been compiled with gccgo and requires libgo.so.7 in order to be able to run. Now my local linux vm is ubuntu 14.04 and libgo7 is only available for ubuntu 16.04 and above. However I was not willing to download and install a complete new distro just for running this single binary. Hence a workaround was necessary. I powered on cloud9 vm, wgetted the deb directly bypassing the package manager. Although dpkg could not install the package, I got the much needed file libgo.so.7. Using it I could debug the binary in my local ubuntu 14.04 vm.

Fig 7 - Satisfying the dependencies
With that out of the picture, the objective of the challenge is to crack the SHA1 hash of the flag applied three times recursively. Since we know, that the flag ends in @flare-on.com, all that is required is to bruteforce the first few characters. Taking the good boy message "You have hashed the hashes" as a cue, I quickly brute forced the flag h4sh3d_th3_h4sh3s@flare-on.com

Challenge #8 - chimera

The name of the challenge immediately reminded me of the movie Mission: Impossible II wherein IMF agent Ethan Hunt must track and destroy a biological weapon Chimera along with its anti-dote Bellerophon and prevent it from being misused. While the actual challenge had nothing to do with the movie but certainly it was equally engrossing. Instead of the chimera virus, here we have an PE executable with a the relevant code hidden up the sleeves in the DOS stub. Once this is figured out, all that is left to disassemble the obfuscated 16 bit code to understand its workings. Dosbox along with its debugger proved much helpful in solving this problem. To get the flag I used the following script.
table = [255, 21, 116, 32, 64, 0, 137, 236, 93, 195, 66, 70,
192, 99, 134, 42, 171, 8, 191, 140, 76, 37, 25, 49,
146, 176, 173, 20, 162, 182, 103, 221, 57, 216, 95,
63, 123, 92, 194, 178, 246, 46, 117, 155, 97, 148, 207,
206, 106, 152, 80, 242, 91, 240, 69, 48, 14, 56, 235,
59, 108, 102, 127, 36, 61, 223, 136, 151, 185, 179,
241, 203, 131, 153, 26, 13, 239, 177, 3, 85, 158, 154,
122, 16, 224, 54, 232, 211, 228, 50, 193, 120, 7, 183,
107, 199, 112, 201, 44, 160, 145, 53, 109, 254, 115,
94, 244, 164, 217, 219, 67, 105, 245, 141, 238, 68,
125, 72, 181, 220, 75, 2, 161, 227, 210, 166, 33, 62,
47, 163, 215, 187, 132, 90, 251, 143, 18, 28, 65, 40,
197, 118, 89, 156, 247, 51, 6, 39, 10, 11, 175, 113,
22, 74, 233, 159, 79, 111, 226, 15, 190, 43, 231, 86,
213, 83, 121, 45, 100, 23, 149, 167, 189, 124, 29, 88,
147, 165, 101, 248, 24, 19, 234, 188, 229, 243, 55,
4, 150, 168, 30, 1, 41, 130, 81, 60, 104, 31, 142, 218,
138, 5, 34, 114, 73, 250, 135, 169, 84, 98, 198, 170,
9, 180, 253, 214, 209, 172, 133, 17, 71, 58, 157, 230,
77, 27, 204, 82, 128, 35, 252, 237, 139, 126, 96, 205,
110, 87, 186, 222, 174, 202, 196, 119, 12, 78, 212,
208, 200, 225, 184, 249, 38, 144, 129, 52]

target = [56, 225, 74, 27, 12, 26, 70, 70, 10, 150, 41, 115, 115, 164, 105, 3, 0, 27, 168, 248, 184, 36, 22, 214, 9, 203]

flag = map(ord, list('A'*26))

def rol(n):
 b = (n >> 7) & 1
 n = ((n << 1) | b) & 0xFF
 return n

def ror(n):
 b = n & 1
 n = ((n >> 1) | (b << 7)) & 0xFF
 return n 


def calc():
 for i in xrange(len(flag)-1, -1, -1):
  if i == len(flag) - 1:
   v1 = rol(rol(rol(0x97)))
  else:
   v1 = rol(rol(rol(flag[i+1])))

  v2 = table[v1]
  v2 = table[v2]

  flag[i] ^= v2  

 for i in xrange(len(flag)):
  if i == 0:
   flag[i] ^= 0xC5
  else:
   flag[i] ^= flag[i-1]

 print map(hex, flag)


def reverse():
 for i in xrange(len(target)-1, -1, -1):
  if i == 0:
   target[i] ^= 0xC5
  else:
   target[i] ^= target[i-1]

 for i in xrange(len(target)-1, -1, -1):
  if i == len(target) - 1:
   v1 = rol(rol(rol(0x97)))
  else:
   v1 = rol(rol(rol(save)))

  v2 = table[v1]
  v2 = table[v2]
  save = target[i]
  target[i] ^= v2 

 print ''.join(map(chr, target))


if __name__ == '__main__':
 reverse()
flag retr0_hack1ng@flare-on.com 


Challenge #9 - GUI

The challenge consists of a .net executable with ConfuserEx thrown in for a change. DnSpy along with NoFuserEx is sufficient to extract all the necessary strings for reconstructing back the shared secret.

Share:1-d8effa9e8e19f7a2f17a3b55640b55295b1a327a5d8aebc832eae1a905c48b64
Share:2-f81ae6f5710cb1340f90cd80d9c33107a1469615bf299e6057dea7f4337f67a3
Share:3-523cb5c21996113beae6550ea06f5a71983efcac186e36b23c030c86363ad294
Share:4-04b58fbd216f71a31c9ff79b22f258831e3e12512c2ae7d8287c8fe64aed54cd
Share:5-5888733744329f95467930d20d701781f26b4c3605fe74eefa6ca152b450a5d3
Share:6-a003fcf2955ced997c8741a6473d7e3f3540a8235b5bac16d3913a3892215f0a

Flag Shamir_1s_C0nfused@flare-on.com

Challenge #10 - flava

This was the final challenge, and is composed of many sub challenges. The first part requires to get through three layers of obfuscated javascript with an obfuscated Diffie Hellman (courtesy of Angler EK) for more distress. So unless, one figures out what the heck is with all the obfuscated javascript it is a dead end. Even if one manages to guess that, breaking the Diffie Hellman is more pain. Luckily, Kaspersky researches have already done the hard work before and it requires a bit of Googling to locate the code necessary to break the diffie hellman.
After three layers of javascript there are three more layers of actionscript. While the first layer is straightforward the second and third layers are obfuscated. The challenge in this part is to identify that the RC4 key is reused.  Once we know that, we can simply xor the plain text and ciphertext to get the keystream, and xor the resultant keystream with the second ciphertext to get back the plain text. The third actionscript layer simply prints the flag angl3rcan7ev3nprim3@flare-on.com

Final Words

Overall, the challenges this year were certainly more difficult than those of the preceding year. Some parts required bruteforcing hashes and guesswork which I detest. Another point of notice is that there were no 64 bit binaries. There were also no challenges involving kernel drivers. Finally, I would like to extend my thanks to everyone who helped me through the course of the challenges.

Monday, 7 November 2016

Hack the Vote 2016 CTF - APTeaser writeup


Just for fun I decided to have a go at the Hack the Vote 2016 CTF, particularly the reversing challenges on Windows. There were two of them APTeaser & Trumpervisor. I managed to solve the first. I did try the second but it involved reversing a Win 10 kernel driver implementing a hypervisor using the Intel Virtualization Extensions (VT-x). Anyway, here is a somewhat detailed writeup for the first.

Initial Analysis

The provided file is a pcapng. Opening it in fiddler, reveals an interesting http request for a supposed pdf file on the domain important.documents.trustme, but as indicated from the Content-Type the response is actually an executable.

Serving an executable when all I want is a pdf
Fig 1: Serving an executable when all I want is a pdf
This can be further verified in wireshark as shown in Fig 2.

Cross checking in wireshark
Fig 2: Cross checking in wireshark
We can save the response to a new file for further analysis. As expected the the file has a pdf icon and a dual extension to pass off as an innocuous pdf, waiting to be clicked.

Fig 3: An exe with a pdf icon and dual extensions

Dissecting the executable

The executable in question is a screen grabber. It takes screenshot of the desktop at regular intervals, saves to a jpeg file, "encrypts" the file, and sends it to a remove server. We can decompile it in hex-rays to get the following pseudo code.
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [sp+0h] [bp-2Ch]@1
  int v5; // [sp+10h] [bp-1Ch]@1
  int x; // [sp+14h] [bp-18h]@1
  int y; // [sp+18h] [bp-14h]@1
  int x1; // [sp+1Ch] [bp-10h]@1
  int y1; // [sp+20h] [bp-Ch]@1
  SOCKET sock; // [sp+24h] [bp-8h]@2
  int i; // [sp+28h] [bp-4h]@1

  sub_401BF0(0, 0, 0);
  GdiplusStartup(&v5, &v4, 0);
  x1 = 0;
  y1 = 0;
  x = GetSystemMetrics(SM_CXSCREEN);
  y = GetSystemMetrics(SM_CYSCREEN);
  i = 0;
  do
  {
    sock = create_socket();
    if ( sock == SOCKET_ERROR )
    {
      ++i;
      Sleep(300u);
    }
    else
    {
      i = 0;
      take_screenshot(x1, y1, x - x1, y - y1);
      send_file(sock, "a");
      destroy_socket(sock);
    }
  }
  while ( i < 5 );
  GdiplusShutdown(v5);
  return 0;
}

As shown in the preceding snippet, it takes screencaps at regular intervals, and retrying for 5 times if socket creation fails. To get a better overview of the network traffic, we can analyse it using FakeNet-NG an excellent dynamic network analysis tool from Fireeye.

A quick overview of the network traffic

FakeNet-NG capturing the outbound requests
Fig 4: FakeNet-NG capturing the outbound requests
FakeNet-NG is an immensely helpful tool for dealing with malware/apps that uses the internet for communication. While tools like wireshark can only capture the traffic, fakenet additionally, also has the ability to mimic/emulate/tamper the responses via custom listeners implemented as plugins. On this particular sample, it has redirected an outbound request to 128.213.48.117:27015 to its default tcp listeners. Additionally, the name/pid of the application that initiated the request is also shown. The next point of interest the data that is actually being sent. It starts with the hex bytes 80 F4 FF E0. Now, a jpeg starts with FF D8 FF E0. Similarly the next 4 bytes, 43 4F 4A 46 bear strong resemblance to the true jpg header bytes xx xx 4A 46. From this, it can be easily deduced that a jpeg is being sent after applying some sort of "encryption" on the first two bytes of each dword.

The encryption algorithm

The encrypting functionality is implemented in the function send-file and is the subject of further analysis. The snippet of code that deals with the encryption is as follows.

  completed = 0;
  blocksize = 0;
  while ( completed < filesize )
  {
    seed = get_time(0);
    srand(seed);
    if ( filesize - completed < 2048 )
      blocksize = filesize - completed;
    else
      blocksize = 2048;
    for ( i = 0; i < blocksize / 4; ++i )
    {
      randnum = rand();
      if ( !i )
      {
        xored_val = randnum ^ *(jpeg_buf + completed);
        v4 = std::basic_ostream<char,std::char_traits<char>>::operator<<(std::cerr, randnum, sub_403880, " ", *(jpeg_buf + completed), " ");
        v5 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v4);
        v6 = sub_401040(v5, xored_val);
        v7 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v6, sub_401680, v13, *&v14, v15, v16);
        v9 = sub_401040(v7, v8);
        v11 = std::basic_ostream<char,std::char_traits<char>>::operator<<(v9, v10, *&v14, v15, v16, v17);
        std::basic_ostream<char,std::char_traits<char>>::operator<<(v11);
      }
      *(jpeg_buf + 4 * i + completed) ^= randnum;
    }
    send(sock, jpeg_buf + completed, blocksize, 0);
    completed += blocksize;
    Sleep(Seed * completed % 3600);
  }

The code initializes the random number using the current time as a seed to the function srand. Then it begins to xor the jpeg 4 bytes at a time with the generated random number in blocks of 2048 bytes, i.e it uses a new random number after every 2048 bytes. The range of the function rand lies between 0 and 32767 (0x7FFF). The upper two bytes are always zero. This explains the reason behind the fact that for every 4 bytes of the encrypted data, 2 bytes were always left unencrypted, as was found in fakenet.

Breaking the crypto

To break the crypto, we need to predict the generated random number, which depends on the seed used. The seed in turn is just the current time in epochs. Now each captured packet has a timestamp, we can use that value as a seed to srand to regenerate the exact same sequence of random numbers.

First we need to get the timestamps of the relevant packets. This can be done by setting a filter and exporting the resultant packets to a new pcap file as shown in Fig. 5
Fig 5: Applying a display filter in wireshark
Then a python script to get the timestamps and to carve out the jpeg.
import pyshark
import cStringIO

cap = pyshark.FileCapture('filtered.pcap')
buf = cStringIO.StringIO()
rand_seeds = []

for pkt in cap:
    buf.write(pkt.data.data.decode('hex')) # pkt.layers[3].data
    rand_seeds.append(int(float(pkt.frame_info.time_epoch))) 

open('encrypted.jpg', 'wb').write(buf.getvalue())
print rand_seeds

Once we have the encrypted jpeg and the seed values, we can create the decrypter in C.
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

void main()
{
 int rand_seeds[] = {1460329071, 1460329074, 1460329077, 1460329080, 1460329084, 
  1460329084, 1460329087, 1460329090, 1460329091, 1460329093, 1460329096, 
  1460329100, 1460329102, 1460329105, 1460329109, 1460329111, 1460329112, 
  1460329114, 1460329115, 1460329117, 1460329121, 1460329121, 1460329123, 
  1460329125, 1460329128, 1460329131, 1460329133, 1460329135, 1460329136, 
  1460329140, 1460329141, 1460329142, 1460329145, 1460329148, 1460329150, 
  1460329154, 1460329154, 1460329154, 1460329158, 1460329158, 1460329160, 
  1460329161, 1460329161, 1460329165, 1460329165, 1460329167, 1460329168, 
  1460329170, 1460329171, 1460329172, 1460329173, 1460329174, 1460329175, 
  1460329178, 1460329180, 1460329182, 1460329182, 1460329184, 1460329185, 
  1460329186, 1460329187, 1460329190, 1460329191, 1460329192, 1460329194, 
  1460329197, 1460329197, 1460329198, 1460329199, 1460329200, 1460329202, 
  1460329202, 1460329203, 1460329203, 1460329205, 1460329207, 1460329210, 
  1460329210, 1460329213, 1460329215, 1460329217, 1460329218, 1460329220, 
  1460329220, 1460329222, 1460329225, 1460329227, 1460329230, 1460329232, 
  1460329234};

 FILE *inf = fopen("encrypted.jpg", "rb");
 FILE *outf = fopen("decrypted.jpg", "wb");
 fseek(inf, 0, SEEK_END);
 long size = ftell(inf);
 rewind(inf);

 for (int i = 0; i < 90; i++)
 {
  srand(rand_seeds[i]);
  for (int j = 0; j < (size < 2048?size/4:2048/4); j++)
  {
   unsigned int data;
   fread(&data, 4, 1,  inf);
   data ^= rand();
   fwrite(&data, 4, 1, outf);
  }
  size -= 2048;
 }
 getch();
}

Running this we get the decrypted jpeg as shown in Fig 6. The flag is flag{1_n33d_my_t00Lb4r5}.

Fig 6: The flag