Z perspektywy czasu jednym z błędów, które zrobiłem tworząc kurs asemblera na YouTube parę lat temu było wprowadzenie mechanizmu ASLR (a raczej: zdanie się na istniejący mechanizm ASLR w mmap/VirtualAlloc) przy wyborze adresu, pod który ma zostać załadowany kod maszynowy do uruchomienia. O ile sam mechanizm dobrze obrazuje realia współczesnych systemów operacyjnych, o tyle triki, które trzeba używać przy adresowaniu relatywnym w x86-32 wprowadzają trochę niepotrzebnego zamieszania dla osób początkujących. Lepszym rozwiązaniem byłoby zaczęcie od jednego, zdefiniowanego adresu gdzie zostanie załadowany program i korzystanie z adresowania bezwzględnego, i wprowadzenie ASLR w połowie/pod koniec serii. Niemniej jednak wyszło jak wyszło, więc od czasu do czasu pojawiają się pytanie o to, dlaczego nie można w łatwy sposób zaadresować fragmentów kodu lub danych korzystając z asmloadera. Poniższy post jest de facto fragmentem odpowiedzi na jedno z takich pytań, której udzieliłem chwilę temu w komentarzach na YT, ale po namyśle zdecydowałem, że może zainteresuje ona więcej osób (a przynajmniej będę miał do czego linkować w przyszłości).

Komentarz na YT autorstwa Kapa PL, zacytowany w całości (istotne z uwagi na przykładowy kod):

Nie rozumiem dlaczego tak nie może być :/ ET to nie jest adres danej linijki kodu ?

[bits 32]
push ET
call [ebx+3*4];printf
add esp,4
push 0
call [ebx];exit

ET:db 'Hello World',0xa,0

Odpowiedź:
Sprowadza się to do dość ciekawej rzeczy, którą jest ASLR (Address Space Layout Randomization), który jest używany w pewnej formie w asmloaderze. Konkretniej, ET jest adresem linijki kodu, który w momencie kompilacji jest znany assemblerowi, tj. który jest liczony od podanego (za pomocą dyrektywy [org TUADRES]) lub domyślnego (0) adresu początku kodu.
Natomiast, jeśli program będzie załadowany w losowe miejsce w pamięci (a nie w dokładnie to samo, które założył assembler w momencie kompilacji), to adresy bezwzględne (takie jak ET) będą niepoprawne.

Przykładowo, ET w Twoim przypadku jest pod adresem 0xF, co można odczytać np. disasemblując program (via IDA):

00000000                 push    0Fh
00000005                 call    dword ptr [ebx+0Ch]
00000008                 add     esp, 4
0000000B                 push    0
0000000D                 call    dword ptr [ebx]
0000000F aHelloWorld     db 'Hello World',0Ah,0

Jak widać, push ET został przez assembler zmieniony na bezwzględną wersję, tj. push 0Fh (gdzie 0Fh to wspomniany adres ET ustalony w momencie kompilacji).

W momencie załadowania programu przez asmloader program jednak nie będzie załadowany pod adres 0, tylko pod adres wybrany przez system operacyjny w momencie realizowania żądania alokacji pamięci (który, w uproszczeniu, można traktować tożsamo z mechanizmem ASLR). Informacja o tym gdzie program jest ładowany pojawia się w drugiej linii outputu asmloader, np.:

gynvael:haven-windows> asmloader aaa
Simplified Assembly Loader v.0.0.2 by gynvael.coldwind//vx
Code loaded at 0x001c0100 (28 bytes)

W tym wypadku kod trafił pod adres 0x001c0100, a więc poprawnym adresem stringu "Hello World\n" będzie 0x1c010f a nie 0xf:

(hexdump pamięci)
001c0108 83 c4 04 6a 00 ff 13 48 65 6c 6c 6f 20 57 6f 72 6c 64 0a 00 cc  ...j...Hello World...
...

(zrzut disassemblera)
001c0100 680f000000      push    0Fh
001c0105 ff530c          call    dword ptr [ebx+0Ch]
001c0108 83c404          add     esp,4
001c010b 6a00            push    0
001c010d ff13            call    dword ptr [ebx]
001c010f 48              dec     eax  (48, czyli litera 'H' z 'Hello World')
...

Co istotne, adres ten będzie za każdym uruchomieniem inny (tak działa ASLR).

Więc w tym wypadku, tj. w sytuacji w której stosowany jest ASLR (lub podobny mechanizm sprawiający, że nie znamy docelowego adresu gdzie kod znajdzie się w pamięci), nie możemy korzystać z bezwzględnych adresów.

Trik z call którego używam jest jednym z rozwiązań tego problemu. Można by go rozwiązać na jeszcze kilka innych sposobów, np. stosując relatywne adresowanie względem jakiegoś rejestru, w którym jest adres początku kodu (co jest trochę lepszym rozwiązaniem), np.:

[bits 32]
call addr
addr:
pop ebp        ; w ebp jest adres addr
sub ebp, addr  ; zakladajac org 0, przesuwam ebp
              ; na poczatek kodu programu

; od teraz mozna uzywac ebp+ETYKIETA do adresowania
; relatywnego

lea eax, [ebp+ET]  ; tozsame z eax = ebp + ET
push eax

call [ebx+3*4];printf
add esp,4
push 0
call [ebx];exit

ET:db 'Hello World',0xa,0

Oczywiście gdybyśmy znali adres gdzie będzie załadowany nasz kod, to można by go podać w dyrektywie [org TUADRES] i nie bawić się w relatywne adresowanie, tylko korzystać z adresów bezwzględnych. W przypadku asmloadera niestety nie ma takiej opcji (choć z perspektywy czasu powinienem od tego zacząć w kursach).

I tyle :)

P.S. Od jakiegoś czasu bawię się pomysłem częstszego publikowania na blogu moich odpowiedzi z forów/mejli/komentarzy/etc - sporo pytań się powtarza, więc być może niektóre odpowiedzi by zainteresowały większe grono osób. Ma ktoś jakiś pomysł jak można by oznaczać takie posty? Myślałem o "FAQ:" albo "Q&A:", choć one nie do końca odpowiadają znaczeniowo tego typu postom.

Comments:

2017-03-20 18:08:14 = DeKrain
{
Takie szybkie pytanie, czym się różni mov od lea? Zauważyłem, że lea jest używane przy zmiennych na stosie.
}
2017-03-20 18:35:42 = RGVLcmFpbg==
{
WmFjemFsZW0gc2llIGJhd2ljIHcgc3p1a2FuaWUgdGFqbnljaCBpbWcsIHphIHBvbW9jYSBjdXJsYSB3IG5hZ2xvd2t1IHBsaWt1IC9pbWcvYXNkZiB6bmFsYXpsZW0gZmxhZ2UgRVRhZywgd2llYyBzcHJvYm93YWxlbSBuYXBpc2FjIG9kcG93aWVkeiBvIG5pY2t1IGFzZGYsIHVybHUgdGFtLCBnZHppZSB0byB6bmFsYXpsZW0gaSBvIGNhcHRjaHkgd2FydG9zY2kgdGVqIGZsYWdpICgwLTUyMTEwMWExNjUyZDYpLCBqZWRuYWsgcHJ6eXBhZGtpZW0gbmF0a25hbGVtIHNpZSBuYSB6ZGplY2llIDcvNy4gTm8gdG8gaWRlIHN6dWthYyBkYWxlaiA6UCwgUFMuIHNrYXBuYWxlbSBzaWUsIHplIG5pZSBtb3puYSB3IEJhc2ljNjQgdXp5d2FjIHpuYWtvdyBVTklDT0RF
}
2017-03-29 20:46:51 = Syweryn (aka. MajsterTynek)
{
@DeKrain: w skrócie:
MOV przenosi dane z pod danej komórki w pamięci,
a LEA zapisuje adres tejże komórki pamięci,
która te dane przechowuje.

@( RGVLcmFpbg== ):
Ja urządzałem sobie google hacking i jest o 1go więcej,
w tym znalazłem jednego niezidentyfikowanego,
bo trafiłem na sam bez stronki, może to ten twój.

Brakowało mi 2óch bodajże, ale w internacie
nikczemniki z innych pokoi szybkim formatem
zabrali mi dostęp do wyników, a dysku
do tej pory nie chciało mi się odzyskiwać. :F

W sumie zaraz sam pozyskam twojego, bo podałeś URL.

EDIT: Znalazłem ten nagłówek.
Teraz wystarczy spreparować zapytanie.
Trochę mi to zajmie, bo użyję samego TCP.
}
2017-03-29 21:18:59 = DeKrain
{
@Syweryn
Czyli LEA używa się np. jako "lea eax, [esp]", aby zapisać do rejestru EAX adres wartości wierzchołka stosu?
PS. Nie trudno się było domyślić, że klucz będzie pod "/img/asdf" :P
}
2017-03-30 00:56:11 = Syweryn (aka. MajsterTynek)
{
@DeKrain: (EDIT: Tak, dokładnie jak powiedziałeś. :) )
Połamałeś mi głowę swoim konkretnym przykładem.
Nie potrafię stwierdzić czy przykład da się nawet
zakodować w kodzie maszynowym. (EDIT: Da się.)

