Click here to Skip to main content
Click here to Skip to main content

Code Injection into Running Linux Application

By , 12 Feb 2009
Rate this:
Please Sign up or sign in to vote.

Introduction

Let's assume that your program is running on Linux and is not going to terminate for a long period of time, like UNIX daemons. However you want to upgrade the program in some simple way but you do not want to terminate the program execution. What comes to your mind is to somehow upgrade some known function in your program so that it will do some additional job without compromising the function's usual behavior and without terminating your program. You think about injecting some new code into the code of your program so that it will be triggered when another already existing function in your program is called. This may be a bit imaginary example but it demonstrates the idea, why it is sometimes needed to inject some code in the running program. It is also relevant to mention the virus injection techniques into the running code.

In this article, I'll explain how it is possible to inject a C function into the running program on Linux without terminating the program. We'll talk about Linux object files Executable and Linkable Format (ELF), about object file sections, symbols and relocations.

Working Example Overview

I will explain step by step the code injection technique using the following simple example. The example consists of 3 components:

  1. Dynamic (shared) library libdynlib.so that is built from dynlib.hpp and dynlib.cpp C++ source files. 
  2. Application app that is built from app.cpp source file and is linked with libdynlib.so library. 
  3. The injection function located in injection.cpp file.

Let us review the components code.

// dynlib.hpp

extern "C" void print();

The dynlib.hpp header defines the print() function.

// dynlib.cpp

#include <stdlib.h>
#include <iostream>
#include "dynlib.hpp"

using namespace std;


extern "C" void print()
{
    static unsigned int counter = 0;
    ++counter;

    cout << counter << ": PID " << getpid() << ": In print() " << endl;
}

The dynlib.cpp implements the print() function that just prints a counter (that is incremented at every function call), the program process id and a message.

// app.cpp

#include <dlfcn.h />
#include <iostream />
#include "dynlib.hpp"

using namespace std;


int main()
{
    while (1)
    {
        print();
        cout << "Going to sleep ..." << endl;
        sleep(3);
        cout << "Waked up ..." << endl;
    }

    return 0;
}

