I'd like to replace/emulate syscalls in a static binary running on 64bit linux 4.4.0-33, preferably using Intel Pin
from the documentation there is PIN_AddSyscallEntryFunction(...)
https://software.intel.com/sites/landingpage/pintool/docs/98484/Pin/html/group__SYSCALL.html
but it seems unable to skip the real syscall, am I missing something? Google didn't do the trick :(
I can try to replace syscall with a invalid id inside syscall entry callback and patching the retval inside syscall exit callback, but I'd rather not do these
there also seems to be other lower level functions(e.g. https://software.intel.com/sites/landingpage/pintool/docs/98484/Pin/html/group__INS__REF.html) but I'd like to try higher level functions first for readability and also to exploit the full potential of Pin and get familiar with the tool
-- BACKGROUND --
I've implemented a virtual file system(of HDFS) using LD_PRELOAD, letting any program able to access HDFS unmodified using a special path /hdfs/...
, but it didn't work for static linked binaries, and it has too many interception points(open and also open64, seek and also fseek, fputs etc)
here are the methods that I'v considered, please suggest if there's a better way:
- LD_PRELOAD replacing open/read/write/... // not working for static linked binaries, so I'm trying Pin here
- ptrace/SYSEMU // it seems too complex and likely to have performance issues
- nfs/fuse/... // too complex, need to adapt to too many protocols and can only be used for vfs, can't be extended to support other tech(e.g. hook socket operations) later when needed
- replace
sysenter/syscall
withint3
// should be the same as Pin? and SIGTRAP would be slower
is there any alternatives to Pin? https://github.com/pmem/syscall_intercept also relies on LD_PRELOAD so no luck there
CodePudding user response:
Pin allows to add a call of function before the instruction using INS_InsertCall()
. You can add the call of the function before the syscall
instruction. This function will check the syscall
arguments and emulate the system call if it is necessary. The arguments for system calls are passed only via registers, therefore it is necessary to pass the CONTEXT
object to the function. This object keeps the state of the processor and allows to get the register values. Also, this object can be passed to PIN_ExecuteAt()
to skip the syscall
instruction:
#include "pin.H"
#include <sys/syscall.h>
VOID syscall_handler(CONTEXT* ctx) {
bool skip_orig_sycall = true;
switch (PIN_GetContextReg(ctx, REG_RAX)) {
case SYS_write:
// emulate the syscall here or
// just notify the app that somthing went wrong with write() call
PIN_SetContextReg(ctx, REG_RAX, static_cast<ADDRINT>(-1));
break;
default:
skip_orig_sycall = false;
break;
}
if (skip_orig_sycall) {
const ADDRINT syscall_ins_size = 2;
const ADDRINT cur_ip = PIN_GetContextReg(ctx, REG_RIP);
PIN_SetContextReg(ctx, REG_RIP, cur_ip syscall_ins_size);
PIN_ExecuteAt(ctx); // continue execution after syscall instruction
}
}
VOID image_load(IMG img, VOID* v) {
if(!IMG_IsMainExecutable(img)) {
return;
}
for (SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec)) {
for (RTN rtn = SEC_RtnHead(sec); RTN_Valid(rtn); rtn = RTN_Next(rtn)) {
RTN_Open(rtn);
for (INS ins = RTN_InsHead(rtn); INS_Valid(ins); ins = INS_Next(ins)) {
if (INS_IsSyscall(ins)) {
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)syscall_handler,
IARG_CONTEXT, IARG_END);
}
}
RTN_Close(rtn);
}
}
}
int main(int argc, char* argv[]) {
if (PIN_Init(argc, argv)) {
PIN_ERROR("Cannot initialize Pin");
return EXIT_FAILURE;
}
PIN_InitSymbols();
IMG_AddInstrumentFunction(image_load, 0);
PIN_StartProgram();
return EXIT_SUCCESS;
}