Sunday, August 2, 2020

Digest for comp.lang.c++@googlegroups.com - 23 updates in 4 topics

Frederick Gotham <cauldwell.thomas@gmail.com>: Aug 01 04:23PM -0700

The following function:
 
void cpystring(char *to, char const *from)
{
while ( *to++ = *from++ );
}
 
gets compiled on a x86_64 Linux machine to:
 
00000000000005ca <_Z9cpystringPcPKc>:
5ca: 55 push %rbp
5cb: 48 89 e5 mov %rsp,%rbp
5ce: 48 89 7d f8 mov %rdi,-0x8(%rbp)
5d2: 48 89 75 f0 mov %rsi,-0x10(%rbp)
5d6: 48 8b 55 f0 mov -0x10(%rbp),%rdx
5da: 48 8d 42 01 lea 0x1(%rdx),%rax
5de: 48 89 45 f0 mov %rax,-0x10(%rbp)
5e2: 48 8b 45 f8 mov -0x8(%rbp),%rax
5e6: 48 8d 48 01 lea 0x1(%rax),%rcx
5ea: 48 89 4d f8 mov %rcx,-0x8(%rbp)
5ee: 0f b6 12 movzbl (%rdx),%edx
5f1: 88 10 mov %dl,(%rax)
5f3: 0f b6 00 movzbl (%rax),%eax
5f6: 84 c0 test %al,%al
5f8: 0f 95 c0 setne %al
5fb: 84 c0 test %al,%al
5fd: 74 02 je 601 <_Z9cpystringPcPKc+0x37>
5ff: eb d5 jmp 5d6 <_Z9cpystringPcPKc+0xc>
601: 90 nop
602: 5d pop %rbp
603: c3 retq
 
 
And so here's the first program that allocates the interprocess memory and copies the machine code into it:
 
char unsigned const g_cpystring_bytes[] = {
0x55, 0x48, 0x89, 0xE5, 0x48, 0x89, 0x7D, 0xF8, 0x48, 0x89, 0x75, 0xF0, 0x48, 0x8B, 0x55, 0xF0,
0x48, 0x8D, 0x42, 0x01, 0x48, 0x89, 0x45, 0xF0, 0x48, 0x8B, 0x45, 0xF8, 0x48, 0x8D, 0x48, 0x01,
0x48, 0x89, 0x4D, 0xF8, 0x0F, 0xB6, 0x12, 0x88, 0x10, 0x0F, 0xB6, 0x00, 0x84, 0xC0, 0x0F, 0x95,
0xC0, 0x84, 0xC0, 0x74, 0x02, 0xEB, 0xD5, 0x90, 0x5D, 0xC3
};
 
#include <cstring> // memcpy
#include <boost/interprocess/shared_memory_object.hpp> // shared_memory_object
#include <boost/interprocess/mapped_region.hpp> // mapped_region
 
using namespace boost::interprocess;
 
int main(void)
{
shared_memory_object shm_obj(create_only, //only create
"gotham_cpystring", //name
read_write ); //read-write mode

shm_obj.truncate(sizeof g_cpystring_bytes);

mapped_region region(shm_obj, read_write);

std::memcpy(region.get_address(), g_cpystring_bytes, region.get_size());
}
 
 
This first program compiles fine and seems to do its job properly, the shared memory object is created in "/dev/shm" and it's 58 bytes in size.
 
And then next I have the second program that tries to call the function:
 
#include <boost/interprocess/shared_memory_object.hpp> // shared_memory_object
#include <boost/interprocess/mapped_region.hpp> // mapped_region
 
using namespace boost::interprocess;
 
char buf1[] = "cat",
buf2[] = "dog";
 
//Not sure if I need to specify the calling convention (e.g. sysv_abi)
void (*cpystring)(char *to, char const *from) = nullptr;
 
int main(void)
{
//Open already created shared memory object.
shared_memory_object shm_obj(open_only, "gotham_cpystring", read_write);
 
//Map the whole shared memory in this process
mapped_region region(shm_obj, read_write);
 
cpystring = reinterpret_cast<decltype(cpystring)>(region.get_address());

cpystring(buf1, buf2);
}
 
 
This second program fails with a segfault. I thought the problem might be that the shared memory is in a page that isn't marked for execution, and so I set it as executable as follows:
 