The application app.cpp calls the print() function (from the libdynlib.so dynamic library, then sleeps for a few seconds and continues doing the same in the infinite loop.

// injection.cpp

#include <stdlib.h />

extern "C" void print();

extern "C" void injection()
{
    print(); // do the original job, call the function print()
	system("date"); // do some additional job
}

The injection() function call is going to replace the print() function call in the application main() function. The injection() function will first call the original print() function and then do some additional job. For example, it can run some external executable file using system() function call or just print the current date as I do.

Compile and Run the Application

Let us first compile the components with g++ C++ compiler and gcc C compiler.

g++ -ggdb -Wall dynlib.cpp -fPIC -shared -o libdynlib.so
g++ -ggdb app.cpp -ldynlib -ldynlib -L./ -o app
gcc  -Wall injection.cpp -c -o injection.o

-rwxr-xr-x  1 gregory ftp  52248 Feb 12 02:05 app
-rw-r--r--  1 gregory ftp   1088 Feb 12 02:05 injection.o
-rwxr-xr-x  1 gregory ftp  52505 Feb 12 02:05 libdynlib.so

Note that the dynamic library libdynlib.so is compiled and linked with -fPIC flag that produces position independent code and the injection object is compiled with C compiler. We can now run the application app executable.

[lnx63:code_injection] ==> ./app
1: PID 4184: In print()
Going to sleep ...
Waked up ...
2: PID 4184: In print()
Going to sleep ...
Waked up ...
3: PID 4184: In print()
Going to sleep ...

Getting into Debugger

The application app passed few loop iterations but we pretend that it's already running few weeks so it's now time to inject our new code without terminating the applications. We'll use Linux gdb debugger during the injection process. First we need to attach gdb to the application process 4184, see the PID (application process id) printed above.

[lnx63:code_injection] ==> gdb app 4184
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
	Using host libthread_db library "/lib/tls/libthread_db.so.1".

Attaching to program: /store/fileril104/project/gregory/code_injection/app, process 4184
Reading symbols from 
	/store/fileril104/project/gregory/code_injection/libdynlib.so...done.
Loaded symbols for /store/fileril104/project/gregory/code_injection/libdynlib.so
Reading symbols from /usr/lib/libstdc++.so.6...done.
Loaded symbols for /usr/lib/libstdc++.so.6
Reading symbols from /lib/tls/libm.so.6...done.
Loaded symbols for /lib/tls/libm.so.6
Reading symbols from /lib/libgcc_s.so.1...done.
Loaded symbols for /lib/libgcc_s.so.1
Reading symbols from /lib/tls/libc.so.6...done.
Loaded symbols for /lib/tls/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0x006e17a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
(gdb)

Loading the Injection Code into the Executable Process Memory

As I mentioned above, injection.o object file is not initially included in the app executable process image. We first need to load injection.o into the process memory address space. This can be done with mmap() system call that will map the injection.o file into the app process address space. Let us do it in the debugger.

(gdb) call open("injection.o", 2)
$1 = 3
(gdb) call mmap(0, 1088, 1 | 2 | 4, 1, 3, 0)
$2 = 1073754112
(gdb)

We first open the injection.o file with O_RDWR (value 2) read/write permissions. We need write permission because later we'll make changes in the loaded injection code. The returned allocated file descripter for the opened file is 3. Then we bring the file into the process address space with mmap() call. The mmap() call accepts the file size (1088 bytes), the file mapping permissions - PROT_READ | PROT_WRITE | PROT_EXEC (for reading/writing and executing, 1 | 2 | 4) and opened file descriptor - 3. and returns the starting address of the mapped file within the process address space - 1073754112. We can verify that the injection.o was indeed mapped into the process address space by looking into /proc/[pid]/maps (where pid is the executable process id - 4184 in our example) file that on Linux is the file that contains information about running process memory layout.

[lnx63:code_injection] ==> cat /proc/4184/maps
006e1000-006f6000 r-xp 00000000 fd:00 394811     /lib/ld-2.3.4.so
006f6000-006f7000 r-xp 00015000 fd:00 394811     /lib/ld-2.3.4.so
006f7000-006f8000 rwxp 00016000 fd:00 394811     /lib/ld-2.3.4.so
006ff000-00824000 r-xp 00000000 fd:00 394812     /lib/tls/libc-2.3.4.so
00824000-00825000 r-xp 00124000 fd:00 394812     /lib/tls/libc-2.3.4.so
00825000-00828000 rwxp 00125000 fd:00 394812     /lib/tls/libc-2.3.4.so
00828000-0082a000 rwxp 00828000 00:00 0
00832000-00853000 r-xp 00000000 fd:00 394813     /lib/tls/libm-2.3.4.so
00853000-00855000 rwxp 00020000 fd:00 394813     /lib/tls/libm-2.3.4.so
0096e000-00975000 r-xp 00000000 fd:00 394816     /lib/libgcc_s-3.4.6-20060404.so.1
00975000-00976000 rwxp 00007000 fd:00 394816     /lib/libgcc_s-3.4.6-20060404.so.1
00978000-00a38000 r-xp 00000000 fd:00 45535      /usr/lib/libstdc++.so.6.0.3
00a38000-00a3d000 rwxp 000bf000 fd:00 45535      /usr/lib/libstdc++.so.6.0.3
00a3d000-00a43000 rwxp 00a3d000 00:00 0
08048000-08049000 r-xp 00000000 00:34 30468731   /store/fileril104/project/gregory/
						code_injection/app
08049000-0804a000 rwxp 00000000 00:34 30468731   /store/fileril104/project/gregory/
						code_injection/app
0804a000-0806b000 rwxp 0804a000 00:00 0
40000000-40001000 r-xp 00000000 00:34 30468725   /store/fileril104/project/gregory/
						code_injection/libdynlib.so
40001000-40002000 rwxp 00000000 00:34 30468725   /store/fileril104/project/gregory/
						code_injection/libdynlib.so
40002000-40003000 rwxp 40002000 00:00 0
40003000-40004000 rwxs 00000000 00:34 30468724   /store/fileril104/project/gregory/
						code_injection/injection.o
4000f000-40011000 rwxp 4000f000 00:00 0
bfffe000-c0000000 rwxp bfffe000 00:00 0
ffffe000-fffff000 ---p 00000000 00:00 0

You can verify that /store/fileril104/project/gregory/code_injection/injection.o starts at address 0x40003000 (decimal 1073754112) and ends at address 0x40004000 within the process address space. Other dynamic libraries mapping is also shown in the above output. Well, we now have all the components loaded in the executable process memory.

Relocations

Now it's time to inspect the application binary executable in ELF format from inside. We'll use readelf Linux utility that displays different data from ELF format object files (i.e. any object, library or executable files on Linux). We look at the symbol relocations in the app executable. We are interested in print() function call relocation.

[lnx63:code_injection] ==> readelf -r app

Relocation section '.rel.dyn' at offset 0x5ec contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049d58  00001706 R_386_GLOB_DAT    00000000   __gmon_start__
08049d60  00000305 R_386_COPY        08049d60   _ZSt4cout

Relocation section '.rel.plt' at offset 0x5fc contains 13 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
08049d24  00000107 R_386_JUMP_SLOT   0804868c   print
08049d28  00000207 R_386_JUMP_SLOT   0804869c   _ZNSt8ios_base4InitC1E
08049d2c  00000507 R_386_JUMP_SLOT   080486ac   _ZStlsISt11char_traits
08049d30  00000607 R_386_JUMP_SLOT   080486bc   _ZNSolsEPFRSoS_E
08049d34  00000707 R_386_JUMP_SLOT   08048664   _init
08049d38  00000807 R_386_JUMP_SLOT   080486dc   sleep
08049d3c  00000907 R_386_JUMP_SLOT   080486ec   _ZNKSsixEj
08049d40  00000b07 R_386_JUMP_SLOT   080486fc   _ZNKSs4sizeEv
08049d44  00000c07 R_386_JUMP_SLOT   0804870c   __libc_start_main
08049d48  00000d07 R_386_JUMP_SLOT   08048ae4   _fini
08049d4c  00001307 R_386_JUMP_SLOT   0804872c   _ZSt4endlIcSt11char_tr
08049d50  00001507 R_386_JUMP_SLOT   0804873c   __gxx_personality_v0
08049d54  00001607 R_386_JUMP_SLOT   0804874c   _ZNSt8ios_base4InitD1E

As you can see, the print symbol relocation is located at the absolute (virtual) address (offset) 0x08049d24 in the app executable and the type of this relocation is R_386_JUMP_SLOT. The relocation address is an absolute virtual address of the executable after it is loaded in the memory prior to its run. Note that this relocation resides in the .rel.plt section of the executable binary image. The PLT stands for Procedure Linkage Table, that is the table that provides indirect call for a function. This means that when you call a function you don't directly jump to the function location, but first jump to an entry in the Procedure Linkage Table and then from the PLT jump to the actual function code. This is necessary when you call a function that resides in a dynamic library (libdynlib.so in our example) because you do not know in advance at what address in the executable process space the dynamic libraries will be loaded and in what dynamic library you will first find the required function (print() in our example). All this knowledge is available only at the moment of loading application into the memory prior to its run and at that time it's the job of dynamic linker (ld-linux.so on Linux) to resolve relocations so that the requested function will be correctly called. In our example the dynamic linker will load the libdynlib.so library into the executable process address space, find the address of the print() function in the library and set this address into the relocation address 0x08049d24.

Our goal is to replace the address of the print() function with the address of function injection() from the injection.o object file that was not initially included in the executable process image when it started running.

More information on ELF format, relocations and dynamic linker can be found in Executable and Linkable Format (ELF) document.

We can check that the address 08049d24 currently contains the address of function print().

(gdb) p & print
$4 = (void (*)(void)) 0x40000be8 <print>
(gdb) p/x * 0x08049d24
$5 = 0x40000be8
(gdb)

The address of the injection() function can be found by running readelf -s (displays object file symbol table) on the injection.o file.

[lnx63:code_injection] ==> readelf -s injection.o

Symbol table '.symtab' contains 13 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS injection.cpp
     2: 00000000     0 SECTION LOCAL  DEFAULT    1
     3: 00000000     0 SECTION LOCAL  DEFAULT    3
     4: 00000000     0 SECTION LOCAL  DEFAULT    4
     5: 00000000     0 SECTION LOCAL  DEFAULT    5
     6: 00000000     0 SECTION LOCAL  DEFAULT    6
     7: 00000000     0 SECTION LOCAL  DEFAULT    8
     8: 00000000     0 SECTION LOCAL  DEFAULT    9
     9: 00000000    25 FUNC    GLOBAL DEFAULT    1 injection
    10: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND system
    11: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND print
    12: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND __gxx_personality_v0

The function (symbol) injection is located at the offset 0 in the .text section in the injection.o object file. But the .text section starts at the offset 0x000034 in the injection.o object file.

[lnx63:code_injection] ==> readelf -S injection.o
There are 13 section headers, starting at offset 0x104:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000019 00  AX  0   0  4
  [ 2] .rel.text         REL             00000000 000418 000018 08     11   1  4
  [ 3] .data             PROGBITS        00000000 000050 000000 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 000050 000000 00  WA  0   0  4
  [ 5] .rodata           PROGBITS        00000000 000050 000005 00   A  0   0  1
  [ 6] .eh_frame         PROGBITS        00000000 000058 000038 00   A  0   0  4
  [ 7] .rel.eh_frame     REL             00000000 000430 000010 08     11   6  4
  [ 8] .note.GNU-stack   NOTE            00000000 000090 000000 00      0   0  1
  [ 9] .comment          PROGBITS        00000000 000090 000012 00      0   0  1
  [10] .shstrtab         STRTAB          00000000 0000a2 00005f 00      0   0  1
  [11] .symtab           SYMTAB          00000000 00030c 0000d0 10     12   9  4
  [12] .strtab           STRTAB          00000000 0003dc 00003b 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

Replacing the print() Function with injection() Function

I would like to remind you that the injection.o file was loaded into the executable process memory at address 0x40003000 (see above). So the final absolute address of the injection() function within the executable process.is 0x40003000 + 0x000034.

We now set this address into the print() function relocation address 0x08049d24.

(gdb) set * 0x08049d24 = 0x40003000 + 0x000034
(gdb) 

At this point, we successfully replaced the call to the print() with the call to the injection() function.

Resolving injection() Function Relocations

However we still need some work to be done. The code of the injection() function is not ready to run yet because it has 3 unresolved relocations.

[lnx63:code_injection] ==> readelf -r injection.o

Relocation section '.rel.text' at offset 0x418 contains 3 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000009  00000501 R_386_32          00000000   .rodata
0000000e  00000a02 R_386_PC32        00000000   system
00000013  00000b02 R_386_PC32        00000000   print

Relocation section '.rel.eh_frame' at offset 0x430 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000011  00000c01 R_386_32          00000000   __gxx_personality_v0
00000024  00000201 R_386_32          00000000   .text

The first .rodata relocation points to the "date" constant string stored in the .rodata read-only data section, the second system relocation refers to the system() function call and the third print relocation refers to the print() function call. Note that all the three relocations reside in the .rel.text section that is their offsets are relative to the beginning of the .text section.

We resolve all the above three relocations manually and set appropriate addresses to these three memory locations. The addresses of these relocations within the executable process address space are calculated by summing up:

  1. The injection.o starting address (0x40003000) within the process address space. 
  2. The .text section starting offset 0x000034 within the injection.o object file. 
  3. The relocation offset relative to the .text section (0x00000009 - for .rodata, 0x0000000e. for system and 00000013 for print).

Note that system and print relocations are of R_386_PC32 type. This means that the value (resolved address) to be set into the relocation location should be calculated relatively to the PC program counter, that is relatively to the relocation location. Also R_386_PC32 relocation requires that the value that was stored in the relocation location before relocation resolution (addend) should be added to the resolved address. The R_386_32 .rodata relocation also adds the addend to its resolved address.

(gdb) p & system
$7 = (<text> *) 0x733650 <system>  // Address of the system() function
(gdb) p * (0x40003000 + 0x000034 + 0x0000000e)
$8 = -4                              // Addend of the system relocation
(gdb) set * (0x40003000 + 0x000034 + 0x0000000e) = 0x733650 -
	(0x40003000 + 0x000034 + 0x0000000e) - 4
(gdb) p & print
$9 = (void (*)(void)) 0x40000be8 <print>    // Address of the print() function
(gdb) p * (0x40003000 + 0x000034 + 0x00000013)
$10 = -4                             // Addend of the print relocation
(gdb) set * (0x40003000 + 0x000034 + 0x00000013) = 0x40000be8 -
	(0x40003000 + 0x000034 + 0x00000013) - 4
(gdb) p * (0x40003000 + 0x000034 + 0x00000009)
$11 = 0                              // Addend of the .rodata relocation
(gdb) set * (0x40003000 + 0x000034 + 0x00000009) = 0x40003000 + 0x000050 // 0x000050 is
		// the offset of .rodata section within injection.o object file.

We just resolved all the three relocations within injection() function code. Well, we are done. We exit the debugger. The application will continue running and now do additional job of printing the current date.

gdb) quit
The program is running.  Quit anyway (and detach it)? (y or n) y
Detaching from program:
	/store/fileril104/project/gregory/code_injection/app, process 4184
