Jak uruchomić funkcję w postaci kodu maszynowego?

Odpowiedz Nowy wątek
2019-04-14 10:35
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();
edytowany 1x, ostatnio: Roman Kwaśniewski, 2019-04-14 11:14

Pozostało 580 znaków

2019-04-14 11:18
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


loop:
push 0FFFFFFFFh
call Sleep
jmp loop
edytowany 2x, ostatnio: hit02, 2019-04-14 11:23
Moze nie tyle blad co linker juz swoje podzialal - stivens 2019-04-14 11:40
Nie wiem jakich flag używa ideone.com , ale u mnie kod nie działa. Kompiluje się, ale nie działa. - Roman Kwaśniewski 2019-04-14 11:41

Pozostało 580 znaków

2019-04-14 11:36
0

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

edytowany 1x, ostatnio: stivens, 2019-04-14 11:37

Pozostało 580 znaków

2019-04-14 11:50
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


loop:
push 0FFFFFFFFh
call Sleep
jmp loop
edytowany 2x, ostatnio: hit02, 2019-04-14 11:50

Pozostało 580 znaków

2019-04-14 12:00
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.

Pozostało 580 znaków

2019-04-14 13:34
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

Pozostało 580 znaków

2019-04-14 15:36
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ć.


Na PW przyjmuje tylko (ciekawe!) zlecenia. Masz problem? Pisz na forum, nie do mnie.
edytowany 2x, ostatnio: Shalom, 2019-04-14 15:39

Pozostało 580 znaków

2019-04-14 18:02

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?

@woki nie no bez żartów, nie musisz ręcznie robić tam mprotect jakiegoś, wystarczy odpowiedni parametr dla kompilatora żeby wyłączyć Data execution prevention pod windowsem. Google mówi że to /NXCOMPAT:NO - Shalom 2019-04-14 18:27
Nie wiem jak u Ciebie. Sourcowo testowałem na wine. Takie zabawy, u mnie, na wine nie przechodzą. - woki 2019-04-14 18:41
Możliwe że wine gryzie się trochę z wyłączeniem tych zabezpieczeń ;] - Shalom 2019-04-14 19:10

Pozostało 580 znaków

2019-04-15 17:08
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

Zobacz jak to robią kompilatory. g++ -S program.cpp. - Delor 2019-04-15 18:41
jest jeszcze przekazywanie przez rejestry (jak masz w C cos z sygnatura ... (int n) to powinno byc w %edi - stivens 2019-04-15 18:44

Pozostało 580 znaków

2019-04-15 18:24
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ć ;)


loop:
push 0FFFFFFFFh
call Sleep
jmp loop
edytowany 1x, ostatnio: hit02, 2019-04-15 18:24

Pozostało 580 znaków

2019-04-15 18:54
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


Na PW przyjmuje tylko (ciekawe!) zlecenia. Masz problem? Pisz na forum, nie do mnie.
edytowany 1x, ostatnio: Shalom, 2019-04-15 18:54

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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