void Set_Writeability_Of_Memory(void *const p, bool const writeable)
{
uintptr_t const page_size = sysconf(_SC_PAGE_SIZE);
 
union {
void *p_start_of_page;
uintptr_t i_start_of_page;
};
 
p_start_of_page = p;
 
i_start_of_page -= (i_start_of_page % page_size);
 
mprotect(i_start_of_page, page_size, PROT_READ | (writeable ? PROT_WRITE : 0u));
}
 
however this hasn't fixed it. I call the function "Set_Writeability_Of_Memory" from within my first program and also from within my second program, but the second program still segfaults.
 
Anyone got any ideas?
 
Here's what I'm getting from the GDB debugger:
 
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7ff7000 in ?? ()
(gdb) bt
#0 0x00007ffff7ff7000 in ?? ()
#1 0x0000555555555366 in main () at drone.cpp:26
Frederick Gotham <cauldwell.thomas@gmail.com>: Aug 01 04:44PM -0700

On Sunday, August 2, 2020 at 12:23:39 AM UTC+1, Frederick Gotham wrote:
 
> void Set_Writeability_Of_Memory(void *const p, bool const writeable)
 
 
I meant to set the page as 'executable' -- not as 'writeable'.
 
I have re-written my second program and everything is working now. Here's the second program:
 
void Set_Executability_Of_Memory(void*, bool);
 
#include <cstring> // memcpy
 
#include <iostream>
using std::cout;
using std::endl;
 
#include <boost/interprocess/shared_memory_object.hpp> // shared_memory_object
#include <boost/interprocess/mapped_region.hpp> // mapped_region
 
using namespace boost::interprocess;
 
char buf1[] = "cat";
char buf2[] = "dog";
 
void (*cpystring)(char *to, char const *from) = nullptr;
 
int main(void)
{
//Open already created shared memory object.
shared_memory_object shm_obj(open_only, "gotham_cpystring", read_write);
 
//Map the whole shared memory in this process
mapped_region region(shm_obj, read_write);
 
cout << "About to set memory page as executable" << endl;
Set_Executability_Of_Memory(region.get_address(), true);

cout << "About to call cpystring" << endl;
cpystring = reinterpret_cast< decltype(cpystring) >( region.get_address() );

cpystring(buf1, buf2);

cout << "buf1 : " << buf1 << endl;
cout << "buf2 : " << buf2 << endl;
}
 
void Set_Executability_Of_Memory(void *const p, bool const executable)
{
uintptr_t const page_size = sysconf( _SC_PAGE_SIZE );
 
union {
void *p_start_of_page;
uintptr_t i_start_of_page;
};
 
p_start_of_page = p;
 
i_start_of_page -= (i_start_of_page % page_size);

mprotect(p_start_of_page, page_size, PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0u));
}
 
 
Of course it will be cleaner to put the call to "Set_Exec..." in the first program.
 
It's late now but tomorrow I'll play around with calling other functions from inside "cpystring".
 
When I have this working the way I want to, I'll be able to unload all libraries and just leave some phantom code in RAM for doing some really strange things (for example I can leave a stub function in memory when I unload a library).
Manfred <noname@add.invalid>: Aug 02 04:29PM +0200

On 8/1/2020 10:57 PM, Frederick Gotham wrote:
> (1) When building, at the linking stage just specify -l:libmonkey.so
> (2) At runtime, call dlopen, LoadLibrary followed by dlsym, GetProcAddress
> (3) Copy executable code into a shared memory object, and then just get other processes to map the shared memory and call the function
 
What you are trying to do here is replace the functionality of the
dynamic linker within your program.
None of this is specified by C++ (or C) as a language (i.e. its
standard), in fact it is about the platform ABI, out of the scope of the
language.
C++ (and C) do specify some linkage properties, but they are all high
level requirements, nothing anywhere near the level required to execute
some actual code.
 
