Hello world bez bibliotek i asm

Odpowiedz Nowy wątek
2012-06-27 16:24
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.

Pozostało 580 znaków

2012-06-27 16:39
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


"(...) otherwise, the behavior is undefined".
edytowany 1x, ostatnio: Endrju, 2012-06-27 16:39
na ideone.com nie działa - Wibowit 2012-06-27 18:35
No. Widocznie nie pokazują tego. Tak normalnie wyświetla się "bezsensu.c:1:9: note: #pragma message: Hello world". - Endrju 2012-06-27 18:39
aa przy kompilacji? - Wibowit 2012-06-27 18:47
No tak, bo to oszustwo. ;-) - Endrju 2012-06-27 18:52

Pozostało 580 znaków

2012-06-27 17:16
msm
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ę.)

I wypisuje coś? A jak skompilujesz z -nostdlib? A jeszcze z -nodefaultlibs? Z tymi dwoma flagami to już nie będzie żadnej biblioteki linkował automatycznie. - Endrju 2012-06-27 17:21
Działa normalnie. z -nostdlib wypisuje undefined reference do _alloca, __main i printf. - msm 2012-06-27 17:22
No. Bo GCC domyślnie linkuje, a w C można tak wywołać funkcje bez deklaracji (implicit declaration, to kompatybilność wsteczna). Więc korzystasz z bibliotek w tym. :-P - Endrju 2012-06-27 17:25

Pozostało 580 znaków

2012-06-27 17:24
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 :)

Nie można, przeczytaj komentarze. Niejawnie linkuje do biblioteki, z której bierze printf. - Endrju 2012-06-27 17:25
własnie po dodaniu posta zacząłem czytać :) - fasadin 2012-06-27 17:26
No właśnie nie do końca rozwiązuje :>. Ale poczekaj chwilę, mam pomysł (a to się źle może skończyć). - msm 2012-06-27 17:27
Poka poka. :-> - Endrju 2012-06-27 17:28
Damn, teraz już nie zdążę :>. Ale jak wrócę to napiszę - jeśli mi wyjdzie... - msm 2012-06-27 18:01

Pozostało 580 znaków

2012-06-27 17:54
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

edytowany 1x, ostatnio: Flaker, 2012-06-27 17:54
ale bardziej mi chodziło o zrobienie własnej funkcji/klasy która wyświetla coś na ekran bez użycia asm i bibliotek (lub ogólnie jakiegoś napisu który może być jawnie podany w programie) - fasadin 2012-06-27 18:13

Pozostało 580 znaków

2012-06-27 18:31
Rev
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/ques[...]e-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.


Pozostało 580 znaków

2012-06-27 19:57
msm
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 :>.

edytowany 5x, ostatnio: msm, 2012-06-27 19:59
Pokaż pozostałe 9 komentarzy
@Rev - Teb jest w sumie cały nieudokumentowany oficjalnie (oprócz małego subsystem-independent wycinka) - ale z kolei jest praktycznie gwarantowane że wskazuje na niego fs (nieoficjalnie gwrantowane... ale jeśli MS sam tego używa w swoich narzędziach to coś się dzieje) - przy czym w gcc trzeba by chyba do tego wstawki asm a tak nie można :>. W cl jest do tego takie cudo jak __readfsdword. Ale na przykład liczyłem sobie wskaźniki jako 4 bajty co na systemach 64bit nie do końca się zgadza, albo unsigned short to niekoniecznie 16 bit (ID, a stdint nie mogłem użyć :>)... - msm 2012-06-27 23:46
@Gynvael Coldwind - Ja bym kombinował coś z dostaniem się do rejestru fs bez wstawki asm (coś jak __readfsdword wspomniane) - nie jest to może rozwiązanie idealne, ale zawsze lepsze niż stała z księżyca (a z wygraniem żartowałem) ;) - msm 2012-06-27 23:47
@MSM Yep, ma sens. Hmm... nie ma czasem na końcu pamięci user-mode strony w której jest adres środka ntdll.dll btw (tzn adresu sysenter/syscall/int 0x2e)? To też byłby krok w jakąś stornę. (ot: wrzuciłem jeszcze jakieś readme do tamtego katalogu z linku) - Gynvael Coldwind 2012-06-27 23:52
Dzięki za linka, będzie co poczytać. Ja nie mam chwilowo żadnych pomysłów dających szansę na sukces (tzn. lepszych niż przeglądanie całej dostępnej pamięci do momentu trafienia na poprawny nagłówek)... - msm 2012-06-28 00:15
Przeszukanie pamięci to też jakiś pomysł; chociaż tylko w 32-bitowej przestrzeni chyba; nie jestem pewien czy w 64-bitowej aka 48-bitowej by się w uczciwym czasie coś znalazło, szczególnie jeśli brać pod uwagę, że każda nietrafiona strona == exception (ofc zakładając, że exception handler udostępniony w danym kompilatorze/implementacji umie łapać ten konkretny rodzaj low-levelowych exceptionów; pod linuxem w zasadzie signal() wystarcza), a to dość sporo czasu zajmuje (zresztą, każda trafiona storna to cache miss dla odmiany ;p). - Gynvael Coldwind 2012-06-28 00:24

Pozostało 580 znaków

2012-06-27 22:07
37

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)
$ 

peace,
gynvael.coldwind//vx

"Imagination is more important than knowledge..." Albert Einstein
edytowany 7x, ostatnio: Gynvael Coldwind, 2015-04-27 06:54
Pokaż pozostałe 2 komentarze
;o. Niestety nie potrafię więcej napisać. - fasadin 2012-06-27 22:27
Jak widzę takie tematy, a potem takie odpowiedzi jak ta powyższa i poprzednia od @MSM, to jedyną reakcją jest spojrzenie rodem z http://newnation.sg/wp-content/uploads/mother-of-god.jpg "Mother of Programmers". Skąd Wy macie taką wiedzę, skąd? Ogromne wyrazy uznania i podziwu. - Jadeszek 2012-06-27 22:27
gdy ktoś jeszcze zrozumie co tak naprawdę zrobił Gyn w swoim kodzie, z czego to wynika i jak do tego doszedł - tym większy podziw :) - Rev 2012-06-28 02:54
Nie zgodzę się, @Rev. Ja, na przykład, NIC nie rozumiem i właśnie przez to, z mojej perspektywy, ogarnięcie autora dąży do nieskończoności. Czytam po raz czwarty; dajcie mi jeszcze godzinkę. - 4ggr35510n 2012-06-28 06:28
Ja od razu zrozumiałem co kolega zrobił, bo sam się trochę bawiłem w takie rzeczy, ale sam bym na to nie wpadł. nie ma opcji :D jestem pełen podziwu i gratulacje umiejętności :) - Blood 2012-06-28 11:44

Pozostało 580 znaków

2012-06-28 01:23
13

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"); 
}
Hah, dobra metoda, good work :) - Gynvael Coldwind 2012-06-28 08:41

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