Monday, December 26, 2011

Can you crack it ? A quite detailed solution (Part 2)

This is Part 2/3. You can find the previous parts here :  Part 1

Prerequisites

There are no real requirements other than a little bit of knowledge about how a CPU works. Nevertheless it is good to know about Bitwise operators and Segmented memory to fully understand this part of the challenge.

Writing our virtual machine

On part 1 the code we decoded led us to this URL : www.canyoucrackit.co.uk/15b436de1f9107f3778aad525e5d0b20.js
(I made a copy in case the original link doesn't work anymore 15b436de1f9107f3778aad525e5d0b20.js)
//--------------------------------------------------------------------------------------------------
//
// stage 2 of 3
//
// challenge:
//   reveal the solution within VM.mem
//
// disclaimer:
//   tested in ie 9, firefox 6, chrome 14 and v8 shell (http://code.google.com/apis/v8/build.html),
//   other javascript implementations may or may not work.
//
//--------------------------------------------------------------------------------------------------

var VM = {
  
  cpu: {
    ip: 0x00,
    
    r0: 0x00,
    r1: 0x00,
    r2: 0x00,
    r3: 0x00,
    
    cs: 0x00,
    ds: 0x10,
    
    fl: 0x00,
    
    firmware: [0xd2ab1f05, 0xda13f110]
  },
  
  mem: [
    0x31, 0x04, 0x33, 0xaa, 0x40, 0x02, 0x80, 0x03, 0x52, 0x00, 0x72, 0x01, 0x73, 0x01, 0xb2, 0x50,
    0x30, 0x14, 0xc0, 0x01, 0x80, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    
    0x98, 0xab, 0xd9, 0xa1, 0x9f, 0xa7, 0x83, 0x83, 0xf2, 0xb1, 0x34, 0xb6, 0xe4, 0xb7, 0xca, 0xb8,
    0xc9, 0xb8, 0x0e, 0xbd, 0x7d, 0x0f, 0xc0, 0xf1, 0xd9, 0x03, 0xc5, 0x3a, 0xc6, 0xc7, 0xc8, 0xc9,
    0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
    0xda, 0xdb, 0xa9, 0xcd, 0xdf, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
    0x26, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
    0x7d, 0x1f, 0x15, 0x60, 0x4d, 0x4d, 0x52, 0x7d, 0x0e, 0x27, 0x6d, 0x10, 0x6d, 0x5a, 0x06, 0x56,
    0x47, 0x14, 0x42, 0x0e, 0xb6, 0xb2, 0xb2, 0xe6, 0xeb, 0xb4, 0x83, 0x8e, 0xd7, 0xe5, 0xd4, 0xd9,
    0xc3, 0xf0, 0x80, 0x95, 0xf1, 0x82, 0x82, 0x9a, 0xbd, 0x95, 0xa4, 0x8d, 0x9a, 0x2b, 0x30, 0x69,
    0x4a, 0x69, 0x65, 0x55, 0x1c, 0x7b, 0x69, 0x1c, 0x6e, 0x04, 0x74, 0x35, 0x21, 0x26, 0x2f, 0x60,
    0x03, 0x4e, 0x37, 0x1e, 0x33, 0x54, 0x39, 0xe6, 0xba, 0xb4, 0xa2, 0xad, 0xa4, 0xc5, 0x95, 0xc8,
    0xc1, 0xe4, 0x8a, 0xec, 0xe7, 0x92, 0x8b, 0xe8, 0x81, 0xf0, 0xad, 0x98, 0xa4, 0xd0, 0xc0, 0x8d,
    0xac, 0x22, 0x52, 0x65, 0x7e, 0x27, 0x2b, 0x5a, 0x12, 0x61, 0x0a, 0x01, 0x7a, 0x6b, 0x1d, 0x67,
    0x75, 0x70, 0x6c, 0x1b, 0x11, 0x25, 0x25, 0x70, 0x7f, 0x7e, 0x67, 0x63, 0x30, 0x3c, 0x6d, 0x6a,
    0x01, 0x51, 0x59, 0x5f, 0x56, 0x13, 0x10, 0x43, 0x19, 0x18, 0xe5, 0xe0, 0xbe, 0xbf, 0xbd, 0xe9,
    0xf0, 0xf1, 0xf9, 0xfa, 0xab, 0x8f, 0xc1, 0xdf, 0xcf, 0x8d, 0xf8, 0xe7, 0xe2, 0xe9, 0x93, 0x8e,
    0xec, 0xf5, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    
    0x37, 0x7a, 0x07, 0x11, 0x1f, 0x1d, 0x68, 0x25, 0x32, 0x77, 0x1e, 0x62, 0x23, 0x5b, 0x47, 0x55,
    0x53, 0x30, 0x11, 0x42, 0xf6, 0xf1, 0xb1, 0xe6, 0xc3, 0xcc, 0xf8, 0xc5, 0xe4, 0xcc, 0xc0, 0xd3,
    0x85, 0xfd, 0x9a, 0xe3, 0xe6, 0x81, 0xb5, 0xbb, 0xd7, 0xcd, 0x87, 0xa3, 0xd3, 0x6b, 0x36, 0x6f,
    0x6f, 0x66, 0x55, 0x30, 0x16, 0x45, 0x5e, 0x09, 0x74, 0x5c, 0x3f, 0x29, 0x2b, 0x66, 0x3d, 0x0d,
    0x02, 0x30, 0x28, 0x35, 0x15, 0x09, 0x15, 0xdd, 0xec, 0xb8, 0xe2, 0xfb, 0xd8, 0xcb, 0xd8, 0xd1,
    0x8b, 0xd5, 0x82, 0xd9, 0x9a, 0xf1, 0x92, 0xab, 0xe8, 0xa6, 0xd6, 0xd0, 0x8c, 0xaa, 0xd2, 0x94,
    0xcf, 0x45, 0x46, 0x67, 0x20, 0x7d, 0x44, 0x14, 0x6b, 0x45, 0x6d, 0x54, 0x03, 0x17, 0x60, 0x62,
    0x55, 0x5a, 0x4a, 0x66, 0x61, 0x11, 0x57, 0x68, 0x75, 0x05, 0x62, 0x36, 0x7d, 0x02, 0x10, 0x4b,
    0x08, 0x22, 0x42, 0x32, 0xba, 0xe2, 0xb9, 0xe2, 0xd6, 0xb9, 0xff, 0xc3, 0xe9, 0x8a, 0x8f, 0xc1,
    0x8f, 0xe1, 0xb8, 0xa4, 0x96, 0xf1, 0x8f, 0x81, 0xb1, 0x8d, 0x89, 0xcc, 0xd4, 0x78, 0x76, 0x61,
    0x72, 0x3e, 0x37, 0x23, 0x56, 0x73, 0x71, 0x79, 0x63, 0x7c, 0x08, 0x11, 0x20, 0x69, 0x7a, 0x14,
    0x68, 0x05, 0x21, 0x1e, 0x32, 0x27, 0x59, 0xb7, 0xcf, 0xab, 0xdd, 0xd5, 0xcc, 0x97, 0x93, 0xf2,
    0xe7, 0xc0, 0xeb, 0xff, 0xe9, 0xa3, 0xbf, 0xa1, 0xab, 0x8b, 0xbb, 0x9e, 0x9e, 0x8c, 0xa0, 0xc1,
    0x9b, 0x5a, 0x2f, 0x2f, 0x4e, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  ],
  
  exec: function()
  {
    // virtual machine architecture
    // ++++++++++++++++++++++++++++
    //
    // segmented memory model with 16-byte segment size (notation seg:offset)
    //
    // 4 general-purpose registers (r0-r3)
    // 2 segment registers (cs, ds equiv. to r4, r5)
    // 1 flags register (fl)
    //
    // instruction encoding
    // ++++++++++++++++++++
    //
    //           byte 1               byte 2 (optional)
    // bits      [ 7 6 5 4 3 2 1 0 ]  [ 7 6 5 4 3 2 1 0 ]
    // opcode      - - -             
    // mod               -           
    // operand1            - - - -
    // operand2                         - - - - - - - -
    //
    // operand1 is always a register index
    // operand2 is optional, depending upon the instruction set specified below
    // the value of mod alters the meaning of any operand2
    //   0: operand2 = reg ix
    //   1: operand2 = fixed immediate value or target segment (depending on instruction)
    //
    // instruction set
    // +++++++++++++++
    // 
    // Notes:
    //   * r1, r2 => operand 1 is register 1, operand 2 is register 2
    //   * movr r1, r2 => move contents of register r2 into register r1
    // 
    // opcode | instruction | operands (mod 0) | operands (mod 1)
    // -------+-------------+------------------+-----------------
    // 0x00   | jmp         | r1               | r2:r1
    // 0x01   | movr        | r1, r2           | rx,   imm 
    // 0x02   | movm        | r1, [ds:r2]      | [ds:r1], r2
    // 0x03   | add         | r1, r2           | r1,   imm
    // 0x04   | xor         | r1, r2           | r1,   imm 
    // 0x05   | cmp         | r1, r2           | r1,   imm 
    // 0x06   | jmpe        | r1               | r2:r1
    // 0x07   | hlt         | N/A              | N/A
    //
    // flags
    // +++++
    // 
    // cmp r1, r2 instruction results in:
    //   r1 == r2 => fl = 0
    //   r1 < r2  => fl = 0xff
    //   r1 > r2  => fl = 1
    // 
    // jmpe r1
    //   => if (fl == 0) jmp r1
    //      else nop
    
    throw "VM.exec not yet implemented";
  }
  
};

//--------------------------------------------------------------------------------------------------

try
{
  VM.exec();
}
catch(e)
{
  alert('\nError: ' + e + '\n');
}

//--------------------------------------------------------------------------------------------------
The file is pretty self explanatory. The aim is to write a virtual machine and run the code present in VM.mem. Then we should find a link to the last stage of the challenge hidden somewhere in memory.

There is one thing that is not explained though. This is the variable VM.cpu.firmware. It is not used at all for this part of the challenge but will be primordial for part 3. We can forget about it for the time being.

First let's change the name of the javascript file and create a HTML page to run it with our preferred browser.
$ mv 15b436de1f9107f3778aad525e5d0b20.js My\ VM.js

$ cat <<EOF > page.html
<html>
<script type="text/javascript" src="My VM.js"></script>
</html>
EOF

I suspected a trick so I first wrote a function to disassemble the code present in VM.mem. But no trick whatsoever. So I will just dump my solution that is pretty straightforward.
(Note : I don't usually code in javascript and I don't know it very much so it may be ugly but it does the job)
var VM = {
  
  cpu: {
    ...
  },
  
  mem: [
    ...
  ],
  
  exec: function()
  {
    while(1)
    {
        //Decode operand 1
        var opcode = VM.mem[VM.cpu.ip] >> 5;
        var mod = VM.mem[VM.cpu.ip] & 0x10;
        var param = VM.mem[VM.cpu.ip] & 0x0F;
        var param2 = VM.mem[VM.cpu.ip+1];
        
        var dest=VM.correct_registers(param); // Variable register indicated by operand 1
        
        switch(opcode)
        {
            // jmp
            case 0x00:
                var segment=param2;
                if (mod == 0)
                {
                    VM.cpu.ip=eval("VM.cpu.cs*0x10+"+dest); // Standard jmp are relative to the current CS. 
                    //Actually we should fetch instructions using the cs register (cs*0x10+ip) but we can take cs into account only in jmp and jmpe
                }
                else
                {
                    VM.cpu.ip=eval(segment+"*0x10 +"+dest);  // sixteen bytes segments
                    VM.cpu.cs = segment;    //  We must update the code segment when doing this kind of jump (this is what is done on x86)
                }
            break;
            
            //movr
            case 0x01:
                var src=param2;
                if (mod == 0)
                    src=VM.correct_registers(param2);
                
                eval(dest+"="+src);
                VM.cpu.ip+=2;
            break;
            
            //movm
            case 0x02:
                var src=VM.correct_registers(param2);
                if (mod == 0)
                    eval(dest+"=VM.mem[VM.cpu.ds*0x10+"+src+"]");
                else
                    eval("VM.mem[VM.cpu.ds*0x10+"+dest+"]="+src);
                VM.cpu.ip+=2;
            break;
            
            //add
            case 0x03:
                var src=param2;
                if (mod == 0)
                    src=VM.correct_registers(param2);
                
                eval(dest+"="+dest+"+"+src);
                VM.cpu.ip+=2;
            break;
            
            //xor
            case 0x04:
                var src=param2;
                if (mod == 0)
                    src=VM.correct_registers(param2);
                
                eval(dest+"="+dest+"^"+src);
                VM.cpu.ip+=2;
            break;
            
            //cmp
            case 0x05:
                var src=param2;
                if (mod == 0)
                    src=VM.correct_registers(param2);

                var result = eval(dest+"-"+src); //r1 -r2
                if (result == 0)
                    VM.cpu.fl=0;
                else if (result < 0) //r1 < r2
                    VM.cpu.fl=0xff;
                else if (result > 0) //r1 > r2
                    VM.cpu.fl=1;
                VM.cpu.ip+=2;
            break;

            //jmpe
            case 0x06:
                if (VM.cpu.fl == 0)
                {
                    var segment=param2;
                    if (mod == 0)
                    {
                        VM.cpu.ip=eval("VM.cpu.cs*0x10+"+dest); // Standard jmp are relative to the current CS !
                    }
                    else
                    {
                        VM.cpu.ip=eval(segment+"*0x10+"+dest);
                        VM.cpu.cs=segment;
                    }
                }
                else
                {
                    //NOP
                    if (mod == 0)
                        VM.cpu.ip++;
                    else
                        VM.cpu.ip+=2;
                }
            break;
            
            //hlt
            case 0x07:
                VM.dumphexa(); //Program ended : dumping result
                return 0;
            break;
            
            default :
                throw VM.mem[VM.cpu.ip].toString(16) + " Invalid OpCode<br>";
        }
    }
    },
  
    correct_registers: function(register_number)
    {
        if (register_number == 4)
            return  "VM.cpu.cs";
        else if (register_number == 5)
            return "VM.cpu.ds";
        else
            return "VM.cpu.r"+register_number;
    },
  
    dumphexa: function()
    {
        var i=0;
        for (i=0;i<VM.mem.length;i++)
        {
            var hexdata=VM.mem[i].toString(16);
            if (hexdata.length == 1)
                hexdata = "0"+hexdata;
            document.write(hexdata);
        }
    } 
  }  
};

//--------------------------------------------------------------------------------------------------

try
{
  VM.exec()
}
catch(e)
{
  alert('<br>Error: ' + e + '<br>');
}

//--------------------------------------------------------------------------------------------------
I used eval to do the job and that's why I needed something to get the register variable name from its number. This is what the function correct_registers is for.
Changing the structure of VM.cpu to an array would probably have been simpler.

There are two thing to keep in mind that I totally overlooked at first :
  • Instructions should be fetched relative to the code segment.
  • When there is a jump instruction with a target segment (mod 1) the code segment must be updated to the segment provided.
To fix my initial mistake I fixed the second point but for the first point I just made all jumps relative to the current code segment which basically boils down to the same thing as taking the code segment into account at fetching.

Running the Virtual Machine

To run it we open page.html with the browser of our choice.
It should dump the contents of the memory on the page as a long list of hexadecimal numbers once it reaches the end of VM.exec().

Finally we just take that dump to make it a file and search for anything interesting.
$ xxd -p -r <<EOF > My\ VM.dump
310433aa40028003520072017301b2503014c001800010100000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000003200750c3108333240028003520072017303b200c3b000301bc001ff000000
000000000000000000000000000000000000007510010000000000000000000000cc00000000000000
00000000000000007d1f15604d4d527d0e276d106d5a06564714420eb6b2b2e6ebb4838ed7e5d4d9c3
f08095f182829abd95a48d9a2b30694a6965551c7b691c6e04743521262f60034e371e335439e6bab4
a2ada4c595c8c1e48aece7928be881f0ad98a4d0c08dac2252657e272b5a12610a017a6b1d67474554
202f64613735333730666531356334313438626434636565633836316662646161352e657865204854
54502f312e300000000000000000000000000000377a07111f1d682532771e62235b475553301142f6
f1b1e6c3ccf8c5e4ccc0d385fd9ae3e681b5bbd7cd87a3d36b366f6f66553016455e09745c3f292b66
3d0d02302835150915ddecb8e2fbd8cbd8d18bd582d99af192abe8a6d6d08caad294cf454667207d44
146b456d5403176062555a4a6661115768750562367d02104b08224232bae2b9e2d6b9ffc3e98a8fc1
8fe1b8a496f18f81b18d89ccd4787661723e372356737179637c081120697a146805211e322759b7cf
abddd5cc9793f2e7c0ebffe9a3bfa1ab8bbb9e9e8ca0c19b5a2f2f4e4e000000000000000000000000
000000000000000000000000000000000000000000000000000000000000
EOF
 
$ strings My\ VM.dump
`MMR}
+0iJieU
t5!&/`
"Re~'+Z
gGET /da75370fe15c4148bd4ceec861fbdaa5.exe HTTP/1.0
h%2w
b#[GUS0
k6oofU0
E^      t\?)+f=
EFg }D
kEmT
`bUZJfa
xvar>7#Vsqyc|
Z//NN
Same thing than with Part1. A HTTP request to the next stage. Strangely this one is in HTTP 1.0 while the previous one was in HTTP 1.1. I couldn't make sense of that.
Anyway here is the full link : www.canyoucrackit.co.uk/da75370fe15c4148bd4ceec861fbdaa5.exe

The last and final part of the challenge is still ahead of us. It will be treated on the next post.

No comments:

Post a Comment