[lnx63:code_injection] ==>

// The application execution continues

Waked up ...
Thu Feb 12 20:09:40 IST 2009
4: PID 4184: In print()
Going to sleep ...
Waked up ...
Thu Feb 12 20:09:43 IST 2009
5: PID 4184: In print()
Going to sleep ...
Waked up ...
Thu Feb 12 20:09:46 IST 2009
6: PID 4184: In print()
Going to sleep ...
Waked up ...
Thu Feb 12 20:09:49 IST 2009
7: PID 18138: In print()
Going to sleep ...
Waked up ...

That's it.

Conclusion

I showed how one can inject a C function into the running program on Linux without terminating the program. Note that process memory manipulations that were demonstrated are allowed only for processes for which you are either owner or have appropriate permissions.

History

  • 12th February, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Gregory Shpitalnik
Software Developer (Senior) Marvell
Israel Israel
17 years experience software engineer at Marvell company in Israel.

Comments and Discussions

 
Question-1 is returned whenever I run mmap in gdb PinmemberMember 106455486-Mar-14 4:34 
GeneralMy vote of 3 PinmemberMember 106455486-Mar-14 2:17 
Questiongood artical Pinmemberswap000.bhosale@gmail.com9-Jan-14 20:58 
Question64-bit? PinmemberValeriy Osipov11-Oct-13 0:30 
GeneralMy vote of 5 Pinmembersearockcliff6-Sep-11 21:37 
GeneralNice job PinmemberNiklas Lindquist25-Feb-09 2:20 
GeneralRe: Nice job Pinmemberjohn-6553725-Jul-12 20:53 
GeneralRe: Nice job PinmemberNiklas Lindquist26-Jul-12 9:06 
GeneralNice article ! Pinmembercacalex18-Feb-09 7:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140421.2 | Last Updated 12 Feb 2009
Article Copyright 2009 by Gregory Shpitalnik
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid