zppp project |
|
|
14/09/2000 |
by "Ritz" |
|
|
Published by Quequero |
|
Life is just a blast, movin' very fast... |
Il tute � abbastanza chiaro, magari avresti dovuto spiegare un po' meglio la "pulitura" dello stack perch� in questo modo � un po' confusa :) ma per il resto � tutto ok |
... better stay on top or life will kick you in the ass! [Limp Bizkit, Take a Look Around] |
UIC's form |
|
UIC's form |
Difficolt� |
( )NewBies (x)Intermedio ( )Avanzato ( )Master |
Come da oggetto, oggi scriveremo un piccolo ma interessante tool per Linux...
Introduzione |
Tools usati |
URL o FTP del programma |
Essay |
A seconda della sintassi con cui viene scritto il listato sorgente, si scegliera'
quindi il copmpilatore. Nel caso sia stata scelta la classica sintassi Intel il
compilatore piu' utilizzato e' il NASM, in caso contrario si potra' usare, ad esempio, il
GAS (Gnu Assembler), contenuto nel GCC.
Una volta che il sorgente viene compilato, bastera' semplicemente linkarlo con dei linker
tipo gcc o ld ottenendo come risultato finale un elf eseguibile.
Per quanto riguarda invece le classiche funzioni di i/o da console, file, etc., in Linux
abbiamo a disposizione due diverse possibilita', ovvero possiamo decidere se chiamare la
funzione del kernel relativa al nostro scopo o se preferiamo invece utilizzare le libc.
Nel secondo caso bastera' eseguire semplici call alla relativa funzione passano i relativi
argomenti nello stack, avendo l'accortezza, una volta che la funzione e' stata eseguita,
di pulire lo stack (in Linux e' obbligatori farlo *sempre* dopo che si torna da una
funzione).
Scegliendo la prima ipotesi, invece, dovremo fare delle chiamate al kernel, e cio' in
Linux avviene attraverso l'int 0x80. Prima di eseguire questo int, in eax sara' messo il
numero di funzione richiesto, quindi rispettivamente in ebx, ecx, edx, esi, edi i relativi
argomenti. In valore di ritorno si trovera' in eax e non sara' utilizzato lo stack. E' cmq
buona norma non mettere in eax direttamente il numero della funzione, poiche' tali numeri
con le successive versioni dei kernel possono variare, ma utilizzare invece nomi che le
identifichino, quali sys_write o sys_exit.
Ma veniamo ora ad un esempio pratico: il programma che spieghero' di seguito e' un
semplice tool che serve a configurare una connessione a Internet. Esso pone all'utente
varie domande, terminate le quali va a scrivere alcuni file e script di configurazione.
Per scriverlo utilizzeremo la classica sintassi Intel. I src verranno compilati con NASM e
linkati con gcc.
Dato che ' il primo esempio ho deciso di utilizzare sia il kernel che le libc per le
chiamate varie.
-----------------------------
global main
extern printf
extern scanf
extern strlen
NULL equ 0
-----------------------------
Prima di tutto dichiariamo l'entrypoint "main" (la funzione main() necessaria
per il linker gcc) che indica in punto di inizio esecuzione del nostro codice. Fatto cio'
dichiariamo le funzioni esterne delle libc che ci serviranno, nella fattispecie printf,
scanf e strlen (non chiedetemi a cosa servono per favore:) )....Ovviamente
se non lo sapete: man printf ecc....NdQue
Infine gli equates, con classico NULL = 0.
-----------------------------
section .data
graphic1 db 0Ah,0Dh,'
+----------------------------+',0Dh,0Ah,NULL Linux interpreta il 0Ah
come "a capo" non c'� bisogno del 0Dh NdQue
head db ' | zppp v1.0 by Ritz
for RACL |',0Dh,0Ah,NULL
graphic2 db '
+----------------------------+',0Ah,0Dh,0Ah,0Dh,NULL
copyright db 'This program is totally FREE and comes
with NO WARRANTY. Run from root account. ',0xA,0xD,NULL
explain db 'By using this
simple program you will now configure a new Internet connection.',0Dh,0Ah,'Please answer
to the following questions [^C to terminate]...',0Dh,0Ah,0Dh,0Ah,NULL
file1 db 0xA,'Writing
/etc/ppp/options... ',NULL
file2 db 'Writing
/etc/ppp/pppscript... ',NULL
file3 db 'Writing
/etc/ppp/pap-secrets... ',NULL
file4 db 'Writing
/etc/resolv.conf... ',NULL
script db 'Writing connection
script... ',NULL
done db 'done.',0xD,0xA,NULL
errormex db 'error!! Leaving... ',0xD,0xA,NULL
phone db '. Write here your
ISP phone number: ',NULL
phonebuffer TIMES 0x15 db NULL
lenphone dd NULL
device db '. Write here your
modem device path or its symbolic link: ',NULL
devicebuffer TIMES 0x15 db NULL
lendev dd NULL
user db '. Write here the
username for your account: ',NULL
userbuffer TIMES 0x15 db NULL
lenuser dd NULL
pw db '. Write here the
password for your account: ',NULL
pwbuffer TIMES 0x15 db NULL
lenpw dd NULL
domain db '. Write here the
domain name of your ISP: ',NULL
domainbuffer TIMES 0x15 db NULL
lendomain dd NULL
dns1 db '. Write here the
first DNS IP for your account (needed): ',NULL
dns1buffer TIMES 0x15 db NULL
lendns1buffer dd NULL
dns2 db '. Write here the
second DNS IP for your account (n = none): ',NULL
dns2buffer TIMES 0x15 db NULL
lendns2buffer dd NULL
perfect db 0xD,0xA,'The
configuration has been completed successfully!',0xD,0xA,NULL
advice db 'To connect to the
Internet, simply execute the script /usr/sbin/zppp.',0xD,0xA,NULL
advice2 db 'To let all users
be able to connect, change the mode of /usr/sbin/pppd to 4755.',0xD,0xA,0xD,0xA,NULL
enjoy db
'Enjoy!',0xD,0xA,'@2000 by Ritz for RACL',0xD,0xA,0xD,0xA,NULL
format db '%s',NULL
handle dd NULL
; ---------------------------------------------------------
optionspath db
'/etc/ppp/options',NULL
optionshandle dd NULL
optionsbuffer db 'lock',0xA
db
'defaultroute',0xA
db
'noipdefault',0xA
db
'modem',0xA
optionsins TIMES 0x15 db NULL
optionsbuffer2 db '115200',0xA
db
'crtscts',0xA
db
'passive',0xA
db
'asyncmap 0',0xA
db
'name "'
optionsins2 TIMES 0x15 db NULL
; ---------------------------------------------------------
pppscriptpath db
'/etc/ppp/pppscript',NULL
pppscripthandle dd NULL
pppscriptbuffer db 'TIMEOUT
60',0xA
db
'ABORT ERROR',0xA
db
'ABORT BUSY',0xA
db
'ABORT "NO CARRIER"',0xA
db
'ABORT "NO DIALTONE"',0xA
db
'"" "AT&FH0"',0xA
db
'OK "atdt'
pppscriptins TIMES 0x15 db NULL
pppscript2 db 'TIMEOUT 75',0xA
db
'CONNECT',NULL
; ---------------------------------------------------------
papsecretspath db
'/etc/ppp/pap-secrets',NULL
ppsecretshandle dd NULL
papsecrets1 db '"'
papsecrets TIMES 0x40 db NULL
; ---------------------------------------------------------
scriptpath db
'/usr/sbin/zppp',NULL
scriptbuffer db
'/usr/sbin/pppd -detach connect "/usr/sbin/chat -v -f /etc/ppp/pppscript"',NULL
; ---------------------------------------------------------
resolvpath db
'/etc/resolv.conf',NULL
resolvhandle dd NULL
resolv db
'search '
resolvins TIMES 0x15 db NULL
resolv2 db
'nameserver '
resolvins2 TIMES 0x15 db NULL
resolv3 db
'nameserver '
resolvins3 TIMES 0x15 db NULL
-----------------------------
Come vedete dalla sezione .data il prg dovra' creare 5 file: /etc/ppp/options,
/etc/ppp/pppscript, /etc/ppp/pap-secrets, /etc/resolv.conf e lo script di connessione, che
potremo benissimo mettere in /usr/sbin/zppp. I "TIMES 0xN db NULL" equivalgono
semplicemente alle dichiarazioni "db N dup(NULL)" del TASM, ovvero riempiono di
NULL una quantita' N di byte. Il resto dovrebbe essere tutto chiaro.
Ovviamente i contenuti dei file dovranno avere degli spazi vuoti proprio per permettere
l'inserimento dei dati personali per la connessione.
NOTA: i src sono stati scritti come semplice esempio di coding, come potete vedere da voi
stessi gli overflow sono infiniti, ma questo (visto che non abbiamo bisogno che il prg sia
necessariamente sicuro) a noi non interessa...E se io lo
overflowassi?? :) NdQue
Una volta dichiarata la sezione .data possiamo mettere sotto tutti i dati inizializzati
che ci serviranno. Piccola nota: i dati non inizializzati andrebbero messi ad esser
precisi in .bss, ma in questo semplice esempio ho usato ugualmente questa sezione.
Ora inzia la sezione .text
-----------------------------
section .text
main:
push dword graphic1
call printf
add esp, 4
push dword head
call printf
add esp, 4
push dword graphic2
call printf
add esp, 4
push dword copyright
call printf
add esp, 4
push dword explain
call printf
add esp, 4
push dword phone
call printf
add esp, 4
push dword phonebuffer
push dword format
call scanf
add esp, 8
push dword device
call printf
add esp, 4
push dword devicebuffer
push dword format
call scanf
add esp, 8
push dword user
call printf
add esp, 4
push dword userbuffer
push dword format
call scanf
add esp, 8
push dword pw
call printf
add esp, 4
push dword pwbuffer
push dword format
call scanf
add esp, 8
push dword domain
call printf
add esp, 4
push dword domainbuffer
push dword format
call scanf
add esp, 8
push dword dns1
call printf
add esp, 4
push dword dns1buffer
push dword format
call scanf
add esp, 8
push dword dns2
call printf
add esp, 4
push dword dns2buffer
push dword format
call scanf
add esp, 8
-----------------------------
Anche qui tutto e' molto semplice: il prg infatti fa tutte le domande necessarie e aspetta
la risposta da parte dell'utente. Notate SEMPRE che lo stack viene pulito con l'istro add
esp. Potete farlo anche pushando lo stack in dei registri che non vi servono, ma qui ho
preferito il primo metodo.
Fatto cio' va a sistemare uno a uno i file che poi saranno scritti:
-----------------------------
push dword devicebuffer
call strlen
add esp, 4
mov dword [lendev], eax
mov ecx, eax
inc ecx
mov byte [devicebuffer+eax], 0xA
mov esi, dword devicebuffer
mov edi, dword optionsins
repz movsb
mov ecx, 0x28
mov esi, dword optionsbuffer2
mov edi, dword optionsins
add edi, dword [lendev]
inc edi
repz movsb
push dword userbuffer
call strlen
mov dword [lenuser], eax
mov byte [userbuffer+eax], '"'
mov ecx, eax
inc ecx
mov esi, dword userbuffer
mov edi, dword optionsins
add edi, 0x29
add edi, dword [lendev]
repz movsb
mov eax, dword optionsbuffer
add eax, 0x4E
add eax, dword [lendev]
add eax, dword [lenuser]
mov dword [eax], NULL
-----------------------------
Per fare tutte queste operazioni come vedete bisogna fare un po' di taglio e cucito ;) nel
senso che i byte dei buffer non si trovano tutti al loro posto, anzi, quindi repz movsb
rulez. Come avrete gia' notato, nel NASM per muovere un offset in un registro si scrive
"mov reg, dword buffer", mentre per muovere il contenuto del buffer in questione
nello stesso registro si scrive "mov reg, dword [buffer]". Un po' diverso anche
qui da TASM o MASM. Inoltre, viene accettato il prefisso 0x per indicare i valori hex.
Dopo che i byte sono stati tutti allineati correttamente e il buffer e' pronto per essere
scritto nel file, vengono eseguite le ultime 5 istruzioni del blocco sopra presentato, che
hanno lo scopo di mettere byte NULL alla fine del buffer per questioni di sicurezza nel
caso in cui essi non fossero gia' presenti perche' sono stati sovrascritti nelle
operazioni di "allineamento". Lo so avrei potuto usare delle strutture per fare
le stesse cose ma non sarebbe cambiato molto e per i nostri scopi questi buffer vanno
benissimo.
Le stesse operazioni vanno fatte anche per tutti gli altri file:
-----------------------------
push dword phonebuffer
call strlen
mov dword [lenphone], eax
add esp, 4
mov byte [phonebuffer+eax], '"'
mov byte [phonebuffer+eax+1], 0xA
mov ecx, eax
add ecx, 2
mov esi, dword phonebuffer
mov edi, dword pppscriptins
repz movsb
mov ecx, 0x13
mov esi, dword pppscript2
mov edi, dword pppscriptins
add edi, eax
add edi, 2
repz movsb
mov eax, dword pppscriptbuffer
add eax, 0x71
add eax, dword [lenphone]
mov dword [eax], NULL
; ---------------------------------------------------------
push dword userbuffer
call strlen
mov dword [lenuser], eax
mov ecx, eax
inc ecx
mov esi, dword userbuffer
mov edi, dword papsecrets
repz movsb
mov dword [papsecrets+eax],0x22092A09
push dword pwbuffer
call strlen
add esp, 4
mov dword [lenpw], eax
mov byte [pwbuffer+eax],'"'
mov ecx, eax
inc ecx
mov esi, dword pwbuffer
mov edi, dword papsecrets
add edi, dword [lenuser]
add edi, 4
repz movsb
mov eax, dword papsecrets
add eax, 0x6
add eax, dword [lenuser]
add eax, dword [lenpw]
mov dword [eax], NULL
; ---------------------------------------------------------
push dword domainbuffer
call strlen
add esp, 4
mov dword [lendomain], eax
mov byte [domainbuffer+eax], 0xA
mov ecx, eax
inc ecx
mov esi, dword domainbuffer
mov edi, dword resolvins
repz movsb
push dword dns1buffer
call strlen
add esp, 4
mov dword [lendns1buffer], eax
mov byte [dns1buffer+eax], 0xA
mov ecx, eax
inc ecx
mov esi, dword dns1buffer
mov edi, dword resolvins2
repz movsb
cmp dword [dns2buffer], 0x0000006E
jz otherdns
; inizio sezione inutile se non c'e' il 2� dns ---------------
push dword dns2buffer
call strlen
add esp, 4
mov dword [lendns2buffer], eax
mov byte [dns2buffer+eax], 0xA
mov ecx, eax
inc ecx
mov esi, dword dns2buffer
mov edi, dword resolvins3
repz movsb
mov ecx, dword [lendns2buffer]
add ecx, 0xB
mov esi, dword resolv3
mov edi, dword resolvins2
add edi, dword [lendns1buffer]
inc edi
repz movsb
; fine sezione inutile se non c'e' il 2� dns -----------------
otherdns:
mov ecx, dword [lendns1buffer]
add ecx, dword [lendns2buffer]
add ecx, 0x16
mov esi, dword resolv2
mov edi, dword resolvins
add edi, dword [lendomain]
inc edi
repz movsb
mov eax, dword resolv
add eax, 0x1F
add eax, dword [lendomain]
add eax, dword [lendns1buffer]
add eax, dword [lendns2buffer]
mov dword [eax], NULL
-----------------------------
Soliti lavori di allineamento, con l'accortezza di aver inserito l'opzione di poter
utilizzare anche un solo server dns.
*Solo* dopo che tutti i buffer sono a posto possono esere scritti su file. E qui usero' le
chiamate al kernel al posto di chiamate quali fopen().
-----------------------------
mov eax, 0x8
mov ebx, dword optionspath
mov ecx, 0x1A4
int 0x80
cmp eax, -1
jz near error
mov dword [handle], eax
-----------------------------
Il numero della chiamata e' 8, cioe' sys_create. in ebx va l'offset di optionspath, in ecx
i permessi.
Come si impostano i giusti permessi? Semplice: considerato che il valore numerico di un
permesso (che so, 755) e' sempre espresso in sistema ottale, bastera' convertire tale
valore in hex e quindi metterlo in ecx, nient'altro.
-----------------------------
push dword optionsbuffer
call strlen
add esp, 4
mov ebx, dword [handle]
mov ecx, dword optionsbuffer
mov edx, eax
mov eax, 0x4
int 0x80
-----------------------------
Con il pezzo sopra di codice andiamo a scrivere nel file che abbiam oappena creato tramine
sys_write, numero 0x4. Il numero di byte da scrivere lo ricaviamo con un strlen del buffer
che ci interessa.
Ora non ci resta che chiudere l'handle.
-----------------------------
mov eax, 0x6
mov ebx, dword [handle]
int 0x80
-----------------------------
Fatte queste operazioni per i vari file, non dovremo fare altro che compilare il tutto con
$ nasm -f elf zppp.asm
e quindi andare a linkare il file .o con
$ gcc zppp.o
per avere come risultato l'eseguibile a.out.
|
Il nostro piccolo tool e' terminato e funziona a dovere.
Per i soliti suggerimenti, critiche, appunti, bug e cosi' via mandatemi pure una mail.
Byz,
Ritz
----- [email protected] -----
---- [email protected] ---
Disclaimer |