sbz's blog

Technical writings and recipes on UNIX, software and systems

Perform system calls from python using ctypes

Posted at — Aug 17, 2010

Here a proof of concept code to performs system calls from Python via ctypes. This code show how to use syscall(2) interface to call write(2), getuid(2), and ptrace(2) system call only trace me here:

C statements

syscall(__NR_write, fd, buffer, len) where __NR_write is equal to 1 on x86_64 architecture and fd is stdout

syscall(__NR_getuid) where __NR_getuid is equal to 102 on x86_64 architecture

syscall(__NR_ptrace, req, pid, addr, data) where __NR_ptrace is equal to 101 on x86_64 architecture

Python statements

$ cat syscall.py
#!/usr/bin/env python
# example how to call syscall from python
# with write(2), ptrace(2) and getuid(2) example

from ctypes.util import find_library
from ctypes import *

import platform, sys

if sys.maxint == 0x7fffffffffffffff and platform.architecture()[0][:2] == '64':
   target_x64 = True
else:
   target_x64 = False

syscalls = {}
syscalls['__NR_write'] = (4, 1)[target_x64]
syscalls['__NR_ptrace'] = (26, 101)[target_x64]
syscalls['__NR_getuid'] = (24, 102)[target_x64]

libc = cdll.LoadLibrary(find_library('c'))

def ctypes_bind(funcname, restype=None, argtypes=[]):
   f = getattr(libc, funcname)
   f.restype = restype
   f.argtypes = argtypes
return f

def write(msg):
  syscall = ctypes_bind('syscall', c_int, [c_int, c_int, c_char_p, c_int])
  syscall(syscalls['__NR_write'], 1, msg, len(msg))

def getuid():
  syscall = ctypes_bind('syscall', c_int, [])
  syscall(syscalls['__NR_getuid'])

(
  PTRACE_TRACEME,
  PTRACE_PEEKEXT,
  PTRACE_PEEKDATA,
  PTRACE_USR,
  PTRACE_POKEEXT,
  PTRACE_POKEDATA,
  PTRACE_POKEUSR,
  PTRACE_CONT,
  PTRACE_KILL,
  PTRACE_SINGLESTEP
) = range(0, 10)
PTRACE_ATTACH = 16
PTRACE_DETACH = 17
PTRACE_SYSCALL = 24

def ptrace(req, pid, addr, data):
  syscall = ctypes_bind('syscall', c_long, [c_int, c_int, c_void_p, c_void_p])
  syscall(syscalls['__NR_ptrace'], req, pid, addr, data)

def traceme():
  ptrace(PTRACE_TRACEME, 0, 0, 0)

if __name__ == '__main__':
  write("sbz write syscall from python\n")
  print getuid()

On runtime in ipython:

In [1]: from syscall import *

In [2]: write("called write(1) from ipython\n")
called write(1) from ipython

In [3]: getuid()
1010

Like this, we can emulate the Kernel.syscall function of ruby Kernel module.