Jak uruchomić funkcję w postaci kodu maszynowego?

0

Czy istnieje jakiś sposób na wykonanie funkcji w postaci kodu maszynowego takiego jak ten poniżej, który pochodzi z Wikipedii i ma obliczać n-ty wyraz ciągu Fibonacciego?
8B542408 83FA0077 06B80000 0000C383 FA027706 B8010000 00C353BB 01000000 C9010000 008D0419 83FA0376 078BD98B B84AEBF1 5BC3

Czy jeżeliby przedstawić ten kod w odpowiedniej postaci i ustawić na niego wskaźnik to działałoby to jak zwykła funkcja? tzn. czy taki kod ma prawo działać:

auto kod = kod maszynowy w odpowiedniej postaci;
int (*moja_funkcja)(int) = reinterpret_cast<int(*)(int)>( &kod );
moja_funkcja();
1

Generalnie chcesz mieć swój hex w postaci binarnej. To znaczy możesz to zrobić tak:

#include <stdio.h>

int main()
{
	int(*fun_ptr)(void) = ((int(*)())("\xB8\x0C\x00\x00\x00\xC3"));
	printf("%i", fun_ptr());
	return 0;
}

Funkcja zapisuje 12, do EAX i wychodzi
Co do twojej funkcji, to mam wątpliwości, czy to ruszy.
https://onlinedisassembler.com/odaweb/RMjggwwo/0
Offset 0x2F, co to za odwołanie do 0x5BF1EB4A? Wygląda, jak kod zależny od pozycji, albo po prostu błąd

Poza tym kompilator visual studio nie łyka takiej konwersji stringa do funkcji ;) Może jest jakiś trick, żeby to łyknął, ale generalnie pod gcc to ładzia
https://ideone.com/TnIA4H#cmperr

Tak swoją drogą. Nie łatwiej było by po prostu zrobić wstawkę assembly? :D

0

Mozesz w C dac sygnature funkcji a definicje w assemblerze i skompilowac to razem

0

U mnie działa :D
u_mnie_dziala.png
@stivens Faktycznie pewnie linker zadziałał. Miałem na myśli błąd na wikipedi :P

0

Żródło kodu maszynowego: Wikipedia

#include <iostream>

const char bytes[] = {
    0x85, 0x54, 0x24, 0x08,
    0x83, 0xfa, 0x00, 0x77,
    0x06, 0xb8, 0x00, 0x00,
    0x00, 0x00, 0xc3, 0x83,
    0xfa, 0x02, 0x77, 0x06,
    0xb8, 0x01, 0x00, 0x00,
    0x00, 0xc3, 0x53, 0xbb,
    0x01, 0x00, 0x00, 0x00,
    0xc9, 0x01, 0x00, 0x00,
    0x00, 0x8d, 0x04, 0x19,
    0x83, 0xfa, 0x03, 0x76,
    0x07, 0x8b, 0xd9, 0x8b,
    0xb8, 0x4a, 0xeb, 0xf1,
    0x5b, 0xc3
};


int main() {
    int n;
    std::cin >> n;
    int (*fib)(int) = reinterpret_cast<int(*)(int)>(bytes);

    int result = fib(n);
    std::cout << result << std::endl;
    return 0;
}

Pod współczesnym systemem operacyjnym (Fedora 28, g++ (gcc 8.3.1)): Wynikowy program się nie wysypuje, ale zawsze zwraca 0.
Skompilowanie pod starszym oprogramowaniem (CentOS 6.10, g++ (gcc 4.4.7)): Program ma naruszenie ochrony pamięci przy argumentach większych niż 2 - prawdopodobnie przez rekurencyjne wywołania.
Źródło było o tyle ważne, że w Wikipedii masz wyraźnie zaznaczone, że kod był 32 bitowy, zatem w obu przypadkach kompilowałem binarki pod architekturę i686, a nie x86_64, tak jaką mam w komputerze.

0
hit02 napisał(a):

Generalnie chcesz mieć swój hex w postaci binarnej. To znaczy możesz to zrobić tak:

#include <stdio.h>

int main()
{
	int(*fun_ptr)(void) = ((int(*)())("\xB8\x0C\x00\x00\x00\xC3"));
	printf("%i", fun_ptr());
	return 0;
}

Funkcja zapisuje 12, do EAX i wychodzi
Co do twojej funkcji, to mam wątpliwości, czy to ruszy.
https://onlinedisassembler.com/odaweb/RMjggwwo/0
Offset 0x2F, co to za odwołanie do 0x5BF1EB4A? Wygląda, jak kod zależny od pozycji, albo po prostu błąd

Poza tym kompilator visual studio nie łyka takiej konwersji stringa do funkcji ;) Może jest jakiś trick, żeby to łyknął, ale generalnie pod gcc to ładzia
https://ideone.com/TnIA4H#cmperr

Tak swoją drogą. Nie łatwiej było by po prostu zrobić wstawkę assembly? :D

Po uruchomieniu twojego kodu dostaję Segmentation fault

5

To już nie te czasy żeby wykonać dane jako kod ;) Od dawna domyślnie gcc nie pozwala na takie cuda.
gcc -fno-stack-protector -z execstack
Plus pamiętaj że shellcode jest pod specyficzną architekturę więc jeśli to shellcode 32bit to wrzuć go do binarki 32bit.
Nie wiem czym to kompilujesz ale musisz wyłączyć NX (linux) albo DEP (windows) żeby móc coś takiego uruchomić.

1

Przykład dotyczy windowsa, testowałem go na wine oraz windows 7. Identyczne działanie można uzyskać na linuxie, lecz kod będzie wyglądał inaczej.

#include <windows.h>

void print_string(char *string){
	HANDLE screen = GetStdHandle(STD_OUTPUT_HANDLE);
	DWORD count_written;
	WriteConsole(screen, string,strlen(string),&count_written,NULL);
}

void print_int(int value){
	char str_number[20];
	_itoa(value,str_number,10);
	print_string((char *)str_number);
}


int main(){
  unsigned char bytes[] = {
    0x55,
    0x89, 0xe5,
    0x83, 0xec, 0x10,
    0x8b, 0x55, 0x08,
    0x8b, 0x45, 0x0c,
    0x01, 0xd0,
    0x89, 0x45, 0xfc,
    0x8b, 0x45, 0xfc,
    0xc9,
    0xc3
  };

    HANDLE mem_handle = CreateFileMappingA( INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0,  sizeof(bytes), NULL);
    void* mem_map = MapViewOfFile( mem_handle, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE, 0x0, 0x0, sizeof(bytes));


    memcpy(mem_map, bytes, sizeof(bytes));
	print_string("Wartość argumentu to:\n");

    int result = (( int(*)(int,int) )mem_map)(20,13);
	print_int(result);
	print_string("\n");

  	return 0;
}

Wywołuję tutaj binaria funkcji która wygląda tak:

int function(int a,int b){
  int c = a+b;
  return c;
}

Wyciągnięte z objdumpa (komilowane na 32 bity bez optymalizacji "-O0"):
Czyli to co masz w tablicy [unsigned char bytes].

00401570 <_function>:
  401570:   55                      push   %ebp
  401571:   89 e5                   mov    %esp,%ebp
  401573:   83 ec 10                sub    $0x10,%esp
  401576:   8b 55 08                mov    0x8(%ebp),%edx
  401579:   8b 45 0c                mov    0xc(%ebp),%eax
  40157c:   01 d0                   add    %edx,%eax
  40157e:   89 45 fc                mov    %eax,-0x4(%ebp)
  401581:   8b 45 fc                mov    -0x4(%ebp),%eax
  401584:   c9                      leave
  401585:   c3                      ret

Jak użytkownicy forum, zauważyli, musisz sobie zadać jedno bardzo ważne pytanie.
O ile w ogóle ten przykład będzie działał u Ciebie to czy jest sens?

0

Sposób podany przez @woki działa, zastanawiam się jednak jak przekazać do wywoływanej funkcji jakieś argumenty. Czy jest to w ogóle możliwe? Wikipedia sugeruje, że w większości języków argumenty są przekazywane do funkcji przez stos. Napisałem więc taką funkcję:

pop eax
ret

Niestety nie zwracała ona przekazywanej do niej wartości, a jedynie coś losowego. Przypuszczam więc, że w c++ argumenty przekazywane są do funkcji po prostu przez ich adres w pamięci, w takim wypadku jednak odszukanie ich byłoby trochę problematyczne... niemożliwe

0

Pobrałeś najpewniej adres powrotu. Poczytaj, jak taka ramka stosu jest zbudowana. W skrócie do argumentów na 32bitowej maszynie robisz tak:

push ebp ;zapis adresu poprzedniej ramki
mov ebp, esp ; adres obecnej ramki teraz jest w ebp
sub esp, <liczba zmiennych lokalnych * wielkości w bajtach> ; alokacja stosu na zmienne lokalne
mov eax, [ebp + 8] ; pobranie pierwszego argumentu

Potem oczywiście taką ramkę trzeba posprzątać ;)

1

zastanawiam się jednak jak przekazać do wywoływanej funkcji jakieś argumenty. Czy jest to w ogóle możliwe?

oczywiście, przecież jakoś się to dzieje!

w większości języków argumenty są przekazywane do funkcji przez stos

To jakaś bzdura. Po pierwsze nie zależy to aż tak bardzo od samego języka co raczej od kompilatora. Po drugie konwencji wywołań funkcji jest sporo i zależą od architektury CPU ale też np. od liczby argumentów -> https://en.wikipedia.org/wiki/X86_calling_conventions

0

compiler explorer ( https://godbolt.org/ ) rozłożył mi funkcję f:

int f(int a){
      return a;
}

na coś takiego:

 push   rbp
 mov    rbp,rsp
 mov    DWORD PTR [rbp-0x4],edi
 mov    eax,DWORD PTR [rbp-0x4]
 pop    rbp
 ret 

co znaczy mniej więcej to samo co to:

mov eax, edi
ret

także wywołanie funkcji f(2); jest tłumaczone jako:

 mov    edi,0x2
 call   4004b2 <f(int)>

po zasemblowaniu, żaden z tych kodów nie zwraca jednak przekazanej do niego wartości tylko jakąś losową wartość, która była w eax.

0

Wszystko się rozchodzi o ABI.
Podglądanie compiler explorer może być zwodnicze, bo w C i C++ obowiązuje zasada "AS IF" i kompilator ma prawo nie przestrzegać ABI jeśli widzi, że funkcja nigdy nie jest widoczna na zewnątrz i/lub nie jest pobierany dla niej adres.
Teraz ABI funkcji którą trzymasz w tej tablicy musi być takie same jak ABI dla wskaźnika do funkcji, do którego to konwertujesz.

Przykładowo MSVC zachowuje sie podobnie, ale argument jest wpisany do innego rejestru: https://godbolt.org/z/WhtNjn
Optymalizacje też zmieniają wygląd tej funkcji: https://godbolt.org/z/cNtG_j

0

Znalazłem rejestr w którym przechowuje 1 argument mój kompilator, jest to ecx

1
auto kod = kod maszynowy w odpowiedniej postaci;
int (*moja_funkcja)(int) = reinterpret_cast<int(*)(int)>( &kod );
moja_funkcja();

To ma prawo działać, ale pod Windows taki wskaźnik trzeba oznaczyć za pomocą VirtualProtect() z flagą PAGE_EXECUTE (albo PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE).
Inaczej może kraszyć (i kiedyś to zrobi...)

https://docs.microsoft.com/en-us/windows/desktop/api/memoryapi/nf-memoryapi-virtualprotect
(zalecam przeczytać do końca)
https://docs.microsoft.com/pl-pl/windows/desktop/Memory/memory-protection-constants

1 użytkowników online, w tym zalogowanych: 0, gości: 1