4

I tried to use LD_PRELOAD to hook sprintf function , so I will print to file the result of buffer:

#define _GNU_SOURCE
#include <stdio.h>
#include<dlfcn.h>

int sprintf (char * src , const char *  format , char* argp)
{
    int (*original_func)(char*,const char * , char*);
    original_func = dlsym(RTLD_NEXT,"sprintf");
    int ret = (*original_func)(src ,format,argp);
    FILE* output = fopen("log.txt","a");
    fprintf(output,"%s \n",src);
    fclose(output);
    return ret;


}

When I compile this code gcc -Wall -fPIC -shared -o my_lib.so test_ld.c -ldl

I got error

test_ld.c:5:5: error: conflicting types for ‘sprintf’
 int sprintf (char * src , const char *  format , char* argp)
     ^
In file included from test_ld.c:2:0:
/usr/include/stdio.h:364:12: note: previous declaration of ‘sprintf’ was here
 extern int sprintf (char *__restrict __s,

How can I fix that?

Hans Lub
  • 5,513
  • 1
  • 23
  • 43
MicrosoctCprog
  • 460
  • 1
  • 3
  • 23
  • 3
    The error is because the types of your arguments to sprintf differ from the previously declared on (from the stdib). The type of sprintf is `int sprintf(char *restrict s, const char *restrict format, ...)`. I'm not sure if the `restrict` keywords are necessary, but `sprintf` takes a varargs as the third argument, not a pointer. – Paul Hankin Apr 05 '21 at 10:31

3 Answers3

2

Alex's first solution nicely addresses one problem: the conflicting declarations of sprintf (although there is no reason to not use the same signature as in stdio.h, see dbush's answer) . However, even then there remains one large elephant in the room: sprintf is a variadic function.

That means that, whenever the wrapped program calls sprintf with anything other than one char * third argument, your output may not be correct (and may even depend on your compiler's -O level)

Calling variadic functions from variadic functions (what is essentially what you're doing here) is a known problem. Any solution will be non-portable. With gcc, you can use __buitlin_apply and tap into gccs own private way of handling argument lists:

/* sprintf.c, compile with gcc -Wall -fPIC -shared -o sprintf.so sprintf.c -ldl 
   and use with LD_PRELOAD=./sprintf.so <program> */

#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf

# define ENOUGH 100 /* how many bytes of our call stack 
                       to pass to the original function */

int sprintf (char *src) /* only needs the first argument */                                                                                     
{                                                                                                                                                   
  void *original_func = dlsym(RTLD_NEXT,"sprintf");                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
  void *arg = __builtin_apply_args();                                                                                                             
  void *ret = __builtin_apply((void *)original_func, arg, ENOUGH);                                                                                   
                                                                                                                                                
  FILE* output = fopen("log.txt","a");                                                                                                            
  fprintf(output,"%s \n",src);                                                                                                                    
  fclose(output);                                                                                                                                 
 __builtin_return(ret);                                                                                                                         
                                                                                                                                                
}               

A few remarks:

  • In well-designed libraries variadic functions will have a non-variadic counterpart that uses one va_list argument instead of a variable number of arguments. In that case (and sprintf - vsprintf is such a case) you can use Alex's (portable) second solution with the va_* macros. If not, the solution with __builtin_apply() is the only possible, albeit gcc-specific, one.
  • See also: call printf using va_list
  • Possibly depending on the compiler version, when compiling main.c with the -O2 flag, main() will actually call __sprintf_chk() instead of sprintf() (regardless of -fno-builtin) and the wrapper won't work. To demonstrate the wrapper, compile main.c with -O0. Changing the main program to get the wrapper to work is the tail wagging the dog, of course. This shows the fragility of building wrappers: programs often don't call the library functions you expect. A ltrace <program> beforehand can save a lot of work....
Hans Lub
  • 5,513
  • 1
  • 23
  • 43
  • 1) Why did you define # define ENOUGH 100 ? 2) you wrote int sprintf (char *src) with LD_PRELOAD don't you must write the full signature of function ? 3) Did you test your code? if I not include for `FILE` I can't compile that code , and if I include stdio.h I get conflict error again. – MicrosoctCprog Apr 05 '21 at 17:07
  • `__builtin_apply()` returns a pointer to data describing how to perform a call with the same arguments as were passed to the current function. `__builtin_apply(func, arg, N)` calls `func` with the same arguments as the surrounding function `sprintf`. For this it uses max `N` bytes of stack space - 100 bytes seems reasonable. As we don't need to refer to `format` and `argp` within our function, we can just leave them out - linkers don't typecheck. – Hans Lub Apr 05 '21 at 17:14
  • Did you test your code? if I not include for `FILE` I can't compile that code , and if I include stdio.h I get conflict error again. – MicrosoctCprog Apr 05 '21 at 17:16
  • Yes I did - the initial `#defines` and `#includes` of the program are identical to Alex's solution - I added them – Hans Lub Apr 05 '21 at 17:21
  • I took main.c from Alex and your code as `test_ld.c` and compile it with gcc `gcc -Wall -O2 -shared -fPIC -o libsprintf.so test_ld.c -ldl` and run this with `LD_PRELOAD=./libsprintf.so ./main` but this didn't work , txt file didn't create – MicrosoctCprog Apr 05 '21 at 17:57
  • I took main.c from Alex and your code as `test_ld.c` and compile it with gcc `gcc -Wall -O2 -shared -fPIC -o libsprintf.so test_ld.c -ldl` and run this with `LD_PRELOAD=./libsprintf.so ./main` but this didn't work , txt file didn't create – MicrosoctCprog Apr 05 '21 at 17:57
  • Yes, even if compiled with `-fno-builtin` `main` will not call your new and shiny `sprintf`. I solved it by replacing `#include ` by `extern int sprintf(char*, char *, ...); extern void puts(char *);`. This is an implementation thing (`sprintf` may be a macro inside `stdio.h` for all I know) and is not very relevant for the general problem. – Hans Lub Apr 05 '21 at 18:08
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/230770/discussion-between-microsoctcprog-and-hans-lub). – MicrosoctCprog Apr 05 '21 at 18:15
  • Please edit you post with full solution that I can compile and run – MicrosoctCprog Apr 05 '21 at 18:23
2

The main problem you're having is that your prototype for sprintf doesn't match the official one. Your function has this signature:

int sprintf (char * src , const char *  format , char* argp);

While the official one has:

int sprintf(char *str, const char *format, ...);

You'll need to change your function to have this signature. Once you do that, you'll need to use a va_list to get the variadic argument. Then you would use that to call vsprintf which takes an argument of this type instead of using dlsym to load sprintf.

#include <stdio.h>
#include <stdarg.h>

int sprintf (char * src , const char *  format , ...)
{
    va_list args;
    va_start(args, format);
    int ret = vsprintf(src, format, args);
    va_end(args);

    FILE* output = fopen("log.txt","a");
    fprintf(output,"%s \n",src);
    fclose(output);
    return ret;
}
dbush
  • 205,898
  • 23
  • 218
  • 273
1

You can rename the symbol in stdio, but then there's another problem, compilers like gcc use built-in implementations, unless you pass a flag like -fno-builtin, the compiler will generate the code inline in the executable, it won't link any library for functions like sprintf.

sprintf.c:

#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf

int sprintf (char * src , const char *  format , char* argp)
{
    int (*original_func)(char*,const char * , char*);
    original_func = dlsym(RTLD_NEXT,"sprintf");
    int ret = (*original_func)(src ,format,argp);
    FILE* output = fopen("log.txt","a");
    fprintf(output,"%s \n",src);
    fclose(output);
    return ret;
}

main.c:

#include <stdio.h>

int main(int argc, char *argv[]) {
    char buffer[80];
    sprintf(buffer, "hello world");
    puts(buffer);
    return 0;
}

Makefile:

all: libsprintf.so main

main: main.c
    gcc -Wall -O2 -fno-builtin -o main main.c

libsprintf.so: sprintf.c
    gcc -Wall -O2 -shared -fPIC -fno-builtin -o libsprintf.so sprintf.c -ldl

.PHONY: clean
clean:
    -rm -f main libsprintf.so

usage:

make
LD_PRELOAD=./libsprintf.so ./main

edit

sprintf.c with variadic implementation (it doesn't call sprintf):

#define _GNU_SOURCE
#define sprintf xsprintf
#include <stdio.h>
#include<dlfcn.h>
#undef sprintf
#include <stdarg.h>

int sprintf (char * src , const char *  fmt , ...)
{
    va_list args;
    va_start(args, fmt);
    int ret = vsprintf(src, fmt, args);
    va_end(args);

    FILE* output = fopen("log.txt","a");
    fprintf(output,"%s \n", src);
    fclose(output);
    return ret;
}
Alex
  • 3,264
  • 1
  • 25
  • 40
  • 1) Why does `main.c` must be compile with `-fno-builtin` 2) `#define sprintf xsprintf` replace `sprintf ` into `stdio.h` ? – MicrosoctCprog Apr 05 '21 at 16:43
  • @MicrosoctCprog 1) see the link in the updated answer, there's a mention of gcc documentation about no-builtin. 2) yes, once you define *something* as *somethingelse* everything matching *something* in the sources (including included headers) is replaced by the preprocessor with *somethingelse* – Alex Apr 05 '21 at 17:07
  • I sorry that I didn't mention that but I can't compile main.c with `-fno-builtin` my assumption that I can't control of hook binary, only run it – MicrosoctCprog Apr 05 '21 at 17:10
  • @MicrosoctCprog even if you didn't mention I assumed so, the problem is, if your executable was built using built-in, won't link any library, sprintf won't be defined as dynamic symbol and you can't use LD_PRELOAD to hook it. – Alex Apr 05 '21 at 17:12