Samba _netr_ServerPasswordSet Expoitability Analysis

tl;dr

This is my analysis of the recent pre-auth Samba remote tracked by CVE-2015-0240[1]. It doesn’t appear to be very exploitable to me, but I’d love to be proven wrong.

Note that since the time when I originally did this analysis someone has released their own PoC and analysis [8] showing why they don’t think it’s exploitable on 32-bit.

Introduction

A new remote pre-auth vulnerability, CVE-2015-0240, was announced by Samba on February 23, 2015 This vulnerability obviously would be quite useful if it were exploitable, so I spent a bit of time looking at it. I spent most of my time looking at 4.0.24, however did test on unpatched 3.x as well just to be sure I was seeing similar results. This is a summary of my investigation of the issue on 64-bit and 32-bit and why I think it probably is unlikely to be very exploitable beyond a minor memory revelation. That said, I would love to be proven wrong; if you figure something out, please share!

I explain everything with 64-bit first and then follow up with a much shorter analysis on 32-bit, which is corroborates the analysis that has already been published [8].

Bug overview

The bug is an uninitialized pointer that will be freed in _netr_ServerPasswordSet(). This was decently explained by RedHat in their blog [2], however I'll reiterate some info here. The _netr_ServerPasswordSet() function passes the address of its local creds variable to the netr_creds_server_step_check() function, without first initializing it to NULL:

NTSTATUS _netr_ServerPasswordSet(struct pipes_struct *p, struct netr_ServerPasswordSet *r) { NTSTATUS status = NT_STATUS_OK; int i; struct netlogon_creds_CredentialState *creds; [...] status = netr_creds_server_step_check(p, p->mem_ctx, r->in.computer_name, r->in.credential, r->out.return_authenticator, &creds); unbecome_root(); if (!NT_STATUS_IS_OK(status)) { [...] TALLOC_FREE(creds); return status; }

The _netr_ServerPasswordSet() function expects netr_creds_server_step_check() to initialize it. You can see above that if the netr_creds_server_step_check() function fails, _netr_ServerPasswordSet() will call TALLOC_FREE() on the pointer. netr_creds_server_step_check() pretty much just calls schannel_check_required() or the schannel_check_creds_state() function.

static NTSTATUS netr_creds_server_step_check(struct pipes_struct *p,
					     TALLOC_CTX *mem_ctx,
					     const char *computer_name,
					     struct netr_Authenticator *received_authenticator,
					     struct netr_Authenticator *return_authenticator,
					     struct netlogon_creds_CredentialState **creds_out)
{
	NTSTATUS status;
	bool schannel_global_required = (lp_server_schannel() == true) ? true:false;
	struct loadparm_context *lp_ctx;

	if (schannel_global_required) {
		status = schannel_check_required(&p->auth,
						 computer_name,
						 false, false);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}
[...]
	status = schannel_check_creds_state(mem_ctx, lp_ctx,
					    computer_name, received_authenticator,
					    return_authenticator, creds_out);
	talloc_unlink(mem_ctx, lp_ctx);
	return status;
}we'

Above you can see that if schannel_global_required is set, then schannel_check_required() is called. Let's look at this first.

static NTSTATUS schannel_check_required(struct pipe_auth_data *auth_info,
					const char *computer_name,
					bool integrity, bool privacy)
{
	if (auth_info && auth_info->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) {
		if (!privacy && !integrity) {
			return NT_STATUS_OK;
		}

		if ((!privacy && integrity) &&
		    auth_info->auth_level == DCERPC_AUTH_LEVEL_INTEGRITY) {
			return NT_STATUS_OK;
		}

		if ((privacy || integrity) &&
		    auth_info->auth_level == DCERPC_AUTH_LEVEL_PRIVACY) {
			return NT_STATUS_OK;
		}
	}

	/* test didn't pass */
	DEBUG(0, ("schannel_check_required: [%s] is not using schanneln",
		  computer_name));

	return NT_STATUS_ACCESS_DENIED;
}

So basically if you've bound anonymously to the endpoint, the auth_level variable will be set to DCERPC_AUTH_LEVEL_NONE, meaning you can cause the function to return. This will occur before creds is initialized, so will trigger the bug.

Alternately, as we saw the schannel_check_creds_state() will be called, which we would also like to fail early. What does schannel_check_creds_state() look like:

NTSTATUS schannel_check_creds_state(TALLOC_CTX *mem_ctx,
				    struct loadparm_context *lp_ctx,
				    const char *computer_name,
				    struct netr_Authenticator *received_authenticator,
				    struct netr_Authenticator *return_authenticator,
				    struct netlogon_creds_CredentialState **creds_out)
{
	TALLOC_CTX *tmpctx;
	struct tdb_wrap *tdb_sc;
	struct netlogon_creds_CredentialState *creds;
	NTSTATUS status;
	int ret;

[...]

	/* Because this is a shared structure (even across
	 * disconnects) we must update the database every time we
	 * update the structure */

	status = schannel_fetch_session_key_tdb(tdb_sc, tmpctx, 
						computer_name, &creds);
	if (!NT_STATUS_IS_OK(status)) {
		tdb_transaction_cancel(tdb_sc->tdb);
		goto done;
	}

Still before creds is initialized, the code calls schannel_fetch_session_key_tdb(), passing the computer_name parameter, which we fully control in our DCE-RPC packet. What's important in our case is that it tries to fetch a key using the computer_name, which we can set to be zeroes (as RedHat noted), meaning it won't find anything. This will cause the function to return early via the done label.

talloc_free(tmpctx);
	return status;
}

This status will not be NT_STATUS_OK, and therefore we hit the condition we want to.

Triggering

Please refer to [8] for a public PoC, which leverages Impact[3] and includes the structure prototypes it is missing natively. To figure out the structure definitions prior to a PoC being available, simply referencing the Microsoft [4] documentation will tell you what’s needed.

Uninitialized stack variable exploitation primer

As RedHat notes: "It may be possible to control the value of creds, by sending a number of specially-crafted packets." The idea behind this is influencing the stack such that the uninitialized value is pre-populated with something that can be controlled.

This type of vulnerability has a typical way of being exploited. The idea is basically determine where on the stack the uninitialized variable is at the time of the vulnerable function call and then analyze other function call paths to see if any of them overlap. By isolating an overlapping stack frame where you control the data that is written, you have potential to provide arbitrary data in that location. One of the constraints to this scenario is that between the time you call the overlapping path and the time you trigger the vulnerability, no other stack frames can overlap. If an uncontrolled overlap occurs it makes exploitation extremely difficult, if not impossible, due to lack of control of the stack variable. This non-ideal situation essentially forces you to try to deal with any side effects of however the uncontrolled value is used.

For more background information about this type of vulnerability and approaches to exploitation check out the talk given by Halvar Flake in 2006 [5].

Quick and dirty stack depth analysis

If you just read Halvar's talk, you might be disgusted about my approach as a warning. But it gets us some answers quickly. I'm using some simple GDB scripting in order to determine what's touching the stack pointer of interest and when. This can be done fairly easily and samba being a forking daemon allows us to do a test run to figure out where on the stack the value we're interested in actually is, and then watch how it's used on subsequent executions.

First we want to compile a vulnerable version with debugging simples. This is easy using the configure options:

$ ./configure --enable-debug
$ make

Then we can run our compiled version like so:

# bin/smbd -s /etc/samba/smb.conf
# ps -ef | grep smbd
root     22599     1  0 18:40 ?        00:00:00 bin/smbd -s /etc/samba/smb.conf
root     22600 22599  0 18:40 ?        00:00:00 bin/smbd -s /etc/samba/smb.conf

We'll want to debug the daemonized version of the executable, which you can identify by the fact that it has PID 1 as its parent. Samba will fork on new connections, so in order to debug the child that is dispatched to handle the request we can use some gdb options.

# gdb -q -p 22599
(gdb) break smbd_accept_connection
Breakpoint 1 at 0x7f2a0bf7c575: file ../source3/smbd/server.c, line 529.
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
> silent
> set follow-fork-mode child
> continue
> end
(gdb) break smbd_process
Breakpoint 2 at 0x7f2a0b4ce6a4: file ../source3/smbd/process.c, line 3356.
(gdb) commands 2
Type commands for breakpoint(s) 2, one per line.
End with a line saying just "end".
>silent
>set follow-fork-mode parent
>continue
>end
(gdb) b _netr_ServerPasswordSet
Breakpoint 3 at 0x7f2a0b412a58: file ../source3/rpc_server/netlogon/srv_netlog_nt.c, line 1263.
(gdb) continue

Basically once a connection is accepted we tell gdb to follow the child. The child then calls smbd_process() to handle the packet, at which point we don't want to follow children anymore, so we change the setting back. We can also set a breakpoint on _netr_ServerPasswordSet(). Now we can run our trigger tool and record the stack address that the creds variable is located at, which will be consistent across all forked children.

(gdb) p &creds
$1 = (struct netlogon_creds_CredentialState **) 0x7fff7d712ca8

Once this packet is handled this smbd process will exit. So we'll have to re-attach to do our stack analysis. All of the earlier commands can just be loaded into a script that we load on gdb startup to avoiding having to type them more than once.

So how do we actually trace accesses now that we know the stack address? I use the following script, which I just load using source <script> every time I load up gdb:

set logging file stack_log
set logging on
set logging redirect on
set logging overwrite on
set pagination off

set $trace_addr=0x7fff7d712ca8
break smbd_accept_connection
commands 1
silent
set follow-fork-mode child
continue
end

break smbd_process
commands 2 
silent
set follow-fork-mode parent
continue
end

watch *$trace_addr
commands 3
silent
bt
x/gx $trace_addr
continue
end

Basically this gives us a backtrace every time that stack address is modified, as well dumping out whatever was written to the address. I also log all this to a file as it is quite noisy. The first thing we're interested in is telling whether or not any of the functions immediately leading up to _netr_ServerPasswordSet() are already writing to that stack address, as that scenario pretty much eliminates the possibility of using separate code paths to give us control.

A look at the backtraces from our script show that this is indeed the case. First we identify the actual call to _net_ServerPasswordSet() and then work backwards

#0  _netr_ServerPasswordSet (p=0x7f0a28676b50, r=0x7f0a2867ece0) at ../source3/rpc_server/netlogon/srv_netlog_nt.c:1278
#1  0x00007f0a259b5a88 in api_netr_ServerPasswordSet (p=0x7f0a28676b50) at default/librpc/gen_ndr/srv_netlogon.c:534
#2  0x00007f0a25b16972 in api_rpcTNP (p=0x7f0a28676b50, pkt=0x7f0a286776e0, api_rpc_cmds=0x7f0a25e8e220 <api_netlogon_cmds>, n_cmds=49, syntax=0x7f0a28677590) at ../source3/rpc_server/srv_pipe.c:1390
#3  0x00007f0a25b16510 in api_pipe_request (p=0x7f0a28676b50, pkt=0x7f0a286776e0) at ../source3/rpc_server/srv_pipe.c:1324
#4  0x00007f0a25b171f0 in process_request_pdu (p=0x7f0a28676b50, pkt=0x7f0a286776e0) at ../source3/rpc_server/srv_pipe.c:1571
#5  0x00007f0a25b17518 in process_complete_pdu (p=0x7f0a28676b50) at ../source3/rpc_server/srv_pipe.c:1625
#6  0x00007f0a25b1199d in process_incoming_data (p=0x7f0a28676b50, data=0x7f0a286721d8 "", n=68) at ../source3/rpc_server/srv_pipe_hnd.c:219
#7  0x00007f0a25b11aa8 in write_to_internal_pipe (p=0x7f0a28676b50, data=0x7f0a286721d8 "", n=84) at ../source3/rpc_server/srv_pipe_hnd.c:245
#8  0x00007f0a25b12450 in np_write_send (mem_ctx=0x7f0a286725b0, ev=0x7f0a2865e110, handle=0x7f0a28675a00, data=0x7f0a286721c8 " 05", len=84) at ../source3/rpc_server/srv_pipe_hnd.c:538
#9  0x00007f0a25aa1529 in smbd_smb2_ioctl_send (mem_ctx=0x7f0a28672010, ev=0x7f0a2865e110, smb2req=0x7f0a28672010, fsp=0x7f0a28675800, in_ctl_code=1163287, in_input=..., in_max_output=65535, in_flags=1) at ../source3/smbd/smb2_ioctl.c:476
#10 0x00007f0a25aa065f in smbd_smb2_request_process_ioctl (req=0x7f0a28672010) at ../source3/smbd/smb2_ioctl.c:217
#11 0x00007f0a25a8b46e in smbd_smb2_request_dispatch (req=0x7f0a28672010) at ../source3/smbd/smb2_server.c:2194
#12 0x00007f0a25a8ebfc in smbd_smb2_io_handler (sconn=0x7f0a2866feb0, fde_flags=1) at ../source3/smbd/smb2_server.c:3269
#13 0x00007f0a25a8ed03 in smbd_smb2_connection_handler (ev=0x7f0a2865e110, fde=0x7f0a2866f9c0, flags=1, private_data=0x7f0a2866feb0) at ../source3/smbd/smb2_server.c:3307
#14 0x00007f0a24497a73 in run_events_poll (ev=0x7f0a2865e110, pollrtn=1, pfds=0x7f0a2866c210, num_pfds=3) at ../source3/lib/events.c:257
#15 0x00007f0a24497cff in s3_event_loop_once (ev=0x7f0a2865e110, location=0x7f0a25bdd808 "../source3/smbd/process.c:3693") at ../source3/lib/events.c:326
#16 0x00007f0a246e50c3 in _tevent_loop_once (ev=0x7f0a2865e110, location=0x7f0a25bdd808 "../source3/smbd/process.c:3693") at ../lib/tevent/tevent.c:530
#17 0x00007f0a25a6e8db in smbd_process (ev_ctx=0x7f0a2865e110, msg_ctx=0x7f0a2865e1f0, sock_fd=34, interactive=false) at ../source3/smbd/process.c:3693
#18 0x00007f0a2651b929 in smbd_accept_connection (ev=0x7f0a2865e110, fde=0x7f0a2866f7f0, flags=1, private_data=0x7f0a2866e320) at ../source3/smbd/server.c:610
#19 0x00007f0a24497a73 in run_events_poll (ev=0x7f0a2865e110, pollrtn=1, pfds=0x7f0a2866c210, num_pfds=5) at ../source3/lib/events.c:257
#20 0x00007f0a24497cff in s3_event_loop_once (ev=0x7f0a2865e110, location=0x7f0a2652022e "../source3/smbd/server.c:931") at ../source3/lib/events.c:326
#21 0x00007f0a246e50c3 in _tevent_loop_once (ev=0x7f0a2865e110, location=0x7f0a2652022e "../source3/smbd/server.c:931") at ../lib/tevent/tevent.c:530
#22 0x00007f0a2651c58a in smbd_parent_loop (ev_ctx=0x7f0a2865e110, parent=0x7f0a28661530) at ../source3/smbd/server.c:931
#23 0x00007f0a2651de6c in main (argc=3, argv=0x7fff7d713fa8) at ../source3/smbd/server.c:1565
0x7fff7d712ca8:	0x0000000000000000

The above shows that this variable was set to 0 inside _netr_ServerPasswordSet(). This is expected as TALLOC_FREE() which is called on the uninitialized variable will set the variable to NULL after being called. So what happens before?

#0  _talloc_zero (ctx=0x7f0a2867ece0, size=16, name=0x7f0a25bad5d1 "struct netr_Authenticator") at ../lib/talloc/talloc.c:1898
#1  0x00007f0a259b5a3f in api_netr_ServerPasswordSet (p=0x7f0a28676b50) at default/librpc/gen_ndr/srv_netlogon.c:528
#2  0x00007f0a25b16972 in api_rpcTNP (p=0x7f0a28676b50, pkt=0x7f0a286776e0, api_rpc_cmds=0x7f0a25e8e220 <api_netlogon_cmds>, n_cmds=49, syntax=0x7f0a28677590) at ../source3/rpc_server/srv_pipe.c:1390
#3  0x00007f0a25b16510 in api_pipe_request (p=0x7f0a28676b50, pkt=0x7f0a286776e0) at ../source3/rpc_server/srv_pipe.c:1324
#4  0x00007f0a25b171f0 in process_request_pdu (p=0x7f0a28676b50, pkt=0x7f0a286776e0) at ../source3/rpc_server/srv_pipe.c:1571
#5  0x00007f0a25b17518 in process_complete_pdu (p=0x7f0a28676b50) at ../source3/rpc_server/srv_pipe.c:1625
#6  0x00007f0a25b1199d in process_incoming_data (p=0x7f0a28676b50, data=0x7f0a286721d8 "", n=68) at ../source3/rpc_server/srv_pipe_hnd.c:219
#7  0x00007f0a25b11aa8 in write_to_internal_pipe (p=0x7f0a28676b50, data=0x7f0a286721d8 "", n=84) at ../source3/rpc_server/srv_pipe_hnd.c:245
#8  0x00007f0a25b12450 in np_write_send (mem_ctx=0x7f0a286725b0, ev=0x7f0a2865e110, handle=0x7f0a28675a00, data=0x7f0a286721c8 " 05", len=84) at ../source3/rpc_server/srv_pipe_hnd.c:538
#9  0x00007f0a25aa1529 in smbd_smb2_ioctl_send (mem_ctx=0x7f0a28672010, ev=0x7f0a2865e110, smb2req=0x7f0a28672010, fsp=0x7f0a28675800, in_ctl_code=1163287, in_input=..., in_max_output=65535, in_flags=1) at ../source3/smbd/smb2_ioctl.c:476
#10 0x00007f0a25aa065f in smbd_smb2_request_process_ioctl (req=0x7f0a28672010) at ../source3/smbd/smb2_ioctl.c:217
#11 0x00007f0a25a8b46e in smbd_smb2_request_dispatch (req=0x7f0a28672010) at ../source3/smbd/smb2_server.c:2194
#12 0x00007f0a25a8ebfc in smbd_smb2_io_handler (sconn=0x7f0a2866feb0, fde_flags=1) at ../source3/smbd/smb2_server.c:3269
#13 0x00007f0a25a8ed03 in smbd_smb2_connection_handler (ev=0x7f0a2865e110, fde=0x7f0a2866f9c0, flags=1, private_data=0x7f0a2866feb0) at ../source3/smbd/smb2_server.c:3307
#14 0x00007f0a24497a73 in run_events_poll (ev=0x7f0a2865e110, pollrtn=1, pfds=0x7f0a2866c210, num_pfds=3) at ../source3/lib/events.c:257
#15 0x00007f0a24497cff in s3_event_loop_once (ev=0x7f0a2865e110, location=0x7f0a25bdd808 "../source3/smbd/process.c:3693") at ../source3/lib/events.c:326
#16 0x00007f0a246e50c3 in _tevent_loop_once (ev=0x7f0a2865e110, location=0x7f0a25bdd808 "../source3/smbd/process.c:3693") at ../lib/tevent/tevent.c:530
#17 0x00007f0a25a6e8db in smbd_process (ev_ctx=0x7f0a2865e110, msg_ctx=0x7f0a2865e1f0, sock_fd=34, interactive=false) at ../source3/smbd/process.c:3693
#18 0x00007f0a2651b929 in smbd_accept_connection (ev=0x7f0a2865e110, fde=0x7f0a2866f7f0, flags=1, private_data=0x7f0a2866e320) at ../source3/smbd/server.c:610
#19 0x00007f0a24497a73 in run_events_poll (ev=0x7f0a2865e110, pollrtn=1, pfds=0x7f0a2866c210, num_pfds=5) at ../source3/lib/events.c:257
#20 0x00007f0a24497cff in s3_event_loop_once (ev=0x7f0a2865e110, location=0x7f0a2652022e "../source3/smbd/server.c:931") at ../source3/lib/events.c:326
#21 0x00007f0a246e50c3 in _tevent_loop_once (ev=0x7f0a2865e110, location=0x7f0a2652022e "../source3/smbd/server.c:931") at ../lib/tevent/tevent.c:530
#22 0x00007f0a2651c58a in smbd_parent_loop (ev_ctx=0x7f0a2865e110, parent=0x7f0a28661530) at ../source3/smbd/server.c:931
#23 0x00007f0a2651de6c in main (argc=3, argv=0x7fff7d713fa8) at ../source3/smbd/server.c:1565
0x7fff7d712ca8:	0x00007f0a2867f1c0

Almost immediately before _netr_ServerPasswordSet() is called, we see that this stack variable was set inside _talloc_zero(). If we look at the source code for api_netr_ServerPasswordSet(), we can see where this _talloc_zero() call is:

r->out.return_authenticator = talloc_zero(r, struct netr_Authenticator);
	if (r->out.return_authenticator == NULL) {
		talloc_free(r);
		return false;
	}

	r->out.result = _netr_ServerPasswordSet(p, r);

This call to talloc_zero() is unavoidable and happens right before the vulnerable function. This implies that, at least with this particular stack setup for this binary (though I've tested it on others as well), we won't ever be able to prime the stack variable with our own data prior to _netr_ServerPasswordSet() being called, because it will always be overwritten by talloc_zero(). The talloc_zero() function only has a single local variable, which points to the chunk returned to r->out.return_authenticator, so we know that the uninitialized creds variable will point to this chunk at the time of free.

As far as stack depth analysis goes, this is pretty much as far as we need to go to determine that exploitation through variable priming is probably not going to be feasible. The analysis provided in [8] would seem to corroborate these findings.

Exploitability analysis

Now we've determined some clearer constraints related to this vulnerability. We can't control the stack variable with arbitrary data. We're limited to the fact that it will almost definitely be primed by talloc_zero(). This doesn't always mean exploitation is out of the question it just means we have to understand what the effects of an unexpected TALLOC_FREE() on a chunk is to see if it lets us do anything interesting.

One interesting thing I noticed in the stack depth output I had generated was the call immediately following _netr_ServerPasswordSet() wrote to the same stack variable again, and interestingly wrote the exact same pointer (0x00007f0a2867f1c0) that r->out.return_authenticator used to point to:

#0  ndr_push_init_ctx (mem_ctx=0x7f0a2867ece0) at ../librpc/ndr/ndr.c:112
#1  0x00007f0a259b5af2 in api_netr_ServerPasswordSet (p=0x7f0a28676b50) at default/librpc/gen_ndr/srv_netlogon.c:546
#2  0x00007f0a25b16972 in api_rpcTNP (p=0x7f0a28676b50, pkt=0x7f0a286776e0, api_rpc_cmds=0x7f0a25e8e220 <api_netlogon_cmds>, n_cmds=49, syntax=0x7f0a28677590) at ../source3/rpc_server/srv_pipe.c:1390
#3  0x00007f0a25b16510 in api_pipe_request (p=0x7f0a28676b50, pkt=0x7f0a286776e0) at ../source3/rpc_server/srv_pipe.c:1324
#4  0x00007f0a25b171f0 in process_request_pdu (p=0x7f0a28676b50, pkt=0x7f0a286776e0) at ../source3/rpc_server/srv_pipe.c:1571
#5  0x00007f0a25b17518 in process_complete_pdu (p=0x7f0a28676b50) at ../source3/rpc_server/srv_pipe.c:1625
#6  0x00007f0a25b1199d in process_incoming_data (p=0x7f0a28676b50, data=0x7f0a286721d8 "", n=68) at ../source3/rpc_server/srv_pipe_hnd.c:219
#7  0x00007f0a25b11aa8 in write_to_internal_pipe (p=0x7f0a28676b50, data=0x7f0a286721d8 "", n=84) at ../source3/rpc_server/srv_pipe_hnd.c:245
#8  0x00007f0a25b12450 in np_write_send (mem_ctx=0x7f0a286725b0, ev=0x7f0a2865e110, handle=0x7f0a28675a00, data=0x7f0a286721c8 " 05", len=84) at ../source3/rpc_server/srv_pipe_hnd.c:538
#9  0x00007f0a25aa1529 in smbd_smb2_ioctl_send (mem_ctx=0x7f0a28672010, ev=0x7f0a2865e110, smb2req=0x7f0a28672010, fsp=0x7f0a28675800, in_ctl_code=1163287, in_input=..., in_max_output=65535, in_flags=1) at ../source3/smbd/smb2_ioctl.c:476
#10 0x00007f0a25aa065f in smbd_smb2_request_process_ioctl (req=0x7f0a28672010) at ../source3/smbd/smb2_ioctl.c:217
#11 0x00007f0a25a8b46e in smbd_smb2_request_dispatch (req=0x7f0a28672010) at ../source3/smbd/smb2_server.c:2194
#12 0x00007f0a25a8ebfc in smbd_smb2_io_handler (sconn=0x7f0a2866feb0, fde_flags=1) at ../source3/smbd/smb2_server.c:3269
#13 0x00007f0a25a8ed03 in smbd_smb2_connection_handler (ev=0x7f0a2865e110, fde=0x7f0a2866f9c0, flags=1, private_data=0x7f0a2866feb0) at ../source3/smbd/smb2_server.c:3307
#14 0x00007f0a24497a73 in run_events_poll (ev=0x7f0a2865e110, pollrtn=1, pfds=0x7f0a2866c210, num_pfds=3) at ../source3/lib/events.c:257
#15 0x00007f0a24497cff in s3_event_loop_once (ev=0x7f0a2865e110, location=0x7f0a25bdd808 "../source3/smbd/process.c:3693") at ../source3/lib/events.c:326
#16 0x00007f0a246e50c3 in _tevent_loop_once (ev=0x7f0a2865e110, location=0x7f0a25bdd808 "../source3/smbd/process.c:3693") at ../lib/tevent/tevent.c:530
#17 0x00007f0a25a6e8db in smbd_process (ev_ctx=0x7f0a2865e110, msg_ctx=0x7f0a2865e1f0, sock_fd=34, interactive=false) at ../source3/smbd/process.c:3693
#18 0x00007f0a2651b929 in smbd_accept_connection (ev=0x7f0a2865e110, fde=0x7f0a2866f7f0, flags=1, private_data=0x7f0a2866e320) at ../source3/smbd/server.c:610
#19 0x00007f0a24497a73 in run_events_poll (ev=0x7f0a2865e110, pollrtn=1, pfds=0x7f0a2866c210, num_pfds=5) at ../source3/lib/events.c:257
#20 0x00007f0a24497cff in s3_event_loop_once (ev=0x7f0a2865e110, location=0x7f0a2652022e "../source3/smbd/server.c:931") at ../source3/lib/events.c:326
#21 0x00007f0a246e50c3 in _tevent_loop_once (ev=0x7f0a2865e110, location=0x7f0a2652022e "../source3/smbd/server.c:931") at ../lib/tevent/tevent.c:530
#22 0x00007f0a2651c58a in smbd_parent_loop (ev_ctx=0x7f0a2865e110, parent=0x7f0a28661530) at ../source3/smbd/server.c:931
#23 0x00007f0a2651de6c in main (argc=3, argv=0x7fff7d713fa8) at ../source3/smbd/server.c:1565
0x7fff7d712ca8:	0x00007f0a2867f1c0

The call above is basically allocated an ndr_push structure used for building the response packet that is sent to the client. The function looks like this:

/* create a ndr_push structure, ready for some marshalling */
_PUBLIC_ struct ndr_push *ndr_push_init_ctx(TALLOC_CTX *mem_ctx)
{
	struct ndr_push *ndr;

	ndr = talloc_zero(mem_ctx, struct ndr_push);
	if (!ndr) {
		return NULL;
	}

	ndr->flags = 0;
	ndr->alloc_size = NDR_BASE_MARSHALL_SIZE;
	ndr->data = talloc_array(ndr, uint8_t, ndr->alloc_size);
	if (!ndr->data) {
		return NULL;
	}

	return ndr;
}

The part overwriting the stack location is the assignment to the ndr variable above. What's interesting about this from our perspective is that it means that r->out.return_authenticator contains a stale reference to a now in-use chunk that won't be the zeroed buffer it originally pointed to. Let's try to understand how r->out.return_authenticator is actually used...

r->out.result = _netr_ServerPasswordSet(p, r);

	if (p->fault_state) {
		talloc_free(r);
		/* Return true here, srv_pipe_hnd.c will take care */
		return true;
	}

	if (DEBUGLEVEL >= 10) {
		NDR_PRINT_FUNCTION_DEBUG(netr_ServerPasswordSet, NDR_OUT | NDR_SET_VALUES, r);
	}

	push = ndr_push_init_ctx(r);
	if (push == NULL) {
		talloc_free(r);
		return false;
	}

	/*
	 * carry over the pointer count to the reply in case we are
	 * using full pointer. See NDR specification for full pointers
	 */
	push->ptr_count = pull->ptr_count;

	ndr_err = call->ndr_push(push, NDR_OUT, r);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		talloc_free(r);
		return false;
	}

In the code snippet above we see the call to our vulnerable function which freed r->out.return_authenticator's chunk early. We see the call to ndr_push_init_ctx() which allocates and returns this recently freed chunk use for use by the push variable. Then a call into the command-specific ndr_push function pointer is called. This corresponds to the ndr_push_netr_ServerPasswordSet() function. Basically this just builds a response packet. It does reference r->out.return_authenticator however:

static enum ndr_err_code ndr_push_netr_ServerPasswordSet(struct ndr_push *ndr, int flags, const struct netr_ServerPasswordSet *r)
{
	NDR_PUSH_CHECK_FN_FLAGS(ndr, flags);
	if (flags & NDR_IN) {
        [SNIP]
        }
	if (flags & NDR_OUT) {
		if (r->out.return_authenticator == NULL) {
			return ndr_push_error(ndr, NDR_ERR_INVALID_POINTER, "NULL [ref] pointer");
		}
		NDR_CHECK(ndr_push_netr_Authenticator(ndr, NDR_SCALARS, r->out.return_authenticator));
		NDR_CHECK(ndr_push_NTSTATUS(ndr, NDR_SCALARS, r->out.result));
	}
	return NDR_ERR_SUCCESS;
}

Remember that ndr_push is perfectly fine to use, as it was a legitimately free chunk. The only interesting part is the use of r->out.return_authenticator. We see that it is passed to the ndr_push_netr_Authenticator() function, which does the following:

_PUBLIC_ enum ndr_err_code ndr_push_netr_Authenticator(struct ndr_push *ndr, int ndr_flags, const struct netr_Authenticator *r)
{
	NDR_PUSH_CHECK_FLAGS(ndr, ndr_flags);
	if (ndr_flags & NDR_SCALARS) {
		NDR_CHECK(ndr_push_align(ndr, 4));
		NDR_CHECK(ndr_push_netr_Credential(ndr, NDR_SCALARS, &r->cred));
		NDR_CHECK(ndr_push_time_t(ndr, NDR_SCALARS, r->timestamp));
		NDR_CHECK(ndr_push_trailer_align(ndr, 4));
	}
	if (ndr_flags & NDR_BUFFERS) {
	}
	return NDR_ERR_SUCCESS;
}

Basically what this does is marshals the content of what it thinks is a netr_Authenticator structure into the response packet. What's interesting about this is that r->out.return_authenticator now points to the ndr pointer itself, which means that this will actually leak data from the ndr_push structure rather than the zeroed buffer allocated originally by talloc_zero(). Note that we know it would've been zero here because _netr_ServerPasswordSet() failed and didn't actually use the creds pointer at all, meaning that nothing was written into it.

An example of what is leaked can be seen in the following GDB output. Note that the ndr and r->out.return_authenticator address differs from previous examples, as I generated this during a different debugging session:

(gdb) p *ndr
$1 = {flags = 0, data = 0x7f0e20680510 "", alloc_size = 1024, offset = 0, relative_base_offset = 0, 
  relative_end_offset = 0, relative_base_list = 0x0, switch_list = 0x0, relative_list = 0x0, 
  relative_begin_list = 0x0, nbt_string_list = 0x0, dns_string_list = 0x0, full_ptr_list = 0x0, ptr_count = 0}
(gdb) p *r
$2 = {cred = {data = " 00 00 00 00 00 00 00"}, timestamp = 139698649957648}
(gdb) p/x 139698649957648
$3 = 0x7f0e20680510
(gdb) x/10x ndr
0x7f0e20680460:	0x00000000	0x00000000	0x20680510	0x00007f0e

So the packet response will contain 8 zeroes in data, and the lower 32-bits of the ndr->data pointer in timestamp. This at the very least would reduce the number of bits needed to bruteforce heap ASLR on a 64-bit machine to 20 bits instead of 28. You can confirm this data is in the packet by looking at wireshark. I show an example image below. You can see that Credential: and Timestamp: are non-zero. These correspond to the 12-bytes prior to the highlighted return code. In this case Timestamp: holds 0x20680510, which you can see matches the lower 32-bits of the address how in the gdb output above.

Assuming you have a similar stack layout and smbd doesn’t crash, you could also use this memory revelation to detect if the system is vulnerable to the bug by checking the data and timestamp fields to see if they are non-NULL.

Okay, so there is at least a minor implication of the vulnerability, but we were hoping for something more. What happens when r->out.return_authenticator AND ndr are freed? You’d think this should be a double free, which could be interesting. Sadly not. This is because the chunk is never explicitly freed using these pointers. This is due to the way the talloc algorithm is designed and the recommended way to tear down memory contexts. Essentially it maintains a tree of allocations associated with the api_netr_ServerPasswordSet() function, and when this function exits it simply tears down the entire memory context. This can be seen after the call->ndr_push() function returns.

static bool api_netr_ServerPasswordSet(struct pipes_struct *p)
{
	const struct ndr_interface_call *call;
	struct ndr_pull *pull;
	struct ndr_push *push;
	enum ndr_err_code ndr_err;
	struct netr_ServerPasswordSet *r;

	call = &ndr_table_netlogon.calls[NDR_NETR_SERVERPASSWORDSET];

	r = talloc(talloc_tos(), struct netr_ServerPasswordSet);
	if (r == NULL) {
		return false;
	}
[...]
	talloc_free(r);

What's shown above is the memory context allocation for the api_netr_ServerPasswordSet() function and the only explicit free of the context.

So instead of referencing each pointer lying around in various variables, it simply walks the memory context tree and removes every child. This means that the chunk in question is only ever removed once. When the r->out.return_authenticator pointer was freed early, that reference was removed from the tree. So only the new ndr_push copy exists in the tree, which can be legitimately removed. This might seem somewhat confusing, as it's unlike most heaps I've looked at, but I recommend reading the official talloc tutorial [6] for some background.

The fact the early TALLOC_FREE() itself can't really be manipulated to corrupt the state of the heap in anyway makes it seem like there aren't any real options for exploitation beyond the aforementioned memory revelation.

To help understand what's happening with the talloc memory context trees, take a look at the following output snippets from the talloc_report_full() [7] function. Again, the addresses differ from previous examples, because I generated these at a different time.

Prior to the TALLOC_FREE(creds) call:

full talloc report on 'struct netr_ServerPasswordSet' (total    354 bytes in  12 blocks)
    struct netr_Authenticator      contains     16 bytes in   1 blocks (ref 0) 0x7f39519e9660
    default/librpc/gen_ndr/ndr_netlogon.c:11135 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e9600
    default/librpc/gen_ndr/ndr_netlogon.c:11129 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e95a0
    default/librpc/gen_ndr/ndr_netlogon.c:11122 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e9540
                                   contains      1 bytes in   1 blocks (ref 0) 0x7f39519e94e0
                                   contains      1 bytes in   1 blocks (ref 0) 0x7f39519e93a0
    struct ndr_pull                contains    224 bytes in   5 blocks (ref 0) 0x7f39519e9210
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9470
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9400
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9330
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e92c0

You can see the root of the memory context is struct netr_ServerPasswordSet, allocated by api_netr_ServerPasswordSet(). You can also see that the struct netr_Authenticator entry, corresponding to r->out.return_authenticator, points to 0x7f39519e9660.

After the TALLOC_FREE(creds) call, where creds references the uninitialized stack value holding 0x7f39519e9660, the tree will look like this:

full talloc report on 'struct netr_ServerPasswordSet' (total    338 bytes in  11 blocks)
    default/librpc/gen_ndr/ndr_netlogon.c:11135 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e9600
    default/librpc/gen_ndr/ndr_netlogon.c:11129 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e95a0
    default/librpc/gen_ndr/ndr_netlogon.c:11122 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e9540
                                   contains      1 bytes in   1 blocks (ref 0) 0x7f39519e94e0
                                   contains      1 bytes in   1 blocks (ref 0) 0x7f39519e93a0
    struct ndr_pull                contains    224 bytes in   5 blocks (ref 0) 0x7f39519e9210
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9470
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9400
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9330
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e92c0

Note the old netr_Authenticator entry has been removed due to the vulnerability, as expected. Next let’s look at the tree after the ndr_push structure is allocated:

full talloc report on 'struct netr_ServerPasswordSet' (total   1458 bytes in  13 blocks)
    struct ndr_push                contains   1120 bytes in   2 blocks (ref 0) 0x7f39519e9660
        uint8_t                        contains   1024 bytes in   1 blocks (ref 0) 0x7f39519e9710
    default/librpc/gen_ndr/ndr_netlogon.c:11135 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e9600
    default/librpc/gen_ndr/ndr_netlogon.c:11129 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e95a0
    default/librpc/gen_ndr/ndr_netlogon.c:11122 contains     16 bytes in   1 blocks (ref 0) 0x7f39519e9540
                                   contains      1 bytes in   1 blocks (ref 0) 0x7f39519e94e0
                                   contains      1 bytes in   1 blocks (ref 0) 0x7f39519e93a0
    struct ndr_pull                contains    224 bytes in   5 blocks (ref 0) 0x7f39519e9210
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9470
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9400
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e9330
        struct ndr_token_list          contains     32 bytes in   1 blocks (ref 0) 0x7f39519e92c0

You see that the ndr_push structure has re-used the chunk that we freed early, at 0x7f39519e9660. Also note that the uint8_t child of ndr_push is the ndr->data pointer, which we leaked the lower 32-bits from.

What's important to realize about the last tree shown above is that when talloc_free(r) is called in api_netr_ServerPasswordSet() (as shown earlier), there are not two references to this chunk that would cause a double free. It simply walks through and frees all the children of the root chunk. This implies to me that we can't actually meaningfully cause any sort of heap corruption that we'd like in order to mess with program state.

I'm not intimately familiar with talloc so again it's possible I'm overlooking something that means exploitation might be possible. I'd be happy to hear there is a way.

Notes on 32-bit

Note that analysis below corroborates the findings in [8]. I still included this just to show how the stack depth analysis could be used to find the same result.

Also, in theory on 32-bit the memory revelation described above would leak the full heap address, which would be useful. However this would be dependent on the stack layout, which my analysis and [8] both show to simply crash.

Pre-crash:

(gdb) p &creds
$1 = (struct netlogon_creds_CredentialState **) 0xbf969898
(gdb) x/100x $esp-0x20
0xbf969840:	0x00000000	0x00000000	0xb9378150	0xb9378150
0xbf969850:	0xb73e6000	0xb936e3d0	0xbf969888	0xb7461cec
0xbf969860:	0xb9378180	0xb7665441	0x00000038	0xb73e00a7
0xbf969870:	0x00000000	0x00000000	0x00000000	0xb9378180
0xbf969880:	0x00000000	0xb6b7fd25	0xb73e6000	0xb73e21f1
0xbf969890:	0xb9378180	0x00000000	0x0000000c	0xb9377e80
0xbf9698a0:	0x00000000	0x00000020	0xb76653b6	0xb773e000

Note in the data above, &creds points to the value 0x0000000c. The following is a talloc_report_full() call to show what some of these values are. For instance you can see that we're a bit further down the stack (variable wise) than the talloc address we were observing before when looking.

full talloc report on 'struct netr_ServerPasswordSet' (total    206 bytes in  12 blocks)
    struct netr_Authenticator      contains     12 bytes in   1 blocks (ref 0) 0xb9378180
    default/librpc/gen_ndr/ndr_netlogon.c:11135 contains     12 bytes in   1 blocks (ref 0) 0xb9378140
    default/librpc/gen_ndr/ndr_netlogon.c:11129 contains     16 bytes in   1 blocks (ref 0) 0xb9378100
    default/librpc/gen_ndr/ndr_netlogon.c:11122 contains     12 bytes in   1 blocks (ref 0) 0xb93780c0
                                   contains      1 bytes in   1 blocks (ref 0) 0xb9378080
                                   contains      1 bytes in   1 blocks (ref 0) 0xb9377fc0
    struct ndr_pull                contains    120 bytes in   5 blocks (ref 0) 0xb9377ed0
        struct ndr_token_list          contains     16 bytes in   1 blocks (ref 0) 0xb9378040
        struct ndr_token_list          contains     16 bytes in   1 blocks (ref 0) 0xb9378000
        struct ndr_token_list          contains     16 bytes in   1 blocks (ref 0) 0xb9377f80
        struct ndr_token_list          contains     16 bytes in   1 blocks (ref 0) 0xb9377f40

One thing we notice is that 0xc corresponds to the 12 byte size of the netr_Authenticator allocation, so my assumption was in this case it is just a local length parameter deeper in talloc_zero() function that is priming this variable, and still not very promising.

Crash:

Program received signal SIGSEGV, Segmentation fault.
0xb73dfa7b in talloc_chunk_from_ptr (ptr=0xc) at ../lib/talloc/talloc.c:349
349		if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) { 
(gdb) bt
#0  0xb73dfa7b in talloc_chunk_from_ptr (ptr=0xc) at ../lib/talloc/talloc.c:349
#1  0xb73e1425 in _talloc_free (ptr=0xc, location=0xb7663b64 "../source3/rpc_server/netlogon/srv_netlog_nt.c:1277")
    at ../lib/talloc/talloc.c:1349
#2  0xb7461e43 in _netr_ServerPasswordSet (p=0xb936e378, r=0xb9377e80)

Doing our ghetto stack depth analysis shows that it is indeed _talloc_zero() that's doing it:

#0  0xb732c1de in _talloc_zero (ctx=0xb8cd5b60, size=12, name=0xb75af441 "struct netr_Authenticator") at ../lib/talloc/talloc.c:1899
#1  0xb73afe7e in api_netr_ServerPasswordSet (p=0xb8ccc3b0) at default/librpc/gen_ndr/srv_netlogon.c:528
#2  0xb7516b7d in api_rpcTNP (p=0xb8ccc3b0, pkt=0xb8cccc58, api_rpc_cmds=0xb768a140 <api_netlogon_cmds>, n_cmds=49, syntax=0xb8ccca24) at ../source3/rpc_server/srv_pipe.c:1390
#3  0xb751673a in api_pipe_request (p=0xb8ccc3b0, pkt=0xb8cccc58) at ../source3/rpc_server/srv_pipe.c:1324
#4  0xb75173ee in process_request_pdu (p=0xb8ccc3b0, pkt=0xb8cccc58) at ../source3/rpc_server/srv_pipe.c:1571
#5  0xb7517711 in process_complete_pdu (p=0xb8ccc3b0) at ../source3/rpc_server/srv_pipe.c:1625
#6  0xb75119f6 in process_incoming_data (p=0xb8ccc3b0, data=0xb8cc8600 "", n=68) at ../source3/rpc_server/srv_pipe_hnd.c:219
#7  0xb7511b07 in write_to_internal_pipe (p=0xb8ccc3b0, data=0xb8cc8600 "", n=84) at ../source3/rpc_server/srv_pipe_hnd.c:245
#8  0xb7512471 in np_write_send (mem_ctx=0xb8cc8888, ev=0xb8cbaf40, handle=0xb8ccc1c8, data=0xb8cc85f0 " 05", len=84) at ../source3/rpc_server/srv_pipe_hnd.c:538
#9  0xb749da17 in smbd_smb2_ioctl_send (mem_ctx=0xb8cc84c8, ev=0xb8cbaf40, smb2req=0xb8cc84c8, fsp=0xb8ccb2c8, in_ctl_code=1163287, in_input=..., in_max_output=65535, in_flags=1) at ../source3/smbd/smb2_ioctl.c:476
#10 0xb749cb58 in smbd_smb2_request_process_ioctl (req=0xb8cc84c8) at ../source3/smbd/smb2_ioctl.c:217
#11 0xb7487438 in smbd_smb2_request_dispatch (req=0xb8cc84c8) at ../source3/smbd/smb2_server.c:2194
#12 0xb748a663 in smbd_smb2_io_handler (sconn=0xb8cc7478, fde_flags=1) at ../source3/smbd/smb2_server.c:3269
#13 0xb748a77d in smbd_smb2_connection_handler (ev=0xb8cbaf40, fde=0xb8cc3c90, flags=1, private_data=0xb8cc7478) at ../source3/smbd/smb2_server.c:3307
#14 0xb70d8b62 in run_events_poll (ev=0xb8cbaf40, pollrtn=1, pfds=0xb8cc7e80, num_pfds=3) at ../source3/lib/events.c:257
#15 0xb70d8e2c in s3_event_loop_once (ev=0xb8cbaf40, location=0xb75dd520 "../source3/smbd/process.c:3693") at ../source3/lib/events.c:326
#16 0xb7122239 in _tevent_loop_once (ev=0xb8cbaf40, location=0xb75dd520 "../source3/smbd/process.c:3693") at ../lib/tevent/tevent.c:530
#17 0xb7469945 in smbd_process (ev_ctx=0xb8cbaf40, msg_ctx=0xb8cbafc0, sock_fd=34, interactive=false) at ../source3/smbd/process.c:3693
#18 0xb772579e in smbd_accept_connection (ev=0xb8cbaf40, fde=0xb8cc6e68, flags=1, private_data=0xb8cc6de0) at ../source3/smbd/server.c:610
#19 0xb70d8b62 in run_events_poll (ev=0xb8cbaf40, pollrtn=1, pfds=0xb8cc7e80, num_pfds=5) at ../source3/lib/events.c:257
#20 0xb70d8e2c in s3_event_loop_once (ev=0xb8cbaf40, location=0xb772a1b6 "../source3/smbd/server.c:931") at ../source3/lib/events.c:326
#21 0xb7122239 in _tevent_loop_once (ev=0xb8cbaf40, location=0xb772a1b6 "../source3/smbd/server.c:931") at ../lib/tevent/tevent.c:530
#22 0xb77264ab in smbd_parent_loop (ev_ctx=0xb8cbaf40, parent=0xb8cbb9f8) at ../source3/smbd/server.c:931
#23 0xb7727f70 in main (argc=3, argv=0xbfaf88d4) at ../source3/smbd/server.c:1565
0xbfaf7a98:     0x0000000c

The line corresponds to this functionality of talloc_zero():

        if (p) {
                memset(p, ' ', size);
        }

We know that the size of the chunk is 12, corresponding to the value written, so it stands to reason this is being pushed as part of this call. As [8] noted, this is indeed the case.

Testing

Not only can you use the PoC from [8] to trigger the bug, but in some situations, like on a 64-bit system demonstrated earlier, you could detect a vulnerable system. This is possible because triggering the bug could up leaking some heap data in the packet response, which should be NULL if the requested failed.

Conclusion

So we can trigger the bug, but we don't really have ideal conditions for exploiting this class of vulnerability. The use of talloc by Samba actually seems to help make exploitation seem improbable, which is interesting because other features of talloc have made exploitation easier in the past; namely the deconstructor function pointer located in the chunk header.

Also kudos to sleepya for releasing the PoC and 32-bit analysis in [8].

Resources

  1. https://www.samba.org/samba/security/CVE-2015-0240
  2. https://securityblog.redhat.com/2015/02/23/samba-vulnerability-cve-2015-0240/
  3. http://corelabs.coresecurity.com/index.php?module=Wiki&action=view&type=tool&name=Impacket
  4. https://msdn.microsoft.com/en-us/library/cc237244.aspx
  5. https://www.blackhat.com/presentations/bh-europe-06/bh-eu-06-Flake.pdf
  6. https://talloc.samba.org/talloc/doc/html/libtalloc__tutorial.html
  7. https://talloc.samba.org/talloc/doc/html/group__talloc__debug.html#ga66136eb6105416bfcbd390ce6a4fc89c
  8. https://gist.github.com/worawit/33cc5534cb555a0b710b

Published date:  02 March 2015

Written by:  Information Security Expert

Call us before you need us.

Our experts will help you.

Get in touch