.so linux下的注入:如何定位dlopen()的地址?
最近我对Linux产生了兴趣,我正在尝试创建一个能够注入共享对象的程序(例如,so文件、“动态可加载库”和Windows下的“DLL”)。我知道这可以通过设置环境变量来实现,但我想在已经运行的进程上实现 我已经知道如何在Windows下执行此操作。有几种方法,但一般来说,您可以通过使用CreateRemoteThread()创建远程线程来调用LoadLibrary()。当然,在远程进程中需要LoadLibrary的地址,但是(以我的经验)每个进程的偏移量总是相同的 我做了一些关于如何在Linux下实现这一点的研究。例如,Phrack 59中的一个有趣的短语显示了如何实现这一点。本文还附带了一个源代码,但由于对目标进程进行了一些假设,并且它是32位的,所以我无法让它工作。我遇到的其他事情:,但这一个只解释了如何从gdb内部实现它。(我会发布更多链接,但该网站将我限制在2:-/) 首先,我想获取远程进程中dlopen()函数的地址。为此,我想我必须获得进程的ELF头并遍历符号表。事实上,我通过以下方式做到了这一点: 1) 获取ELF头(根据我的经验,存储在0x400000的64位以下。) 2) 在标记为动态的程序标题中查找全局偏移表 3) 通过访问全局偏移表中的第二个条目来检索第一个链接映射 4) 迭代链接映射链的动态部分,从而获得字符串表、符号表和哈希表的地址(*哈希表+0x4保存符号表中的条目数。) 5) 循环遍历符号表 我的程序的一些示例输出:.so linux下的注入:如何定位dlopen()的地址?,linux,shared-libraries,elf,dll-injection,Linux,Shared Libraries,Elf,Dll Injection,最近我对Linux产生了兴趣,我正在尝试创建一个能够注入共享对象的程序(例如,so文件、“动态可加载库”和Windows下的“DLL”)。我知道这可以通过设置环境变量来实现,但我想在已经运行的进程上实现 我已经知道如何在Windows下执行此操作。有几种方法,但一般来说,您可以通过使用CreateRemoteThread()创建远程线程来调用LoadLibrary()。当然,在远程进程中需要LoadLibrary的地址,但是(以我的经验)每个进程的偏移量总是相同的 我做了一些关于如何在Linux
** looking at lib "" **
Trying to find symbol main in symbol table... numentries: 49
index 1 name: val: 0
...
index 49 name: memcpy val: 0
symbol not found.
** looking at lib "" **
Trying to find symbol main in symbol table... numentries: 11
index 1 name: val: 0
...
index 11 name: __vdso_time val: 0xffffffffff700a80
symbol not found.
** looking at lib "/lib/x86_64-linux-gnu/libc.so.6" **
Trying to find symbol main in symbol table... numentries: 2190
index 1 name: val: 0
...
index 2190 name: wcpcpy val: 0xa3570
symbol not found.
但是,我无法找到dlopen的有效地址!(或者甚至是main的地址!)出于测试目的,我让程序分析自己,所以我确信main存在。我还尝试了readelf-s来查看符号表,它显示:
Symbol table '.symtab' contains 151 entries:
Num: Value Size Type Bind Vis Ndx Name
...
149: 0000000000401880 216 FUNC GLOBAL DEFAULT 13 main
所以,不知怎的,readelf找到了main,而我找不到。我还查看了libelf库,但这依赖于从应用程序文件读取,而不是访问进程的内存(即进程运行时不能使用它)。有人知道我如何在远程进程,甚至main中找到dlopen()吗
我正在运行Ubuntu 12.04 64位。首先,关于main的地址: 似乎必须使用节标题才能找到main的地址。仅仅使用动态部分似乎是不可能的。运行
readelf-D-s
和readelf-D--dyn sym
也不会给出main的地址
现在,关于查找dlopen
的地址。结果是我从哈希表中读取了错误数量的符号表条目。有两种类型的哈希表(我到目前为止遇到过):DT_hash
表和DT_GNU_hash
表。前者在hash\u table\u addr+4
()处有条目数,后者没有明确指定hash表的数量。我们需要通过遍历哈希表的bucket表来获得这个数量。除此之外,我的方法很好,现在我能够找到dlopen
,malloc
等的地址
要从哈希表中获取符号表的条目数(即大小),可以使用(C):
ssize\u t ReadData(int-pid、void*缓冲区、const-void*源、ssize\t大小)
{
//在Ubuntu和其他具有“强化内核”的发行版下,进程使用此函数
//应该以root用户身份运行。
//看https://wiki.ubuntu.com/SecurityTeam/Roadmap/KernelHardening#ptrace_Protection
iovec本地_vec;
local_vec.iov_base=缓冲区;
local_vec.iov_len=大小;
iovec远程通信;
远程_vec.iov_base=地址;
remote_vec.iov_len=大小;
返回进程\u vm\u readv(pid、本地\u vec、1和远程\u vec、1、0);
}
无符号长FindNumEntriesHashTable(int-pid、void*TablePtr、const-void*TableLibAddr)
{
//检查TablePtr是否小于0。
无符号长指针=((长)TablePtr<0)?(无符号长)TablePtr+(无符号长)TableLibAddr:(无符号长)TablePtr;
无符号长ret=0;
读取数据(pid和ret,(void*)(指针+sizeof(Elf_字)),sizeof(Elf_字));
返回ret;
}
无符号长FindNumEntriesGnuHashTable(int-pid,void*TablePtr,const-remote\u voidptr TableLibAddr)
{
无符号长指针=((长)TablePtr<0)?(无符号长)TablePtr+(无符号长)TableLibAddr:(无符号长)TablePtr;
//读入gnu_哈希表上的必需信息
无符号长nbuckets=0;
无符号长symndx=0;
无符号长maskwords=0;
读取数据(pid和nbuckets,(常量远程无效PTR)指针,大小(Elf单词));
读取数据(pid和SYMMDX,(常量远程无效PTR)(指针+大小of(Elf_字)),大小of(Elf_字));
读取数据(pid和maskwords,(常量远程无效)(指针+2*sizeof(Elf_字)),sizeof(Elf_字));
//计算到bucket表的偏移量。maskwords条目的大小在32位下为4,在64位下为8。
无符号长masktab_size=(ENV_NUMBITS==32)?4*maskwords:8*maskwords;
无符号长巴克塔布=4*sizeof(Elf字)+马斯克塔布尺寸;
//读入桶表
Elf_单词buckettab[nbuckets];
读取数据(pid和buckettab,(常量远程无效)(指针+buckettab关闭),nbuckets*sizeof(Elf单词));
//循环遍历bucket表。如果给定索引大于已知索引,则更新。
无符号长num_项=0;
对于(大小i=0;inum|u条目数)
{
ssize_t ReadData(int pid, void* buffer, const void* source, ssize_t size)
{
// Under Ubuntu and other distros with a 'hardened kernel', processes using this function
// should be run as root.
// See https://wiki.ubuntu.com/SecurityTeam/Roadmap/KernelHardening#ptrace_Protection
iovec local_vec;
local_vec.iov_base = Buffer;
local_vec.iov_len = Size;
iovec remote_vec;
remote_vec.iov_base = Address;
remote_vec.iov_len = Size;
return process_vm_readv(pid, &local_vec, 1, &remote_vec, 1, 0);
}
unsigned long FindNumEntriesHashTable(int pid, void* TablePtr, const void* TableLibAddr)
{
// Check if TablePtr is smaller than 0.
unsigned long pointer = ((long)TablePtr < 0) ? (unsigned long)TablePtr + (unsigned long)TableLibAddr : (unsigned long)TablePtr;
unsigned long ret = 0;
ReadData(pid, &ret, (void*)(pointer + sizeof(Elf_Word)), sizeof(Elf_Word));
return ret;
}
unsigned long FindNumEntriesGnuHashTable(int pid, void *TablePtr, const remote_voidptr TableLibAddr)
{
unsigned long pointer = ((long)TablePtr < 0) ? (unsigned long)TablePtr + (unsigned long)TableLibAddr : (unsigned long)TablePtr;
// Read in required info on the gnu_hash table
unsigned long nbuckets = 0;
unsigned long symndx = 0;
unsigned long maskwords = 0;
ReadData(pid, &nbuckets, (const remote_voidptr)pointer, sizeof(Elf_Word));
ReadData(pid, &symndx, (const remote_voidptr)(pointer + sizeof(Elf_Word)), sizeof(Elf_Word));
ReadData(pid, &maskwords, (const remote_voidptr)(pointer + 2 * sizeof(Elf_Word)), sizeof(Elf_Word));
// Calculate the offset to the bucket table. The size of the maskwords entries is 4 under 32 bit, 8 under 64 bit.
unsigned long masktab_size = (ENV_NUMBITS == 32) ? 4 * maskwords : 8 * maskwords;
unsigned long buckettab_offs = 4 * sizeof(Elf_Word) + masktab_size;
// Read in the bucket table
Elf_Word buckettab[nbuckets];
ReadData(pid, &buckettab, (const remote_voidptr)(pointer + buckettab_offs), nbuckets * sizeof(Elf_Word));
// Loop through the bucket table. If the given index is larger than the already known index, update.
unsigned long num_entries = 0;
for (size_t i = 0; i < nbuckets; i++)
{
if (num_entries == 0 || buckettab[i] > num_entries)
{
num_entries = buckettab[i];
}
}
if (num_entries == 0)
{
return 0;
}
// Add one, since the first entry is always NULL.
return num_entries++;
}
dlopen_doit (void *a)
{
struct dlopen_args *args = (struct dlopen_args *) a;
if (args->mode & ~(RTLD_BINDING_MASK | RTLD_NOLOAD | RTLD_DEEPBIND
| RTLD_GLOBAL | RTLD_LOCAL | RTLD_NODELETE
| __RTLD_SPROF))
GLRO(dl_signal_error) (0, NULL, NULL, _("invalid mode parameter"));
args->new = GLRO(dl_open) (args->file ?: "", args->mode | __RTLD_DLOPEN,
args->caller,
args->file == NULL ? LM_ID_BASE : NS,
__dlfcn_argc, __dlfcn_argv, __environ);
}
# define GLRO(name) _rtld_local_ro._##name
struct rtld_global_ro {
[...]
void *(*_dl_open) (const char *file, int mode, const void *caller_dlopen,
Lmid_t nsid, int argc, char *argv[], char *env[]);
[...]
};