Co robi LEA:
http://stackoverflow.com/a/1658310/7715436
(Vol. 2A 3-525)[ http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf ]

W zasadzie można według kodowania tej istrukcji (LEA r32, m)
(http://gynvael.vexillium.org/dump/opcodes.txt)
uzyskać dowolną kombinację na jaką pozwala ModR/M + SIB,
czyli obrazując to mniej więcej na tyle (z wyjątkami):
https://en.wikipedia.org/wiki/X86#Addressing_modes
(dla twojego przykładu zachodzą szczególne zależności w jej kodowaniu)

@( RGVLcmFpbg== ): BTW jak uzyskałeś obrazek?
Wysłałem tam zapytanie GET ze zmienionymi
jednym lub/i obiema polami (?):
If-Modified-Since: /* znacznie starsza data */
If-None-Match: /* zupełnie inny hash */
i uzyskuję odpowiedź 200 OK, ale "Content-Length: 0" ;-;

@Gyn:
Tak mi DeKrain łeb połamał, że znalazłem w internecie,
że można w x86-64 adresować względem RIP/EIP.
[ http://wiki.osdev.org/X86-64_Instruction_Encoding#RIP.2FEIP-relative_addressing ].
"RIP-relative addressing allows object files to be location independent."

@everyone:
Tyle internetu to jeszcze nigdy nie przewijałem,
żeby znaleźć odpowiedzi i upewnić się. (od 23:30)
Po drodze z połowę chyba pogubiłem. :F
}
2017-03-30 01:02:17 = Syweryn (aka. MajsterTynek)
{
// Napisałem więcej wiadomości, ale że dowsząd są linki to Gyn musi zaakceptować. ;-;

@( RGVLcmFpbg== ): BTW jak uzyskałeś obrazek?
Wysłałem tam zapytanie GET ze zmienionymi
jednym lub/i obiema polami (?):
If-Modified-Since: /* znacznie starsza data */
If-None-Match: /* zupełnie inny hash */
i uzyskuję odpowiedź 200 OK, ale "Content-Length: 0" ;-;

}
2017-03-30 13:15:12 = DeKrain
{
@Syweryn poprostu zostaw (np. na tej stronie) wszystkie puste pola i prześlij
}
2017-03-30 15:50:10 = DeKrain
{
Znalazłem zdjęcie nr 3. Wystarczy jako id postu wpisać jakieś nieistniejące (np. 0)
}
2017-03-30 15:51:18 = DeKrain
{
I 3 przy okazji. Wystarczy wpisać id, które nie jest liczbą
}
2017-03-30 16:37:48 = Syweryn (aka. MajsterTynek)
{
@DeKrain: Oto co wysyłam i odbieram:
pastebin com j0VNxFzS

Samemu tego lepiej już nie zrobię,
a samo z siebie bardziej nie będzie działać.
}
2017-03-30 17:40:48 = Syweryn (aka. MajsterTynek)
{
@DeKrain: Dopisałem Twojego kotka do listy, jednak nie potrafię go uzyskać. :<
"Niektóre kotki nie są z rodziny błędów WWW, są tu niejako w odwiedzinach ;>"

Dzielę się moją kolekcją znalezionych:

(pic 1/7) -- NOT FOUND --
(pic 2/7) R U A HACKER NINJA?
(pic 3/7) GOOD HACKER OR A HACKER?
(pic 4/7) DIZ BE HACKER?
(pic 5/7) -- NOT FOUND --
(pic 6/7) -- NOT FOUND --
(pic 7/7) OH NO! // pusty komentarz
(pic 8/7) HOW FAST CAN A HACKER RUN?

http://gynvael.coldwind.pl/6q1c61k.jpg
http://gynvael.coldwind.pl/xacrackme/cat.jpg
http://gynvael.coldwind.pl/img/getimagesize_gif.gif
http://gynvael.coldwind.pl/img/asdf (???)
}
2017-03-30 19:42:55 = DeKrain
{
Mam taką samą listę. Teraz próbuję złamać admin panel (/admin). Gynvael na czacie końca live'a napisał, że tam też kryje się obrazek
}

Add a comment:

Nick:
URL (optional):
Math captcha: 6 ∗ 7 + 6 =