DCE/RPC over SMB - nt login, code walk-through.
Luke Kenneth Casson Leighton
lkcl at samba.org
Mon Feb 14 04:54:38 GMT 2000
stage 2. what happens when rpcclient -S . -U% -l log is used and an
lsaquery command issued. server-side.
rrpcclient -S . means connection on loop-back. so we're bypassing SMB,
for now.
inside lsarpcd, firs thing is that open_sockets() waits for an msrpc
connection (msrpcd.c). the next critical function to be reached is
msrpcd_init().
msrpcd_init() calls get_user_creds(). this reads in s vuser_key, pid +
vuid. strictly speaking, this stage is now redundant (i keep telling
myself that, and i get it wrong, every time :-) :-) however, i'll cover
this in the SMB section. damn damn why am i so stupid :)
*sigh*. ok. so msrpcd_init() gets user credentials, and then looks up
the pid+vuid with get_valid_user_strcut() and does a boecome_user(),
setting the vuser context and the unix context to that contained in
user_struct. as this is a call from rpcclient as root, this enables us to
do an "su" if we specify -Usomeotheruser. but let's leave that aside for
now.
the next critical call is msrpcd_process() which goes into a while-loop on
receive_message_or_msrpc and then calling process_msrpc on it.
receive_message_or_msrpc calls receive_msrpc, which is the counter-part of
msrpc_send(), client-side.
process_msrpc() is where the fun starts. rpc_local() is called, which
either adds the received PDU to the amounting stack of data or calls
rpc_redir_local(). rpc_redir_local() is where the down-and-dirty happens,
so we'll skip that for now. back out to process_msrpc(). the next
function to be called is msrpc_send() and there then follows a series of
loops on rpc_local() followed by msrpc_send().
rpc_local() is responsible for picking up PDUs and assembling them, and
processing them one-at-a-time in rpc_redir_local(). as a *consequence* of
calling rpc_redir_local(), data may be produced in the form of PDUs to be
send back with msrpc_send().
so this complex loop, here, is a state-machine version of the logic in
rpc_api_pipe_req() which can send one-or-more PDUs and receive one-or-more
PDUs. except that this logic _also_ has to cope with receiving
one-or-more PDUs and *not* sending a PDU back (the PDU_3WAY_AUTH for
challenge/response NTLMSSP sequences).
glad that's out the way. on with the fun. rpc_redir_local().
rpc_redir_local() accepts a data stream of pre-assembled marshalled data
with the DCE/RPC headers stripped out. *one* of the eaders is preserved
intact so that the PDU type can be identified.
in the example we are following, we will be expecting to receive a bind
request. tracing the function calls, we come to
srv_pipe_bind_and_alt_req(). there are some critical noteworthy things
here, aside from the "authentication" process being handed off to
higher-order-functions. i picked this example as an anonymous connection
so that we won't have to go through these, for now. the critical things
to note are that assoc_gid is initialised from the vuser key that was
created from the incoming loop-back connection. if the DCE/RPC bind
request contains a non-NULL assoc_gid, then this is used instead.
why? because this is the first part of the vuser_key. remember, in the
bind_request from the client, we send the smbd pid in the assoc_gid, and
the smbd vuid in the context_id. well, here we're picking it up again.
rreconstruction of this info is critical, as it is used to form the right
user context, picked up from the get_valid_user_struct() database
(implemented with tdb, and the pid+vuid as the database key).
in this example, the key is assembled but nothing is actually done with it
-- right now. it just contains the same vuser_key info that rpcclient
created for us.
the call to api_auth_gen (a higher-order-function) creates the
bind-response for us, just like api_auth_check() decoded the bund-request
for us. back out of srv_pipe_bind_and_alt_req(). send the bind response.
if anyone's interested, the default for non-authenticated bind / bind ack
processing is done in srv_pipe_noauth.c.
back to rpc_redir_local(). ignoring all the fragment reassembly, the
first next call we will receive is an actual dce/rpc request: an
lsa_open_policy(). wow! so, first we obtain the context_id (see switch
RPC_REQUEST). the context_id contains the smbd vuid, therefore we use
this to create a vuser_key and do a become_vuser() call. if this fails,
we do a become_guest() instead. following this, an api_pipe_request() is
made.
api_pipe_request() jumps us to a higher-order-function table which takes
us through to lsarpcd/srv_lsa.c this is an lsa_open_policy() call from
the client, so api_lsa_open_policy() is called, here. _now_ we're getting
somewhere. note the similarity between this function and
lsa_open_policy() in cli_lsarpc.c? we've come _full_ damn circle just to
call a function with exactly the same arguments as lsa_open_policy()...
....
*blink*
hey, we're calling exactly the same function, except it's over a network.
that's so cool! except, like, it's got an underscore in front of it.
anyway, in to _lsa_open_policy().
_lsa_open_policy() is actually very simple. it ... well... opens a policy
handle. this does a create_policy_hnd() call and then calls
register_policy_hnd().
hey, this is deja-vu. we've been here before. didn't we _already_ call
register_policy_hnd() NO - we didn't... but lsa_open_policy() did, back
in rpcclient. so efffectively what we are doing here is to register the
same info as what the client is doing: matching up info into policy
handles.
the thing to note here is that _unfortunately, the call to
ope_policy_hnd() had to be made with a get_sec_ctx() function. this picks
up the vuser_key from the last become_vuser() call that was made.
threading is therefore going to be a damn nuisance. i _can_ think of a
way round this, it will require another sed script mod, passing the
vuser_key down the the chain into srv_lsa.c and all other similar files.
next important event is _lsa_query_info_policy(). other than receiving
the info level and sending back the structure, the only noteworthy DCE/RPC
activity carried out by this function is to check that the policy handle
is valid. we _cannot_ just hand out info to any old POLICY_HND, as in a
"real" implementation, we would obtain the vuser_key, obtain the user's
SID from the user_struct, and call check_security_permissions() here
inside _lsa_query_info_policy().
however, this information is available anonymously, so we don't need to do
that! but really, it's not the proper way to do this. i'm getting
side-tracked by implementation issues, here, and this is about DCE/RPC
itself, not DCE/RPC services.
next important event is the _lsa_close() call, which receives a policy
handle. again, we call close_policy_hnd() which calls, if it existed in
this example, the free_fn associated with the data. in _this_ example, in
the _lsa_open_policy() implementation we _didn't_ associate any data with
the policy handle, therefore there is nothing to do. however, this is
generally not the case: there useually is data associated with policy
handles in server-side implementation of dce/rpc services.
the one significant thing i am missing from the
associate-data-with-policy-handle "thing" is a type field. i ran into
difficulties with this in the samrtdbd implementation, over the
_samr_query_sec_object function. it';s possible to query the security
descriptor of *any *POLICY_HND, not just users, not just groups. so i
either have to deal with this, or set a "type" in the API, not just a
free-fn and a pointer-to-data.
this is actually quite important. because if someone does this:
samr_open_user( &pol_hnd)
samr_query_group_info(pol_hnd , ...)
this will result in the pointer-t--data associated with the open user
being typecast to a *group* data structure, which is _seriously_ bad
news..
anyway, i think i've covered all the DCE/RPC-related issues, here, i don't
want to get too into service-related implementation issues at this point.
i will think about what to write up, next.
luke
More information about the samba-technical
mailing list