Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Simulating a hardware interrupt to edit VTable (Linux signals)

11 views
Skip to first unread message

Frederick Gotham

unread,
Jun 18, 2020, 6:06:56 AM6/18/20
to

This thread is a follow-up to two previous threads:

8th June - Change one function pointer in Vtable

https://groups.google.com/forum/#!topic/comp.lang.c++/q_QY4zNnLJ4

11th June - Working code for runtime Vtable alteration (MS-Windows, Linux)

https://groups.google.com/forum/#!topic/comp.lang.c++/ZV80wcglsNU

This time around I'm using Linux signals to simulate a hardware interrupt so that the original code in 'main' can remain intact.

I start off with the following simple program which prints incrementing numbers to the screen (either in decimal or hexadecimal):


#include <chrono> // milliseconds
#include <thread> // this_thread::sleep_for
#include <iostream> // cout, cin, endl, flush
#include <ios> // dec, hex

using std::cout;
using std::cin;
using std::endl;

struct NumberPrinter {
unsigned i;
virtual void Print(void) = 0;
};

struct DecimalNumberPrinter : NumberPrinter {
void Print(void) override;
};

struct HexadecimalNumberPrinter : NumberPrinter {
void Print(void) override;
};

void DecimalNumberPrinter ::Print(void) { cout << std::dec << i++ << endl; }
void HexadecimalNumberPrinter::Print(void) { cout << "0x" << std::hex << i++ << endl; }

auto main(void) -> int
{
NumberPrinter *p;

cout << "Enter 1 for Decimal, or 2 for Hexadecimal: " << std::flush;

unsigned choice;
cin >> choice;

if ( 2 == choice )
p = new HexadecimalNumberPrinter;
else
p = new DecimalNumberPrinter;

p->i = 0;

for ( ; /* ever */ ; )
{
p->Print();

std::this_thread::sleep_for(std::chrono::milliseconds(100u));
}
}


We can compile this single source file to a full program as follows:

g++ -o prog prog.cpp

We can run it at the command line, select option (1) for Decimal, and then if we hit Ctrl + C, it kills the program.

But instead of compiling it to a full program, we can instead just make an object file:

g++ -c prog.cpp

and so then we will have an object file "prog.o".

Without altering the original source file, I now want to introduce a second source file:


#include <csignal> /* signal */
#include <cstdint> /* see 'using std::' below */
using std::int32_t;
using std::uint32_t;
using std::uint64_t;
using std::uintptr_t;

void Interrupt_Routine(int); /* Defined at the bottom of this file */

// The next line will be executed before 'main'
void (*const executed_before_main)(int) = std::signal(SIGINT, Interrupt_Routine);

struct NumberPrinter {
unsigned i;
virtual void Print(void) = 0;
};

struct DecimalNumberPrinter : NumberPrinter {
void Print(void) override;
};

struct HexadecimalNumberPrinter : NumberPrinter {
void Print(void) override;
};

struct VTable {
void (*funcptr[1u])(void);
};

// The following two lines are required function declarations
extern "C" uint32_t sysconf(int32_t name);
extern "C" int32_t mprotect(uint64_t addr, uint64_t len, int32_t prot);

void Set_Writeability_Of_Memory(void (**const p)(void), bool const writeable)
{
uintptr_t const page_size = sysconf( 30 /*_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, 1u /*PROT_READ*/ | (writeable ? 2u /*PROT_WRITE*/ : 0u));
}

bool Try_Replace_Entry_In_VTable(VTable *const pvtable, void (*const before)(void), void (*const after)(void))
{
unsigned const how_many_pointers_to_try = 5u;

for (unsigned i = 0; i != how_many_pointers_to_try; ++i)
{
if ( before == pvtable->funcptr[i] )
{
Set_Writeability_Of_Memory(&pvtable->funcptr[i], true);
pvtable->funcptr[i] = after;
Set_Writeability_Of_Memory(&pvtable->funcptr[i], false);
return true;
}
}

return false;
}

void Interrupt_Routine(int)
{
static bool alternator = false;

alternator = !alternator;

DecimalNumberPrinter obj; // This object is needed for its vtable pointer

void (*const address_of_decimal_func)(void) = reinterpret_cast<void(*)(void)>(&DecimalNumberPrinter ::Print);
void (*const address_of_hexadecimal_func)(void) = reinterpret_cast<void(*)(void)>(&HexadecimalNumberPrinter::Print);

void (*const before)(void) = (alternator ? address_of_decimal_func : address_of_hexadecimal_func);
void (*const after)(void) = (alternator ? address_of_hexadecimal_func : address_of_decimal_func );

// - - - - - - - - Pointer to vtable might be at the beginning of the object

if ( sizeof(obj) < sizeof(void(*)(void)) )
return;

VTable *pvtable = reinterpret_cast<VTable*>( *reinterpret_cast<void**>(&obj) );

if ( Try_Replace_Entry_In_VTable(pvtable, before, after) )
{
return;
}

// - - - - - - - - Or let's try at the end

if ( sizeof(obj) < sizeof(void(*)(void))+1u )
return;

pvtable = reinterpret_cast<VTable*>( reinterpret_cast<char*>(&obj + 1u) - sizeof(void*) );

// Watch out for unaligned memory access in the next line
if ( Try_Replace_Entry_In_VTable(pvtable, before, after) )
{
return;
}

// - - - - - - - - Or at the end but before the final padding bytes

if ( sizeof(obj) < sizeof(void(*)(void))+2u )
return;

for (unsigned i = 0; i != (sizeof(obj) - 1u - sizeof(void(*)(void))); ++i)
{
pvtable = reinterpret_cast<VTable*>( reinterpret_cast<char*>(pvtable) - 1u );

// Watch out for unaligned memory access in the next line

if ( Try_Replace_Entry_In_VTable(pvtable, before, after) )
{
return;
}
}
}


Now if we compile these two source files to object files:

g++ -c prog.cpp
g++ -c improviser.cpp

We can then create a full program:

g++ -o prog prog.o improviser.o

If we run this program at the command line, select (1) for Decimal, and then hit Ctrl + C, it alternates between decimal and hexadecimal every time we hit Ctrl + C.

What I have achieved here is that I don't have to edit the object files of the original program -- which I realise isn't exactly the same as not having to edit the executable binary of the original program -- but it is certainly a step in the right direction.

Next what I'll have to try do is compile the original program:

g++ -o prog prog.cpp

And then somehow combine this executable binary with another object file (which won't be straight forward because the program's entry point has changed).
0 new messages