Hacking the Virtual Memory

Sravanthi Sinha
4 min readJul 24, 2017

--

In this post I would be explaining about how to alter/hack the memory(heap in this case) used by a process.

Things to know:

  • A bit about the Virtual Address Space.
  • What is /proc filesystem?
  • What are /proc/maps, /proc/mem and link between them?

Ever wondered how a process can access/utilize the memory on a machine?

Every process’s executable is stored on-disk, when we execute a C program the program loader transfers the binary code from on-disk program to memory. The kernel has to allocate an additional space in memory as and when needed by the process. This space particularly allocated for a process to access is named as virtual address space which is divided into various segments.

Virtual Address Space

What is up with /proc filesystem?

The proc file system acts as an interface to internal data structures in the
kernel. It can be used to obtain information about the system and to change
certain kernel parameters at runtime (sysctl).

For every process running in a system a sub-directory is created in proc which is named after the process ID (PID).

To view the list of them use: ls /proc/

Each process sub-directory has the following entries: clear_refs, cmdline, cp, cwd, environ, exe, fd, maps, mem, root, stat, statm, status, wchan, pagemap, stack, smaps.

Here we will discuss more on maps and mem entries only

  • maps- Memory maps to the executables and library files.
  • mem- Memory held by this process.

/proc/PID/maps

The /proc/PID/maps file contain the currently mapped memory regions and their access permissions.

The format followed- address perms offset dev inode pathname

for eg: the maps file of a cat process

maps file of a `cat`

/proc/PID/mem

It shows the contents of $pid’s memory mapped. If an address is unmapped in the process, reading from the corresponding offset in the file returns EIO(Input/output error). For example, since the first page in a process is never mapped (so that dereferencing a NULL pointer fails cleanly rather than unintendedly accessing actual memory), reading the first byte of /proc/$pid/mem always yield an I/O error.

Now let us consider a C program which allocates and stores a string on heap.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/**
* main - uses strdup to create a new string, loops forever-ever
*
* Return: EXIT_FAILURE if malloc failed. Other never returns
*/
int main(void)
{
char *s;
unsigned long int i;
s = strdup("Holberton");
if (s == NULL)
{
fprintf(stderr, "Can't allocate mem with malloc\n");
return (EXIT_FAILURE);
}
i = 0;
while (s)
{
printf("[%lu] %s\n", i, s);
sleep(1);
i++;
}
return (EXIT_SUCCESS);
}

Reading the heap memory using a python program which takes pid of the process and prints the contents of a heap

#!/usr/bin/python3
import sys
import re
def print_usage():
print("Usage: read_heap.py pid")
exit(1)
def read_heap(pid, only_writable=True):
mem_perm = 'rw' if only_writable else 'r-'
maps_filename = "/proc/{}/maps".format(pid)
mem_filename = "/proc/{}/mem".format(pid)
try:
with open(maps_filename, 'r') as maps_file:
with open(mem_filename, 'rb+', 0) as mem_file:
for line in maps_file.readlines():
addr_perm = r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r][-w])'
m = re.search(addr_perm, line)
h = re.search(r'(\[heap\])', line)
if m.group(3) == mem_perm and h and h.group(0) == "[heap]":
start_addr = int(m.group(1), 16)
end_addr = int(m.group(2), 16)
mem_file.seek(start_addr)
heap = mem_file.read(end_addr - start_addr)
print(''.join(chr(i) for i in heap))
except IOError as e:
print("[ERROR] Can not open file {}:".format(maps_filename))
print(" I/O error({}): {}".format(e.errno, e.strerror))
exit(1)
try:
if len(sys.argv) != 2:
print_usage()
pid = int(sys.argv[1])
read_heap(pid)
except Exception as e:
print_usage()

In the above video, I have ran the program and got the pid of process and read the heap memory used by the process.

So we can easily alter the heap memory as we have the access to the heap memory as following

#!/usr/bin/python3
import sys
import re
def print_usage():
print("Usage: read_write_heap.py pid search_s replace_s")
exit(1)
def read_write_heap(pid, search_s, replace_s, only_writable=True):
mem_perm = 'rw' if only_writable else 'r-'
maps_filename = "/proc/{}/maps".format(pid)
mem_filename = "/proc/{}/mem".format(pid)
try:
with open(maps_filename, 'r') as maps_file:
with open(mem_filename, 'rb+', 0) as mem_file:
for line in maps_file.readlines():
addr_perm = r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r][-w])'
m = re.search(addr_perm, line)
h = re.search(r'(\[heap\])', line)
if m.group(3) == mem_perm and h and h.group(0) == "[heap]":
start_addr = int(m.group(1), 16)
end_addr = int(m.group(2), 16)
mem_file.seek(start_addr)
heap = mem_file.read(end_addr - start_addr)
pos = heap.find(bytes(search_s, "ASCII"))
if pos:
mem_file.seek(start_addr + pos)
adjusted_str = replace_s.ljust(len(search_s))
mem_file.write(bytes(adjusted_str, "ASCII"))
heap.write(bytes(adjusted_str, "ASCII"))
else:
print("Couldn't find the %s in the heap", search_s)
except IOError as e:
print("[ERROR] Can not open file {}:".format(maps_filename))
print(" I/O error({}): {}".format(e.errno, e.strerror))
exit(1)
try:
if len(sys.argv) != 4:
print_usage()
pid = int(sys.argv[1])
search_s = sys.argv[2]
replace_s = sys.argv[3]
if (len(search_s) == 0 or len(replace_s) == 0):
print_usage()
read_write_heap(pid, search_s, replace_s)
except Exception as e:
print_usage()

We have to provide the pid, search string and the string to be replaced with.

Keep Hacking ;)

--

--