Difference in behavior when hooking a library function via LD_PRELOAD on Ubuntu and CentOS - linux

There is a hook function socketHook.c that intercepts socket() calls:
#include <stdio.h>
int socket(int domain, int type, int protocol)
{
printf("socket() has been intercepted!\n");
return 0;
}
gcc -c -fPIC socketHook.c
gcc -shared -o socketHook.so socketHook.o
And a simple program getpwuid.c (1) that just invokes the getpwuid() function:
#include <pwd.h>
int main()
{
getpwuid(0);
return 0;
}
gcc getpwuid.c -o getpwuid
getpwuid() internally makes a socket() call.
On CentOS:
$ strace -e trace=socket ./getpwuid
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(AF_UNIX, SOCK_STREAM, 0) = 4
On Ubuntu:
$ strace -e trace=socket ./getpwuid
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5
When running (1), socket() is intercepted on CentOS, but not on Ubuntu.
CentOS. printf() from socketHook.c is present:
$ uname -a
Linux centos-stream 4.18.0-301.1.el8.x86_64 #1 SMP Tue Apr 13 16:24:22 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ LD_PRELOAD=$(pwd)/socketHook.so ./getpwuid
socket() has been intercepted!
Ubuntu(Xubuntu 20.04). printf() from socketHook.c is NOT present:
$ uname -a
Linux ibse-VirtualBox 5.8.0-50-generic #56~20.04.1-Ubuntu SMP Mon Apr 12 21:46:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ LD_PRELOAD=$(pwd)/socketHook.so ./getpwuid
$
So my question is:
What does it depend on? I think this is affected by the fact that socket() is not called directly from the executable, but from getpwuid(), which in turn is called, if I understand correctly, from libc.so
How to achieve the same behavior in CentOS as in Ubuntu? I don't want intercept indirect calls from libc

What does it depend on?
There are two questions to ask:
Which function actually calls the socket system call?
How is that function getting called.
You can see how the socket system call is invoked by running your program under GDB, and using catch syscall socket command. On Ubuntu:
(gdb) catch syscall socket
Catchpoint 1 (syscall 'socket' [41])
(gdb) run
Starting program: /tmp/a.out
Catchpoint 1 (call to syscall socket), 0x00007ffff7ed3477 in socket () at ../sysdeps/unix/syscall-template.S:120
120 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) bt
#0 0x00007ffff7ed3477 in socket () at ../sysdeps/unix/syscall-template.S:120
#1 0x00007ffff7f08010 in open_socket (type=type#entry=GETFDPW, key=key#entry=0x7ffff7f612ca "passwd", keylen=keylen#entry=7) at nscd_helper.c:171
#2 0x00007ffff7f084fa in __nscd_get_mapping (type=type#entry=GETFDPW, key=key#entry=0x7ffff7f612ca "passwd", mappedp=mappedp#entry=0x7ffff7f980c8 <map_handle+8>) at nscd_helper.c:269
#3 0x00007ffff7f0894f in __nscd_get_map_ref (type=type#entry=GETFDPW, name=name#entry=0x7ffff7f612ca "passwd", mapptr=mapptr#entry=0x7ffff7f980c0 <map_handle>,
gc_cyclep=gc_cyclep#entry=0x7fffffffda0c) at nscd_helper.c:419
#4 0x00007ffff7f04fb7 in nscd_getpw_r (key=0x7fffffffdaa6 "0", keylen=2, type=type#entry=GETPWBYUID, resultbuf=resultbuf#entry=0x7ffff7f96520 <resbuf>,
buffer=buffer#entry=0x5555555592a0 "", buflen=buflen#entry=1024, result=0x7fffffffdb60) at nscd_getpw_r.c:93
#5 0x00007ffff7f05412 in __nscd_getpwuid_r (uid=uid#entry=0, resultbuf=resultbuf#entry=0x7ffff7f96520 <resbuf>, buffer=buffer#entry=0x5555555592a0 "", buflen=buflen#entry=1024,
result=result#entry=0x7fffffffdb60) at nscd_getpw_r.c:62
#6 0x00007ffff7e9e95d in __getpwuid_r (uid=uid#entry=0, resbuf=resbuf#entry=0x7ffff7f96520 <resbuf>, buffer=0x5555555592a0 "", buflen=buflen#entry=1024,
result=result#entry=0x7fffffffdb60) at ../nss/getXXbyYY_r.c:255
#7 0x00007ffff7e9dfd3 in getpwuid (uid=0) at ../nss/getXXbyYY.c:134
#8 0x0000555555555143 in main () at t.c:5
(gdb) info sym $pc
socket + 7 in section .text of /lib/x86_64-linux-gnu/libc.so.6
(gdb) up
#1 0x00007ffff7f08010 in open_socket (type=type#entry=GETFDPW, key=key#entry=0x7ffff7f612ca "passwd", keylen=keylen#entry=7) at nscd_helper.c:171
171 nscd_helper.c: No such file or directory.
(gdb) x/i $pc-5
0x7ffff7f0800b <open_socket+59>: callq 0x7ffff7ed3470 <socket>
From this we can see that
The function socket is called. Using nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep ' socket' we can confirm that that function is exported from libc.so.6, and thus should be interposable.
The caller does not call socket#plt (i.e. does not use the procedure linkage table), and so LD_PRELOAD will have no effect.
The call from open_socket() to socket() has been non-interposable since 2004, so it's likely that this call isn't intercepted on CentOS either, but some other call is. Probably the 3rd one in your strace output.
Using above method you should be able to tell where that call comes from.
I don't want intercept indirect calls from libc
In that case, LD_PRELOAD may be the wrong mechanism to use.
If you want to only intercept socket() calls from your own code, it's trivial to redirect them to e.g. mysocket() without any need for LD_PRELOAD.
You can do that at source level by adding e.g.
#define socket mysocket
to all your files, or using -Dsocket=mysocket argument at compile time.
Alternatively, using the linker --wrap=socket will do the redirection without recompiling.

Related

why does memory address change in Ubuntu and not in Redhat

I have this program:
double t;
main() {
}
On Ubuntu, I run:
% gdb a.out
(gdb) p &t
$1 = (double *) 0x4010 <t>
(gdb) run
Starting program: /home/phan/a.out
[Inferior 1 (process 95930) exited normally]
(gdb) p &t
$2 = (double *) 0x555555558010 <t>
Why did the address change from 0x4010 to 0x555555558010. Is there someway to prevent this?
On Redhat, it doesn't do that:
% gdb a.out
(gdb) p &t
$1 = (double *) 0x601038 <t>
(gdb) r
Starting program: /home/phan/a.out
[Inferior 1 (process 23337) exited normally]
(gdb) p &t
$2 = (double *) 0x601038 <t>
BTW, this only occurs in Ubuntu 18.04. In Ubuntu 16.04, it works exactly as Redhat, for example the address is the same before and after.
You are presumably seeing pre and post-relocation addresses for the .bss segment.
You can avoid this by disabling position independent executables, thus making gcc choose the final address of the .bss register up front:
gcc -no-pie foo.c
-static would have the effect.
I don't know why there'd be a difference between Ubuntu and Redhat though.

override return address of main in c

i'm trying to execute a buffer overflow attack on a program written in c, i'm using GNU/Linux (Ubuntu 16.04 LTS).
this is the source code:
#include<stdio.h>
void CALLME(){
puts("successful!");
}
int main(void){
char s[16];
scanf("%s",s);
}
what i want to do is override the return address of main so that after main function, the function CALLME will be executed.
i compile the program with
gcc -m32 -fno-stack-protector -o prog prog.c
use command:
nm prog | grep CALLME
i got the address of CALLME: 0804845b
disassemble main in gdb i found that: during main function, the return address is located at 8(%ebp) and the address of string s is at -0x18(%ebp). So the difference is 0x8 + 0x18 = 32
i try to exploit:
perl -e 'print "a" x 32 . "\x5b\x84\x04\x08"' | ./main
it didn't work.
Segmentation fault (core dumped)
Why ? Is main function more special ? Because in other functions (i made) that have a similar vulnerability it works ?
NOTE: i don't think about ASLR, some guys said that happens only when i compile gcc -pie ... and other stuffs.

Why does system call fails?

I try to compile simple utility (kernel 3.4.67), which
all it does is trying to use system call very simply as following:
int main(void)
{
int rc;
printf("hello 1\n");
rc = system("echo hello again");
printf("system returned %d\n",rc);
rc = system("ls -l");
printf("system returned %d\n",rc);
return 0;
}
Yet, system call fails as you can see in the following log:
root#w812a_kk:/ # /sdcard/test
hello 1
system returned 32512
system returned 32512
I compile it as following:
arm-linux-gnueabihf-gcc -s -static -Wall -Wstrict-prototypes test.c -o test
That's really wierd becuase I used system in past in different linux and never had any issue with it.
I even tried another cross cpompiler but I get the same failure.
Version of kernel & cross compiler:
# busybox uname -a
Linux localhost 3.4.67 #1 SMP PREEMPT Wed Sep 28 18:18:33 CST 2016 armv7l GNU/Linux
arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (crosstool-NG linaro-1.13.1-4.7-2013.03-20130313 - Linaro GCC 2013.03) 4.7.3 20130226 (prerelease)
EDIT:
root#w812a_kk:/ # echo hello again && echo $? && echo $0
hello again
0
tmp-mksh
root#w812a_kk:/ #
But I did find something interesting:
On calling test_expander() withing the main, it works OK. so I suspect that maybe system call try to find a binary which is not founded ?
int test_expander(void)
{
pid_t pid;
char *const parmList[] = {"/system/bin/busybox", "echo", "hello", NULL};
if ((pid = fork()) == -1)
perror("fork error");
else if (pid == 0) {
execv("/system/bin/busybox", parmList);
printf("Return not expected. Must be an execv error.n");
}
return 0;
}
Thank you for any idea.
Ran
The return value of system(), 32512 decimal, is 7F00 in hex.
This value is strangely similar to 0x7F, which is the result of system() if /bin/sh can not be executed. It seems there is some problem with byte ordering (big/little endian). Very strange.
Update: while writing the answer, you edited the question and pulled in something about /system/bin/busybox.
Probably you simply don't have /bin/sh.
I think I understand what happens
From system man page:
The system() library function uses fork(2) to create a child process
that executes the shell command specified in command using execl(3)
as follows:
execl("/bin/sh", "sh", "-c", command, (char *) 0);
But in my filesystem sh is founded only /system/bin , not in /bin
So I better just use execv instead. (I can't do static link becuase it's read-only filesystem)
Thanks,
Ran

C program stores function parameters from $rbp+4 in memory? My check failed

I was trying to learn how to use rbp/ebp to visit function parameters and local variables on ubuntu1604, 64bit. I've got a simply c file:
#include<stdio.h>
int main(int argc,char*argv[])
{
printf("hello\n");
return argc;
}
I compiled it with:
gcc -g my.c
Then debug it with argument parameters:
gdb --args my 01 02
Here I know the "argc" should be 3, so I tried to check:
(gdb) b main
Breakpoint 1 at 0x400535: file ret.c, line 5.
(gdb) r
Starting program: /home/a/cpp/my 01 02
Breakpoint 1, main (argc=3, argv=0x7fffffffde98) at ret.c:5
5 printf("hello\n");
(gdb) x $rbp+4
0x7fffffffddb4: 0x00000000
(gdb) x $rbp+8
0x7fffffffddb8: 0xf7a2e830
(gdb) x/1xw $rbp+8
0x7fffffffddb8: 0xf7a2e830
(gdb) x/1xw $rbp+4
0x7fffffffddb4: 0x00000000
(gdb) x/1xw $rbp
0x7fffffffddb0: 0x00400550
I don't find any clue that a dword of "3" is saved in any of bytes in $rbp+xBytes. Did I get anything wrong in my understanding or commands?
Thanks!
I was trying to learn how to use rbp/ebp to visit function parameters and local variables
The x86_64 ABI does not use stack to pass parameters; they are passed in registers. Because of that, you wouldn't find them at any offset off $rbp (this is different from ix86 calling convention).
To find the parameters, you'll need to look at the $rdi and $rsi regusters:
Breakpoint 1, main (argc=3, argv=0x7fffffffe3a8) at my.c:4
4 printf("hello\n");
(gdb) p/x $rdi
$1 = 0x3 # matches argc
(gdb) p/x $rsi
$2 = 0x7fffffffe3a8 # matches argv
x $rbp+4
You almost certainly wouldn't find anything useful at $rbp+4, because it is usually incremented or decremented by 8, in order to store the entire 64-bit value.

gdb catch syscall condition and string comparisson

I would like to catch a system call (more specifically access) and set a condition on it based on string comparison (obviously for arguments that are strings).
Specific example: when debugging ls I would like to catch access syscalls for specific pathnames (the 1st argument)
int access(const char *pathname, int mode);
So far, I have succeeded in manually inspecting the pathname argument of access (see [1]).
I tried to use this blog post:
catch syscall access
condition 1 strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
but failed (see [2]), as gdb informed me of a segfault and that Evaluation of the expression containing the function (strcmp#plt) will be abandoned.. However gdb suggested set unwindonsignal on.
Which I tried:
set unwindonsignal on
catch syscall access
condition 1 strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
but failed again (see [3]) with a similar error and the suggestion set unwindonsignal off...
I searched for the The program being debugged was signaled while in a function called from GDB. error message, but (I think) I didn't find something relevant.
Any help or ideas?
[1]
$ gdb ls
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ls...(no debugging symbols found)...done.
(gdb) catch syscall access
Catchpoint 1 (syscall 'access' [21])
(gdb) r
Starting program: /bin/ls
Catchpoint 1 (call to syscall access), 0x00007ffff7df3537 in access () at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) x /s $rdi
0x7ffff7df6911: "/etc/ld.so.nohwcap"
(gdb) c
Continuing.
Catchpoint 1 (returned from syscall access), 0x00007ffff7df3537 in access () at ../sysdeps/unix/syscall-template.S:81
81 in ../sysdeps/unix/syscall-template.S
(gdb) x /s $rdi
0x7ffff7df6911: "/etc/ld.so.nohwcap"
(gdb) c
Continuing.
Catchpoint 1 (call to syscall access), 0x00007ffff7df3537 in access () at ../sysdeps/unix/syscall-template.S:81
81 in ../sysdeps/unix/syscall-template.S
(gdb) x /s $rdi
0x7ffff7df9420 <preload_file.9747>: "/etc/ld.so.preload"
[2]
$ gdb ls
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ls...(no debugging symbols found)...done.
(gdb) catch syscall access
Catchpoint 1 (syscall 'access' [21])
(gdb) condition 1 strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
(gdb) info breakpoints
Num Type Disp Enb Address What
1 catchpoint keep y syscall "access"
stop only if strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
(gdb) r
Starting program: /bin/ls
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
Error in testing breakpoint condition:
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on".
Evaluation of the expression containing the function
(strcmp#plt) will be abandoned.
When the function is done executing, GDB will silently stop.
Catchpoint 1 (returned from syscall munmap), 0x0000000000000000 in ?? ()
[3]
$ gdb ls
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ls...(no debugging symbols found)...done.
(gdb) set unwindonsignal on
(gdb) catch syscall access
Catchpoint 1 (syscall 'access' [21])
(gdb) condition 1 strcmp((char*)($rdi), "/etc/ld.so.preload") == 0
(gdb) r
Starting program: /bin/ls
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
Error in testing breakpoint condition:
The program being debugged was signaled while in a function called from GDB.
GDB has restored the context to what it was before the call.
To change this behavior use "set unwindonsignal off".
Evaluation of the expression containing the function
(strcmp#plt) will be abandoned.
Catchpoint 1 (returned from syscall munmap), 0x00007ffff7df3537 in access () at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) x /s $rdi
0x7ffff7df6911: "/etc/ld.so.nohwcap"
You can use the gdb internal function $_streq like this:
(gdb) catch syscall access
Catchpoint 1 (syscall 'access' [21])
(gdb) condition 1 $_streq((char *)$rdi, "/etc/ld.so.preload")
(gdb) ru
Starting program: /bin/ls
Catchpoint 1 (call to syscall access), 0x00007ffff7df3537 in access ()
at ../sysdeps/unix/syscall-template.S:81
81 ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) p (char *)$rdi
$1 = 0x7ffff7df9420 <preload_file> "/etc/ld.so.preload"

Resources