Powstał więc problem - kto za mnie będzie dopisywał każdą funkcję do listy. Niestety, na asystenta mnie jeszcze nie stać, więc pozostało to zrzucić na kompilator.
Wszystko co od teraz napiszę dotyczyć będzie GCC, a konkretniej kompilatora g++. Prawdopodobnie da się to zrobić w identyczny sposób na innych kompilatorach, tylko wystarczy trochę poniższe makra pozmieniać. Kod testowałem na MinGW GCC 3.4.5 na Viście, GCC 4.1.2 20061115 na Debianie i GCC 4.0.1 (Apple Inc. build 5490) na Mac OS X.
Przyszły mi do głowy dwa pomysły:
1) Wykorzystać mechanizm eksportów, i run-time parsować tablicę eksportów pliku wykonywalnego (czy to EAT w PE, czy analogiczne mechanizmy w ELF / MACH-O). Jest to dość między-kompilatorowe, ale na każdy OS będzie trzeba klepać parsing eksportów na nowo - a to będzie z 30-50 linii per OS.
2) Wykorzystać pewną właściwość assemblera (GNU AS) używanego przez GCC, i stworzyć nową sekcję i wrzucać w nią kolejne pointery na funkcje. Rozwiązanie zadziała prawdopodobnie tylko na kompilatorze GCC (lub innych opartych o GNU AS... są takie?), ale z bardzo niewielkimi zmianami ruszy na (prawie) każdym OS'ie gdzie jest GCC.
To pierwsze wiem jak zrobić, więc z oczywistych powodów wybrałem to drugie ;>
Tak na prawdę całość rozbija się o trzy, proste, ale tworzone ponad godzinę (przez linuxowego gcc --;), makra:
1) LISTED_FUNC_LIST_START(a) - makro służy do inicjacji listy funkcji, w parametrze przyjmuje nazwę listy (a konkretniej, nazwę nowo-utworzonej tablicy pointerów na funkcje)
2) LISTED_FUNC(a) - makro do definiowania funkcji, gdzie 'a' to nazwa funkcji
3) LISTED_FUNC_LIST_END - makro kończące listę funkcji
Pierwsze makro wygląda następująco:
#define LISTED_FUNC_LIST_START(a) \
/* 0 */ __asm (".section .fnc" SECT_ATTR); \
/* 1 */ __asm (".globl " FUNC_UNDERSCORE #a); \
/* 2 */ __asm (FUNC_UNDERSCORE #a ":"); \
/* 3 */ extern func_ptr a[1]
Dzieją się tutaj 4ry rzeczy. W linii oznaczonej /* 0 */ deklarowana jest nowa sekcja o nazwie .fnc. SECT_ATTR to zależny od OSu zestaw atrybutów funkcji. Tak na prawdę zestawy są dwa - na Linux-based OS'y, i na inne (Mac OS X / Windows). Na Linux-based OSy wygląda to następująco:
# define SECT_ATTR ",\"a\",@progbits"
Atrybut "a" oznacza "allocatable" - bez niego sekcja nie zostanie wczytana do pamięci procesu. Natomiast "@progbits" oznacza po prostu "w tej sekcji są dane".
Jeżeli zaś chodzi o Mac OS X'a czy Windowsa:
# define SECT_ATTR ",\"dr\""
Atrybuty "dr" to kolejno data oraz read-only.
Wróćmy do LISTED_FUNC_LIST_START. Linia /* 1 */ to globalne wyeksportowanie symbolu - listy funkcji o nazwie z parametru. Makro FUNC_UNDERSCORE to dla Mac OS X'a i Windows "_", oraz ciąg pusty "" dla Linux-based OS'ów.
Linia /* 2 */ to zadeklarowanie etykiety (oznaczenia miejsca - adresu następujących danych) na poziomie assemblera.
A linia /* 3 */ to zadeklarowanie tejże tablicy/listy funkcji na poziomie C++. Oczywiście a[1] oznacza w tym wypadku "tablica z PRZYNAJMNIEJ jednym elementem", a nie "tablica o wielkości jednego elementu" - ponieważ w C++ nie ma kontroli granic tablic, możemy użyć takiego triku. Prawdopodobnie jest on zupełnie zbędny i a[] by wystarczyło, ale nvm, niech już sobie będzie.
Czyli streszczając, powyższe makro tworzy sekcje ".fnc" i deklaruje tablicę funkcji na poziomie asma, linkera i C++.
Czas na drugie makro, czyli deklaracja pojedynczej funkcji:
#define LISTED_FUNC(a) \
/* 0 */ __asm (".section .fnc" SECT_ATTR); \
/* 1 */ __asm (".long " FUNC_UNDERSCORE #a); \
/* 2 */ __asm (".text"); \
/* 3 */ extern "C" void a()
Linia /* 0 */ jest identyczna jak powyżej, z jedną uwagą - wszystko od niej do momentu zmiany sekcji będzie DOPISYWANE (słowo klucz) do sekcji .fnc.
Linia /* 1 */ to po prostu wrzucenie w dane miejsce ADRESU funkcji "a" (w listingu asma pojawi się ".long _nazwa", czyli po prostu "wstaw tutaj ADRES funkcji _nazwa")
Przedostatnia linia, /* 2 */, to "powrót" do pisania w sekcji kodu (".text" to skrócone ".section .text") - została ona dodana po kilku empirycznych próbach z paroma identycznymi funkcjami ;D
Ostatnia, /* 3 */ to po prostu deklaracja funkcji (po niej MUSI nastąpić ciało funkcji). Jedna uwaga co do zastosowanego tutaj extern "C" - bez tego w przypadku C++ funkcja "a" miała by w nazwie pewne dekoracje (np. zamiast alamakota było by __Z10alamakotav), a jako że w linii /* 1 */ korzystamy z tej nazwy, to musieli byśmy tam wstawić nazwę z dekoracją, co okazało się nietrywialne (z poziomu __asm, głównie przez liczbę przy Z ;p). W związku z czym można albo zrezygnować z dekoracji (jako że wszystkie funkcje na liście mają i tak różne nazwy, i identyczny prototyp, to to nie robi żadnej różnicy), albo wprowadzić dodatkową nazwę dla funkcji (alias) - czyli po prostu w sekcji .text wyemitować symbol, analogicznie jak w poprzednim makrze. Okazało się że drugie rozwiązanie nie działa na GCC pod Linux-based OS'ami, ponieważ ów kompilator tam chce być fajny, i wyrzuca wszystkie globalne wstawki asma na początek listingu (przez co aliasy nie działają jak powinny). Więc zostało rozwiązanie pierwsze - extern "C".
Czas na ostatnie makro, które kończy listę:
#define LISTED_FUNC_LIST_END \
/* 0 */ __asm (".section .fnc" SECT_ATTR); \
/* 1 */ __asm (".long 0")
Jak widać jest trywialne - na koniec sekcji .fnc (/* 0 */) wrzucamy LONG'a o wartości 0 (/* 1 */).
Jak tego użyć? Rzućmy okiem na poniższy kod:
#include <cstdio>
// Func pointer typedef
typedef void (*func_ptr)();
// Defines
#ifdef __unix__
# define FUNC_UNDERSCORE ""
# define SECT_ATTR ",\"a\",@progbits"
#else
# define FUNC_UNDERSCORE "_"
# define SECT_ATTR ",\"dr\""
#endif
#define LISTED_FUNC_LIST_START(a) \
/* 0 */ __asm (".section .fnc" SECT_ATTR); \
/* 1 */ __asm (".globl " FUNC_UNDERSCORE #a); \
/* 2 */ __asm (FUNC_UNDERSCORE #a ":"); \
/* 3 */ extern func_ptr a[1]
#define LISTED_FUNC_LIST_END \
/* 0 */ __asm (".section .fnc" SECT_ATTR); \
/* 1 */ __asm (".long 0")
#define LISTED_FUNC(a) \
/* 0 */ __asm (".section .fnc" SECT_ATTR); \
/* 1 */ __asm (".long " FUNC_UNDERSCORE #a); \
/* 2 */ __asm (".text"); \
/* 3 */ extern "C" void a()
// Listed functions
LISTED_FUNC_LIST_START(my_function_list);
// 1st func
LISTED_FUNC(first_func)
{
puts("first function");
}
// 2nd func
LISTED_FUNC(second_func)
{
puts("second function");
}
// 3rd func
LISTED_FUNC(third_func)
{
puts("third function");
}
LISTED_FUNC_LIST_END;
// End of listed functions
int
main()
{
func_ptr *ptr;
for(ptr = my_function_list; *ptr; ptr++)
(*ptr)();
return 0;
}
Czyli na górze makra, potem LISTED_FUNC_LIST_START(my_function_list); które zadeklaruje tablice pointerów na funkcje my_function_list, potem jakieś 3 funkcje deklarowane w sposób LISTED_FUNC(first_func), i na koniec LISTED_FUNC_LIST_END;. A w funkcji main zwykłe przechodzenie tablicy i wykonywanie funkcji, jedna po drugiej.
Tak to wygląda na Viście:
12:42:33 gynvael >ver
Microsoft Windows [Wersja 6.0.6001]
Ansi hack ver 0.004b by gynvael.coldwind//vx
12:42:40 gynvael >g++ --version
g++ (GCC) 3.4.5 (mingw special)
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
12:42:43 gynvael >g++ test.cpp -Wall -Wextra
12:42:48 gynvael >a
first function
second function
third function
12:42:49 gynvael >
Tak na Debianie:
12:48:31 gynvael:debianvm> uname -a
Linux debianvm 2.6.18-6-686-bigmem #1 SMP Sat Dec 27 10:38:36 UTC 2008 i686 GNU/Linux
12:53:19 gynvael:debianvm> g++ --version
g++ (GCC) 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12:53:37 gynvael:debianvm> g++ ltest.cpp -Wall -Wextra
12:53:47 gynvael:debianvm> ./a.out
first function
second function
third function
12:53:49 gynvael:debianvm>
A tak na OS X:
Mac:~ gynvael$ uname -a
Darwin Mac.local 9.6.0 Darwin Kernel Version 9.6.0: Mon Nov 24 17:37:00 PST 2008; root:xnu-1228.9.59~1/RELEASE_I386 i386
Mac:~ gynvael$ g++ --version
i686-apple-darwin9-g++-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5490)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Mac:~ gynvael$ g++ -Wall -Wextra test.cpp
Mac:~ gynvael$ ./a.out
first function
second function
third function
Mac:~ gynvael$
I tyle.
By the way...
On 22nd Nov'24 we're running a webinar called "CVEs of SSH" – it's free, but requires sign up: https://hexarcana.ch/workshops/cves-of-ssh (Dan from HexArcana is the speaker).
P.S. kompilacja z -g na Debianie coś mi nie działa, może rzucę potem okiem wtf
P.S.2. powyższy kod powinien działać na platformach 32-bitowych, tj na 64-bitowych nie zadziała
P.S.3. Unavowed przetestował powyższe na Linux-based OS'ie na procesorze z rodziny ARM - trzeba tam zrobić jedną zmianę, mianowicie z atrybutów sekcji wywalić ",@progbits" (samo "a" ma zostać). Output z jego ARMowej maszynki:
<:tmp>$ uname -a
Linux 2.6.16.16 #1 Tue May 16 21:45:07 CEST 2006 armv4tl GNU/Linux
<:tmp>$ gcc --version
gcc (GCC) 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ g++ -o test test.cc
./test
<:tmp>$ ./test
first function
second function
third function
<:tmp>$
Comments:
Po owocnych dyskusjach z deusem, moja propozycja tak wygląda, może innym też się przyda. przenośne, duża kontrola nad tym, kiedy grupę tworzymy i kiedy grupę uruchamiamy */
#include <iostream>
using namespace std;
#define ACTION(name, code) struct name##_t { name##_t() code } name;
struct Group {
ACTION(first,
{
cout << "1" << endl;
})
ACTION(second,
{
cout << "2" << endl;
})
ACTION(last,
{
cout << "3" << endl;
})
};
int main() {
Group(); // run all now!
struct Group2 {
ACTION(a, { cout << "A" << endl; })
ACTION(b, { cout << "B" << endl; })
ACTION(c, { cout << "C" << endl; })
};
Group2(); // run all now!
return 0;
}
Rzucę potem okiem na VS ;>
Ewentualnie popatrz na to co ranides napisał ;>
@ranides
Pomysłowy sposób ;> Dało by radę w moim przypadku takie rozwiązanie, noo i bardziej przenośne ;>
Good work ;>
Hmm, ja nie mam zielonego pojęcia prawdę mówiąc, dawno nie miałem w łapkach żadnej sensownej książki o programowaniu ;<
Jeżeli nie, to Biblia C++(?). Nie pamiętam dokładnie tytułu ale na półce w księgarni poznasz ją na pewno. Chyba najgrubsza z dostępnych ;).
ps. Gynvael a jakies ksiazki do pisanie sterownikow jak np. twoj ExcpHook mozesz polecic?
Mam nadzieje że nikt nie napisał książki o pisaniu takich sterowników jak ExcpHook ;> Lepiej się uczyć pisać ładnie i działający kod ;D
Anyway, odpowiadając na pytanie, mi bardzo pomogła w ogarnięciu tematu książeczka "Rootkity: Sabotowanie Jądra Windows" (http://helion.pl/ksiazki/rootki.htm).
W sumie jakos specjalnie bardziej funkcjonalne to nie jest, ale przedstawia pewien odmienny schemat dzialania ;>
#include <cstdio>
#include <cstdlib>
#include <windows.h>
using namespace std;
// Func pointer typedef
typedef void (*func_ptr)();
/* Function signature */
#define FUNC_START 0x0BADC0DE
/* Placing a label right before the function declarations to obtain the
* initial search address */
#define START_FUNCTION_LIST(a)
extern DWORD a;
__asm("_" #a ":")
/* Use this macro inside every accessory function with its name as the parameter */
#define FUNCTION_START(a)
__asm("jmp $+8");
__asm("jmp _" #a);
__asm(".long 0x0BADC0DE");
#define FUNC_NUMBER 3 /* Number of the func signatures being searched at run-time */
func_ptr* ParseFunctions(void* StartAddr)
{
func_ptr* ret = (func_ptr*)malloc(1024); // enough for us
BYTE* ptr = (BYTE*)StartAddr;
int i=0;
do
{
if(*(DWORD*)ptr == FUNC_START)
{
printf("Found at 0x%.8x...
",(DWORD)ptr);
ret[i++] = (func_ptr)(ptr-2);
}
ptr++;
}
while(i<FUNC_NUMBER);
ret[i] = 0;
return ret;
}
START_FUNCTION_LIST(FunctionList);
extern "C" void Func1()
{
FUNCTION_START(Func1);
puts("Func1()");
}
extern "C" void Func2()
{
FUNCTION_START(Func2);
puts("Func2()");
}
extern "C" void Func3()
{
FUNCTION_START(Func3);
puts("Func3()");
}
int main()
{
func_ptr* ptr = ParseFunctions(&FunctionList);
for( ;*ptr;ptr++ )
{
(*ptr)();
}
return 0;
}
Czyli generalnie caly trick polega na tym ze na "poczatku" (nie do konca, prolog funkcji jest wrzucany wczesniej dlatego stosujemy ten trick z podwojnym jmp) kazdej procki ktora chcemy automatycznie wywolywac wrzucamy pewien ciag - sygnature - ktora okresla przynaleznosc do samo-odpalajacej sie grupy ;>
Sygnatura powinna byc ofc tak dobrana, zeby nie zainstaly zadne kolizje z kawalkami niezwiazanego kodu, w przykladzie uzylem zwyklego DWORDa o wartosci 0x0BADC0D3.
Na poczatku main() lecimy po pamieci szukajac N wystapien tych sygnaturek i spisujemy znalezione adresy, a potem je odpalamy ;> Tyle.
Thx za kolejny ciekawy pomysł ;>
Ostatnio deus podsunął mi pewien pomysł ze zmiennymi globalnymi, który jeszcze bardziej upraszcza sprawę (kurcze muszę jakiś tag <code> wprowadzić):
#include <vector>
#include <cstdio>
using namespace std;
typedef void (*func_ptr)();
int vector_push_back_wrapper(vector<func_ptr> &a, func_ptr b) { a.push_back(b); return 0; }
#define LISTED_FUNC_LIST_START(a)
vector<func_ptr> a;
#define LISTED_FUNC(b,a)
void a();
static int b##_##a##_var = vector_push_back_wrapper(b, a);
void a()
#define LISTED_FUNC_LIST_END(a)
static int a##_end_var = vector_push_back_wrapper(a, NULL)
// Listed functions
LISTED_FUNC_LIST_START(my_function_list);
// 1st func
LISTED_FUNC(my_function_list, first_func)
{
puts("first function");
}
// 2nd func
LISTED_FUNC(my_function_list, second_func)
{
puts("second function");
}
// 3rd func
LISTED_FUNC(my_function_list, third_func)
{
puts("third function");
}
LISTED_FUNC_LIST_END(my_function_list);
// End of listed functions
int
main()
{
func_ptr *ptr;
for(ptr = &my_function_list[0]; *ptr; ptr++)
(*ptr)();
return 0;
}
Add a comment: