Skip to content

Instantly share code, notes, and snippets.

@pudquick
Last active August 4, 2021 02:57
Show Gist options
  • Save pudquick/d71e151156d72676fb22e5438136627f to your computer and use it in GitHub Desktop.
Save pudquick/d71e151156d72676fb22e5438136627f to your computer and use it in GitHub Desktop.
Calling a non-external local C function (dlsym does not resolve it) with ctypes in python (or by using an address directly)
// helper.h
// two functions in this dylib - helper and helpersecret
void helper();
void helpersecret();
// helper.c
// Compile into example dylib with helper.c and helper.h in the same directory:
// clang -dynamiclib helper.c -o libhelper.dylib
#include <stdio.h>
#include "helper.h"
// helper is externally visible by default
void helper () {
puts("helper");
}
// helpersecret is marked to stay local
__attribute__ ((visibility("hidden"))) void helpersecret () {
puts("you called a secret");
helper();
}
# Running nm against the resulting libhelper.dylib and you can see:
#
# nm libhelper.dylib
# 0000000000008008 d __dyld_private
# 0000000000003f30 T _helper
# 0000000000003f50 t _helpersecret
# U _puts
# U dyld_stub_binder
# T = resolvable, t = will not resolve
# Proof in python:
import ctypes
libhelper = ctypes.CDLL('libhelper.dylib')
# >>> libhelper
# <CDLL 'libhelper.dylib', handle 7f8c93c043a0 at 0x7f8c980b4b50>
# >>> libhelper.helper
# <_FuncPtr object at 0x7f8cb814f040>
# >>> libhelper.helper.restype = None
# >>> libhelper.helper()
# helper
# >>> libhelper.helpersecret
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ctypes/__init__.py", line 386, in __getattr__
# func = self.__getitem__(name)
# File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ctypes/__init__.py", line 391, in __getitem__
# func = self._FuncPtr((name_or_ordinal, self))
# AttributeError: dlsym(0x7f8c93c043a0, helpersecret): symbol not found
#!/usr/bin/env python3
import subprocess
import ctypes
# dylib we want to work with
lib_path = 'libhelper.dylib'
# grab raw symbol map for native architecture
raw_symbols = subprocess.check_output(['/usr/bin/nm', lib_path]).decode('utf-8').rstrip().split('\n')
# filter nm output for one known resolvable symbol and the desired to calculate offsets
known_public_symbol = '_helper'
desired_symbol = '_helpersecret'
filter_names = [known_public_symbol, desired_symbol]
filtered_symbols = [x.split(' ', 3) for x in raw_symbols if x.split(' ', 3)[-1] in filter_names]
syms = dict()
for x in filtered_symbols:
syms[x[-1]] = int('0x'+x[0], 0)
# calculate the offset
offset = syms[desired_symbol] - syms[known_public_symbol]
# load the library
lib_obj = ctypes.CDLL(lib_path)
# get the address of the public symbol (we strip off the leading '_' for the real name)
public_addr = ctypes.cast(lib_obj[known_public_symbol[1:]], ctypes.c_void_p).value
private_addr = public_addr + offset
# create a C function connection from a raw address
# The arguments to CFUNCTYPE are: restype, argtype1 (ex: ctypes.c_int, etc.), argtype2, etc.
# helpersecret has a void return type (so restype = None) and no args (don't provide anything)
priv_func = ctypes.CFUNCTYPE(None)(private_addr)
# >>> priv_func
# <CFunctionType object at 0x7f8c98225c40>
# >>> priv_func()
# you called a secret
# helper
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment