Createremotethread in Linux

Equivalent of VirtualProtectEx/CreateRemoteThread in Linux?

maybe take a look here: CreateRemoteThread in Linux

I don't know of a simpler way than described there. On Windows you have this
fancy API like VirtualProtectEx. On Linux you'd be writing a .so which e.g. executes pthread_create
in a __attribute__((constructor)) function. Then you'd load that .so via the LD_PRELOAD mechanism.

The next best thing to CreateRemoteThread would be manipulating the main thread
of the process with the ptrace API. But this would involve

  1. Holding a thread
  2. Saving its context
  3. Setting arguments for pthread_create
  4. Set IP to pthread_create and execute
  5. Restore the old context.

I think manipulating the memory access rights would also involve calling mprotect from a process context. As already mentioned above, the simplest way
to do that would not be using ptrace but using a precompiled shared object.

.so injection under linux: how to locate address of dlopen()?

First, regarding the address of main:
It seems one would have to use the Section Headers to find out the address of main. Doing it just using the dynamic section seems not possible. Running readelf -D -s and readelf -D --dyn-sym does not give the address of main either.

Now, regarding finding the address of dlopen. It turns out I was reading the wrong number of symbolic table entries from the hash tables. There are two types of hash tables (I have encountered so far): DT_HASH tables, and DT_GNU_HASH tables. The former have the amount of entries at hash_table_addr + 4 (source), the latter do not specify the amount of hash tables explicitely. One needs to obtain this amount by iterating through the hash table's bucket table. Other than that, my approach was good, and now I am able to find the address of dlopen, malloc, etc.

To obtain the number of entries (i.e. size of) a Symbol Table from a Hash Table, one can use (C):

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++;
}

Injecting only function and running it through CreateRemoteThread? c++

Hmm I can't see what exactly is wrong in your code.

The fact you get a JMP instead of the code of your function is probably due to incremental linking.

You shouldn't need to call VirtualProtect since you already have the right page protection flags.

A possible way to get your function is to set it into another section of your executable and get it at runtime.

Here's a simple example, error checking is minimal:

// Test.cpp : Defines the entry point for the console application.
//


#include <stdio.h>
#include <Windows.h>

#define SECTION_NAME ".my_func"

// put the function in its dedicated section
#pragma code_seg (push)
#pragma code_seg(SECTION_NAME)
static int foo = 0;
void doubleValue(int* pointer){
*pointer *= 2;
}
#pragma code_seg (pop)


typedef struct _tag_SECTION_INFORMATION {
// virtual address of the section
void* section_address;
// raw size of the section
DWORD section_size;
} SECTION_INFORMATION;

// given the name of a section, gets information from it.
BOOL GetSectionInformation(char* sec_name, SECTION_INFORMATION* sec_info){
char current_sec_name[IMAGE_SIZEOF_SHORT_NAME];
BOOL bresult = FALSE;

HMODULE hMyself = GetModuleHandle(NULL);
IMAGE_DOS_HEADER* pidh = (IMAGE_DOS_HEADER*)hMyself;
IMAGE_NT_HEADERS* pinh = (IMAGE_NT_HEADERS*)((ULONG_PTR)(hMyself)+pidh->e_lfanew);
WORD numsec = pinh->FileHeader.NumberOfSections;
IMAGE_SECTION_HEADER* pish = IMAGE_FIRST_SECTION(pinh);
for (WORD i = 0; i < numsec; ++i){
memcpy_s(current_sec_name, IMAGE_SIZEOF_SHORT_NAME, &pish->Name, IMAGE_SIZEOF_SHORT_NAME);
if (memcmp(SECTION_NAME, current_sec_name, IMAGE_SIZEOF_SHORT_NAME) == 0)
{
sec_info->section_address = (void*)((ULONG_PTR)hMyself + pish->VirtualAddress);
sec_info->section_size = pish->SizeOfRawData;
bresult = TRUE;
break;
}

pish++;
}

return bresult;
}

int main(int argc, char *argv[])
{
SECTION_INFORMATION sec_info;

// force compiler to not optimize away the function.
printf("%08lX\n", &doubleValue);

// get VA and size of section where 'doubleValue' resides.
if (!GetSectionInformation(SECTION_NAME, &sec_info)) {
printf("[-] error GetSectionInformation()\n");
return -1;
}

// open this process
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE,
GetCurrentProcessId());
if (!hProcess){
printf("[-] Error: couldn't open process...\n");
return -1;
}

// allocate page, size is the size of the section where 'doubleValue' is.
void* page = VirtualAllocEx(hProcess, NULL, sec_info.section_size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!page){
printf("[-] Error: couldn't allocate remote page.\n");
return -1;
}

DWORD nobw = 0;
if(!WriteProcessMemory(hProcess, page, sec_info.section_address, sec_info.section_size, &nobw) && nobw < sec_info.section_size){
printf("[-] Error: couldn't write to page.\n");
return -1;
}

int i = 42;
printf("parameter: %i\n", i);

// Note: you should obviously *not* pass an address from this address space to another process address space.
// It is fine here to pass the address of 'i' as we are still in the same address space.
if (!CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)page, &i, 0, NULL)){
printf("[-] CreateRemoteThread() failed.\n");
return -1;
}

Sleep(1000);

// should print '84'.
printf("result: %i\n", i);

return 0;
}

edit : same code, but in a remote process, injecting the function in calc.exe, calling the doubleValue() function and reading the result from calc.exe:

#include <stdio.h>
#include <Windows.h>

#define SECTION_NAME ".my_func"

// put the function in its dedicated section
#pragma code_seg (push)
#pragma code_seg(SECTION_NAME)
static int foo = 0;
void doubleValue(int* pointer){
*pointer *= 2;
}
#pragma code_seg (pop)


typedef struct _tag_SECTION_INFORMATION {
// virtual address of the section
void* section_address;
// raw size of the section
DWORD section_size;
} SECTION_INFORMATION;

// given the name of a section, gets information from it.
BOOL GetSectionInformation(char* sec_name, SECTION_INFORMATION* sec_info){
char current_sec_name[IMAGE_SIZEOF_SHORT_NAME];
BOOL bresult = FALSE;

HMODULE hMyself = GetModuleHandle(NULL);
IMAGE_DOS_HEADER* pidh = (IMAGE_DOS_HEADER*)hMyself;
IMAGE_NT_HEADERS* pinh = (IMAGE_NT_HEADERS*)((ULONG_PTR)(hMyself)+pidh->e_lfanew);
WORD numsec = pinh->FileHeader.NumberOfSections;
IMAGE_SECTION_HEADER* pish = IMAGE_FIRST_SECTION(pinh);
for (WORD i = 0; i < numsec; ++i){
memcpy_s(current_sec_name, IMAGE_SIZEOF_SHORT_NAME, &pish->Name, IMAGE_SIZEOF_SHORT_NAME);
if (memcmp(SECTION_NAME, current_sec_name, IMAGE_SIZEOF_SHORT_NAME) == 0)
{
sec_info->section_address = (void*)((ULONG_PTR)hMyself + pish->VirtualAddress);
sec_info->section_size = pish->SizeOfRawData;
bresult = TRUE;
break;
}

pish++;
}

return bresult;
}

int main(int argc, char *argv[])
{
SECTION_INFORMATION sec_info;

// force compiler to not optimize away the function.
printf("%08lX\n", &doubleValue);

// get VA and size of section where 'doubleValue' resides.
if (!GetSectionInformation(SECTION_NAME, &sec_info)) {
printf("[-] error GetSectionInformation()\n");
return -1;
}

PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { 0 };
si.cb = sizeof(STARTUPINFO);
if (!CreateProcessW(L"c:\\windows\\system32\\calc.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){
printf("[-] Error: couldn't create process.\n");
return -1;
}

// open this process
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE,
pi.dwProcessId);
if (!hProcess){
printf("[-] Error: couldn't open process...\n");
return -1;
}

// allocate page for function code, size is the size of the section where 'doubleValue' is.
void* page_function = VirtualAllocEx(hProcess, NULL, sec_info.section_size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!page_function){
printf("[-] Error: couldn't allocate remote page for function code.\n");
return -1;
}

// write function code to remote process.
DWORD nobw = 0;
if (!WriteProcessMemory(hProcess, page_function, sec_info.section_address, sec_info.section_size, &nobw) && nobw < sec_info.section_size){
printf("[-] Error: couldn't write to code page.\n");
return -1;
}

// page for result
void* page_result = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!page_result){
printf("[-] Error: couldn't allocate remote page for result.\n");
return -1;
}

int i = 42;
printf("parameter: %i\n", i);
// write parameter to remote process.
nobw = 0;
if (!WriteProcessMemory(hProcess, page_result, &i, sizeof(i), &nobw) && nobw < sizeof(i)){
printf("[-] Error: couldn't write to result page.\n");
return -1;
}

// call remote doubleValue() function, passing the address where the argument lies (42)
if (!CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)page_function, page_result, 0, NULL)){
printf("[-] CreateRemoteThread() failed.\n");
return -1;
}

Sleep(1000);

// now read result in remote process, should be: 84.
DWORD nobr;
if (!ReadProcessMemory(hProcess, page_result, &i, sizeof(i), &nobr)){
printf("[-] ReadProcessMemory failed.\n");
return -1;
}

// should print '84'.
printf("result: %i\n", i);

return 0;
}


Related Topics



Leave a reply



Submit