This is to say that you won't get answers to what you are trying to do
from the language, as a start you need to study the ABI of your target
platform.
Second, you should consider the compiled version of your cpystring
function as a flat bytearray that happens to contain executable code;
you shouldn't rely on how your 4 lines of code get compiled by a C or
C++ compiler - for example you can't even rely on the fact that the
entry point of the function will be the start address of the compiled
bytearray. You may get closer to what you want using assembly or even
machine code (and if you use assembly you should inspect closely your
assembler to verify what its output is).
Third, later on you rely on a blind reinterpret_cast in the hope you'll
get the right call. This is not enough, for example on Windows there are
at least 3 different calling conventions used by the compiler within a
single program for a single architecture. This is why even for
conventional library calls their header files thoroughly decorate each
function prototype to ensure that the correct ABI convention is enforced
by the compiler.
And then there's the whole chapter about memory protection.
 
Back to your list above, (1) and (2) should work, if used properly.
I would consider (3) unusable for any serious work - toying is always
free of course.
Paavo Helde <eesnimi@osa.pri.ee>: Aug 02 08:09PM +0300

02.08.2020 02:44 Frederick Gotham kirjutas:
 
> Of course it will be cleaner to put the call to "Set_Exec..." in the first program.
 
> It's late now but tomorrow I'll play around with calling other functions from inside "cpystring".
 
> When I have this working the way I want to, I'll be able to unload all libraries and just leave some phantom code in RAM for doing some really strange things (for example I can leave a stub function in memory when I unload a library).
 
Not quite sure about your motivations, so far it seems you have just
duplicated a small part of dynamic loader functionality. Is this meant
as a stress test for antivirus products, to see how capable they are in
detecting suspicious activities?
Frederick Gotham <cauldwell.thomas@gmail.com>: Aug 02 03:51PM -0700

I've re-written my first program like this:
 
#include <cstring> // memcpy
 
#include <iostream>
using std::cout;
using std::endl;
 
#include <boost/interprocess/shared_memory_object.hpp> // shared_memory_object
#include <boost/interprocess/mapped_region.hpp> // mapped_region
 
void cpystring(char *to, char const *from) __attribute__ ((noinline));
 
using namespace boost::interprocess;
 
int main(void)
{
shared_memory_object::remove("gotham_cpystring");
 
shared_memory_object shm_obj(create_only, //only create
"gotham_cpystring", //name
read_write); //read-write mode
 
shm_obj.truncate(128u); // Averages about 17 - 58 bytes, so let's be safe with 128 bytes
 
mapped_region region(shm_obj, read_write);

std::memcpy(region.get_address(), (void const*)&cpystring, region.get_size());
}
 
void cpystring(char *to, char const *from)
{
while ( *to++ = *from++ );
}
 
 
And I've re-written my second program like this:
 
#include <iostream>
using std::cout;
using std::endl;
 
#include <boost/interprocess/shared_memory_object.hpp> // shared_memory_object
#include <boost/interprocess/mapped_region.hpp> // mapped_region
 
using namespace boost::interprocess;
 
char buf1[] = "cat";
char buf2[] = "dog";
 
void (*cpystring)(char *to, char const *from) = nullptr;
 
int main(void)
{
//Open already created shared memory object.
shared_memory_object shm_obj(open_only, "gotham_cpystring", read_only);
 
//Map the whole shared memory in this process
mapped_region region(shm_obj, read_only);
 
cout << "About to set memory page as executable" << endl;
extern void Set_Executability_Of_Memory(void *const p, bool const executable);
Set_Executability_Of_Memory(region.get_address(), true);
 
cout << "About to call cpystring" << endl;
cpystring = reinterpret_cast< decltype(cpystring) >( region.get_address() );

cpystring(buf1, buf2);

cout << "buf1 : " << buf1 << endl;
cout << "buf2 : " << buf2 << endl;
}
 
#ifdef BOOST_INTERPROCESS_WINDOWS
extern "C" int VirtualProtect(uint64_t,uint64_t,uint32_t,uint32_t*);
 
struct SYSTEM_INFO {
char stuff[4];
uint32_t dwPageSize;
char more_stuff[128];
};
 
extern "C" void GetSystemInfo(uint64_t);

No comments: