Hello world bez bibliotek i asm

2

Czy da radę bez użycia bibliotek oraz wstawek asm (oraz jakich kolwiek innych wstawek) napisać hello world? Przekopałem forum niestety nie znalazłem nic tutaj takiego. Na googlu pokazuję mi jedynie toturiale jak napisać po prostu hello world. Gdy szukałem jakiegoś source code co wyświetla kod na ekran to niestety nie pasowało do wytycznych. Temat powstał bo zaciekawiła mnie wypowiedź **byku_guzio ** z tego tematu (ostatni post)
http://4programmers.net/Forum/C_i_C++/200635-czy_mozna_programowac_w_c++_bez_bibliotek
stwierdziłem, że taki temat jak powstanie to więcej osób skorzysta niż miałoby miejsce w tamtym temacie.

8

Nie sądzę. Co miałoby ten napis wypisać, skoro nie chcesz użyć ani asm ani żadnej funkcji z biblioteki?

Oszustwo:

#pragma message "Hello world"

int main() {
  return 0;
}

(GCC, w MSVC trzeba objąć napis nawiasami zdaje się) :-P

1

Zależy co uznać za 'bibliotekę' - na przykład taki kod (zapisany jako plik *.c) jest kompilowany bez ostrzeżeń przez moją wersję gcc:

int main() {
    printf("Hello World");
}

Czy można powiedzieć że nie korzysta z bibliotek?

Patrząc na ten program inaczej - wygenerowany plik .exe korzysta z dwóch bibliotek - kernel32 i msvcrt. Bez importowania jakichś bibliotek żaden program na windowsie nie jest w stanie legalnie wyświetlić niczego na ekranie.

(Z drugiej strony... Wpadłem na pewien pomysł, zobaczymy co z tego wyjdzie :P Za sekundę napiszę.)

0

nie wiedziałem o tym ze można printfa użyć bez żadnej biblioteki. Myślałem że musi być include stdio.h
Mi c::b pod windowsem z minGW wyrzucił taki warning: incompatible implicit declaration of built-in function 'printf'
Ale Twój post rozwiązuje moje założenia chociaż myślałem jednak o czymś innym :)

0

Można skorzystać z systemowej obsługi niełapanych wyjątków:

struct Zupa_Pomidorowa{};

int main()
{
	throw Zupa_Pomidorowa();
}

Oczywiście komunikat będzie obszerniejszy niż sama nazwa klasy, i będzie się różnić w zależności od systemu/kompilatora.
Dodatkowo wypisuje na 'stderr' zamiast na 'stdout'. Ale taka właśnie jest specyfika "rozwiązań" tego typu :P

5

Pod systemem operacyjnym, który zabrania programom w user-mode bezpośrednich odwołań do urządzeń - a jedną z funkcji nowoczesnego systemu operacyjnego jest właśnie to - nie da się tego zrobić. Wypisanie hello world na konsoli w Windows wymaga skorzystania z czegoś, co wypisze faktyczne literki na tej konsoli - bez użycia bibliotek zwyczajnie się nie obejdzie. Nawet jeżeli użyjesz TIB pod Windows, żeby uzyskać dostęp do kernel32.dll to wciąż jest to biblioteka, która została załadowana do twojego programu (i dzieje się to zawsze, nawet jeżeli tabela importów jest absolutnie pusta - i taki program da się napisać - http://stackoverflow.com/questions/3898716/how-to-build-an-executable-without-import-table-in-c-c).

Nawet jeżeli napiszesz system operacyjny od nowa i będziesz pisał karcie graficznej bezpośrednio po buforze trybu tekstowego to wciąż jest to tylko zmapowany kawałek pamięci. Swojego rodzaju biblioteka w karcie graficznej odpowiednio przetworzy ten kawałek pamięci i wyświetli odpowiednie piksele na ekranie.

Nie ma co się rozwodzić nad takim problemem, lepiej czas przeznaczyć na bardziej pożyteczne kwestie.

17

@Rev - zepsułeś mi zabawę, dokładnie to zrobiłem i chciałem się pochwalić :].

Przy czym na StackOverflow oszukują bo dołączają windows.h - a ja zrobiłem bez używania żadnych lamerskich nagłówków (w końcu o to chyba chodziło autorowi).

Prawdopodobnie najgorszy kod jaki w życiu napisałem. Nie doliczę się hacków i rzeczy które powinny działać a nie muszą...

Dobrej zabawy życzę:

typedef unsigned char uint8_t; 
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned short wchar_t;
typedef void* (*get_std_handle_func)(int handle);
typedef int (*write_console_func)(void*, const void *, int, int*, void*);
typedef void* (*get_proc_address_func)(void*, char*);

const int std_output = -11;
const int data_directory_export = 0;
const void* tib_addr = (void*)0x7efdd000;

struct unicode_string {
    short length;
    short max_length;
    wchar_t *buffer;
};

struct list_entry {
    struct list_entry *flink;
    struct list_entry *blink;
};

struct ldr_data_table_entry {
    struct list_entry modules;
    uint8_t reserved1[0x8];
    uint32_t dll_base;
    uint8_t reserved[0x8];
    struct unicode_string full_name;
};

struct peb_ldr {
    uint8_t reserved[0x14];
    struct list_entry modules;
};

struct peb {
    uint8_t reserved[0xC];
    struct peb_ldr *ldr;
};

struct tib {
    uint8_t reserved[0x30];
    struct peb *peb;
};

struct image_file_header {
    uint8_t reserved[0x14];
};

struct image_data_directory {
    uint32_t virtual_address;
    uint32_t size;
};

struct image_optional_header {
    uint8_t reserved[0x60];
    struct image_data_directory data_directory[];
};

struct image_nt_headers {
    uint32_t magic;
    struct image_file_header file_header;
    struct image_optional_header optional_header;
};

struct image_dos_header {
    uint8_t reserved[0x3c];
    uint32_t nt_header;
};

struct image_export_directory {
    uint8_t reserved[0x18];
    uint32_t number_of_names;
    uint32_t addr_of_functions;
    uint32_t addr_of_names;
    uint32_t addr_of_name_ordinals;
};

int _memcmp(const void *ptr1, const void *ptr2, int num) {
    for(int i = 0; i < num; i++) {
        if (*(char*)(ptr1 + i) != *(char*)(ptr2 + i)) { return 1; }
    }
    return 0;
}

int _strcmp(const char *str1, const char *str2) {
    while(*str1 && *str2) {
        if (*str1++ != *str2++) { return 1; }
    }
    return *str1 != *str2;
}

struct tib *getTib() {
    return (struct tib*)tib_addr;
}

int main() {
    struct tib *tib = getTib();
    struct peb *peb = tib->peb;
    struct peb_ldr *ldr = peb->ldr;
    struct list_entry *head = &ldr->modules;
    struct list_entry *curr = head->flink;

    uint32_t base = 0;
    while(curr != head) {
        struct ldr_data_table_entry *entry = (struct ldr_data_table_entry*)curr;

        wchar_t sought[] = L"C:\\Windows\\syswow64\\kernel32.dll";
        int sought_len = sizeof(sought) / sizeof(wchar_t) - 1; 

        struct unicode_string full_name = entry->full_name;
        if (full_name.length == sought_len * 2 &&
               !_memcmp(full_name.buffer, sought, sizeof(sought) - sizeof(wchar_t)))
        { base = entry->dll_base; break; }

        curr = curr->flink;
    }

    struct image_dos_header *dos = (struct image_dos_header*)base;
    struct image_nt_headers *nt = (struct image_nt_headers*)(base + dos->nt_header);
    struct image_optional_header *opt = &nt->optional_header;
    struct image_data_directory *data = &opt->data_directory[data_directory_export];
    struct image_export_directory *export = (struct image_export_directory*)(base + data->virtual_address);
    
    get_proc_address_func get_proc_address = 0; 
    char **names = (char**)(base + export->addr_of_names);
    for(int i = 0; i < export->number_of_names; i++) {
        char *sought = "GetProcAddress";
        char *name = names[i] + base;
        if (!_strcmp(sought, name)) {
            uint32_t *func_addrs = (uint32_t*)(export->addr_of_functions + base);
            uint16_t *ordinals = (uint16_t*)(export->addr_of_name_ordinals + base);
            uint32_t address = func_addrs[ordinals[i]] + base;

            get_proc_address = (get_proc_address_func)address;
        }
    }

    get_std_handle_func get_std_handle = (get_std_handle_func)get_proc_address((void*)base, "GetStdHandle");
    write_console_func write_console = (write_console_func)get_proc_address((void*)base, "WriteConsoleA");

    int written;
    char hello[] = "Hello World!";
    void *console = get_std_handle(std_output);
    write_console(console, hello, sizeof(hello) - 1, &written, 0);
}

Fajne ćwiczenie ;)

Nie ma co się rozwodzić nad takim problemem, lepiej czas przeznaczyć na bardziej pożyteczne kwestie.

A tam, przesadzasz :>.

39

Kawałek kodu i ode mnie - bardziej ofc chodziło mi o zilustrowanie metody niż o zawsze-działający-kod :)

Kod napisany jest pod linuxa (32-bity x86), natomiast tą samą metodę można użyć na 64-bity oraz na Windowsa 32- / 64-bity.
Kod nie korzysta z żadnych bibliotek (nawet nie szuka żadnych w pamięci), ani nie ma żadnych wstawek asma/innych (przynajmniej jawnie ;>).
Wyjaśnienie zasady działania pod kodem.

volatile unsigned int something_wicked_this_way_comes(
    int a, int b, int c, int d) {
  a ^= 0xC3CA8900;  b ^= 0xC3CB8900;  c ^= 0xC3CE8900;  d ^= 0x80CDF089;
  return a+b+c+d;
}

void* find_the_witch(unsigned short witch) {
  unsigned char *p = (unsigned char*)something_wicked_this_way_comes;
  int i;
  for(i = 0; i < 50; i++, p++) {
    if(*(unsigned short*)p == witch) return (void*)p;
  }

  return (void*)0;
}

typedef void (*gadget)() __attribute__((fastcall));

int main(void) {
  gadget eax_from_esi_call_int = (gadget)find_the_witch(0xF089);
  gadget set_esi = (gadget)find_the_witch(0xCE89);
  gadget set_ebx = (gadget)find_the_witch(0xCB89);
  gadget set_edx = (gadget)find_the_witch(0xCA89);

  if(!eax_from_esi_call_int) return 1;
  if(!set_esi) return 3;
  if(!set_ebx) return 4;
  if(!set_edx) return 5;

  set_edx(12), set_ebx(1), set_esi(4);
  eax_from_esi_call_int("Hello World\n"); 
  
  return 0;
}

Kod korzysta z metody bardzo podobnej do metody exploitacji języków JITowanych przy zabezpieczonej pamięci via XD/NX/DEP/etc - tj. niejawnie starałem się w pamięć wykonywalną wrzucić kilka "gadżetów" (w rozumieniu ret2libc aka return oriented programing - http://gynvael.coldwind.pl/?id=144) i następnie je użyć do zrobienia syscalla do systemu (tak więc żadne biblioteki nie są potrzebne, natomiast oczywiście zachodzi bezpośrednia interakcja z kernelem).

Owe gadzety są w funkcji something_wicked_with_way_comes, a konkretniej są to stałe przy xor'ach:

  a ^= 0xC3CA8900;  b ^= 0xC3CB8900;  c ^= 0xC3CE8900;  d ^= 0x80CDF089;

Powyższy kod w assemblerze / kodzie maszynowym zostanie zapisany następująco:

[...]
   6:	35 00 89 ca c3       	xor    eax,0xc3ca8900
   b:	89 45 08             	mov    DWORD PTR [ebp+0x8],eax
   e:	8b 45 0c             	mov    eax,DWORD PTR [ebp+0xc]
  11:	35 00 89 cb c3       	xor    eax,0xc3cb8900
  16:	89 45 0c             	mov    DWORD PTR [ebp+0xc],eax
  19:	8b 45 10             	mov    eax,DWORD PTR [ebp+0x10]
  1c:	35 00 89 ce c3       	xor    eax,0xc3ce8900
  21:	89 45 10             	mov    DWORD PTR [ebp+0x10],eax
  24:	8b 45 14             	mov    eax,DWORD PTR [ebp+0x14]
  27:	35 89 f0 cd 80       	xor    eax,0x80cdf089
[...]

Czyli gdyby disassemblować "źle", czyli z przesunięciem o jeden/dwa bajty, to dostanie się trochę inny kod:
6: 35 00 89 ca c3 --> mov edx, ecx ; ret
11: 35 00 89 cb c3 --> mov ebx, ecx ; ret
1c: 35 00 89 ce c3 --> mov esi, ecx ; ret
27: 35 89 f0 cd 80 --> mov eax, esi ; int 0x80

Tak więc dzięki temu jestem pewien, że w pamięci wykonywalnej są potrzebne mi gadgety.
Idąc dalej, używam funkcji find_the_witch do ich znalezienia w kodzie maszynowym funkcji something_wicked_this_way_comes:
gadget eax_from_esi_call_int = (gadget)find_the_witch(0xF089);
gadget set_esi = (gadget)find_the_witch(0xCE89);
gadget set_ebx = (gadget)find_the_witch(0xCB89);
gadget set_edx = (gadget)find_the_witch(0xCA89);

I teraz jeszcze jedna istotna sprawa - typ gadget to:
typedef void (*gadget)() attribute((fastcall));
Są tu dwie istotne rzeczy:

  1. w C argumenty () oznaczają "dowolne nieokreślone argumenty" (w przeciwieństwie do C++ gdzie to jest tożsame z (void) aka brak argumentów)
  2. ustawiona jest konwencja wywołania na fastcall, czyli parametry funkcji polecą do rejestrów zamiast na stos (a konkretniej, pierwszy argument poleci do rejestru ecx w tym konkretnym przypadku).

Dalej po prostu "składam" zwykły asmowy hello world za pomocą tych gadgetów:
set_edx(12), set_ebx(1), set_esi(4);
eax_from_esi_call_int("Hello World\n");

Co runtime będzie wykonane jako:
(main) mov ecx, 12; mov eax, set_edx; call eax; (gadżet) mov edx, ecx; ret; (main) ...
... int 0x80

Pomijając części z main:
[gadzet 1] mov edx, 12 (długość napisu)
[gadzet 2] mov ebx, 1 (stdout)
[gadzet 3] mov esi, 4 (sys_write)
[fastcall to zalatwi] mov ecx, address "Hello World\n"
[gadzet 4] mov eax, esi
[gadzet 4] int 0x80

Oczywiście w tej postaci po wypisaniu hello world program się wywali, natomiast można go dość łatwo przerobić na taki który się mimo wszystko nie wywala :)

Test:

$ gcc -m32 test.c -O0
$ ./a.out 
Hello World
Segmentation fault (core dumped)
$ 
14

przerobiłem że się nie wywala:

typedef void (*gadget)() __attribute__((fastcall));
gadget eax_from_esi_call_int;
gadget set_esi;
gadget set_ebx;
gadget set_edx;

void graceful_exit()
{
  set_ebx(0);
  set_esi(1);
  eax_from_esi_call_int(0);
}

volatile unsigned int something_wicked_this_way_comes(
    int a, int b, int c, int d) {
  a ^= 0xC3CA8900;  b ^= 0xC3CB8900;  c ^= 0xC3CE8900;  d ^= 0x80CDF089;
  graceful_exit();
  return a+b+c+d;
}
 
void* find_the_witch(unsigned short witch) {
  unsigned char *p = (unsigned char*)something_wicked_this_way_comes;
  int i;
  for(i = 0; i < 50; i++, p++) {
    if(*(unsigned short*)p == witch) return (void*)p;
  }
 
  return (void*)0;
}
 
int main(void) {
  eax_from_esi_call_int = (gadget)find_the_witch(0xF089);
  set_esi = (gadget)find_the_witch(0xCE89);
  set_ebx = (gadget)find_the_witch(0xCB89);
  set_edx = (gadget)find_the_witch(0xCA89);
 
  if(!eax_from_esi_call_int) return 1;
  if(!set_esi) return 3;
  if(!set_ebx) return 4;
  if(!set_edx) return 5;
 
  set_edx(12), set_ebx(1), set_esi(4);
  eax_from_esi_call_int("Hello World\n"); 
}

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