How To: Hook Library call by LD_PRELOAD
Tert-Butyllithium

Reference: What Is the LD_PRELOAD Trick?

The LD_PRELOAD trick is a useful technique to influence the linkage of shared libraries and the resolution of symbols (functions) at runtime.

This post is a record for hooking an operator new in C++

Some Basic Information

new is defined at gcc/libstdc++-v3/libsupc++/new_op.cc, which is a part of gcc.

Check Library of a binary

1
2
3
4
5
6
7
$ ldd test_newop        
linux-vdso.so.1 (0x00007ffdb65fe000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fa3e159f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa3e11ae000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa3e0e10000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa3e1b2a000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fa3e0bf8000)

Check Library function used from binary

shell script from karlphillip

1
target="${FILE_NAME}"; for symbol in $(nm -D $target | grep "U " | cut -b12-); do for library in $(ldd $target | cut -d ' ' -f3- | cut -d' ' -f1); do for lib_symbol in $(nm -D $library | grep "T " | cut -b12-); do if [ $symbol == $lib_symbol ]; then echo -e "Found symbol: \e[1;36m$symbol\033[0m at \e[1;34m$library\033[0m"; fi ; done; done; done;

and its output should like this:

1
2
3
4
5
6
Found symbol: __cxa_atexit at /lib/x86_64-linux-gnu/libc.so.6
Found symbol: __libc_start_main at /lib/x86_64-linux-gnu/libc.so.6
Found symbol: _ZdlPvm at /usr/lib/x86_64-linux-gnu/libstdc++.so.6
Found symbol: _ZNSt8ios_base4InitC1Ev at /usr/lib/x86_64-linux-gnu/libstdc++.so.6
Found symbol: _ZNSt8ios_base4InitD1Ev at /usr/lib/x86_64-linux-gnu/libstdc++.so.6
Found symbol: _Znwm at /usr/lib/x86_64-linux-gnu/libstdc++.so.6

Compile

compile a shared object defined the function you want to replace.

new is defined as follow: (gcc version 10)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
{
void *p;

/* malloc (0) is unpredictable; avoid it. */
if (__builtin_expect (sz == 0, false))
sz = 1;

while ((p = malloc (sz)) == 0)
{
new_handler handler = std::get_new_handler ();
if (! handler)
_GLIBCXX_THROW_OR_ABORT(bad_alloc());
handler ();
}
return p;
}

and we could add a line to print the parameter before return: (needs include iostream in this file)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
{
void *p;

/* malloc (0) is unpredictable; avoid it. */
if (__builtin_expect (sz == 0, false))
sz = 1;

while ((p = malloc (sz)) == 0)
{
new_handler handler = std::get_new_handler ();
if (! handler)
_GLIBCXX_THROW_OR_ABORT(bad_alloc());
handler ();
}
std::cout << "new value: " << p << " with size=" << sz <<std::endl;
return p;
}

compile to shared object: g++ -shared -o lib_new_op_hooked.so -fPIC new_op.cc

LD_PRELOAD

In fact, LD_PRELOAD is an environment variable.

1
export LD_PRELOAD=${ABS_PATH_TO_'lib_new_op_hooked.so'}

and then you can run it directly (in the same shell).