From 31a6680dd5a0d15c066d41633d516f8afe545c9d Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Thu, 6 Apr 2017 13:37:02 +0100 Subject: [PATCH] attempt to fix #19 --- mapi/datastructs-abk.go | 11 +++++++++ mapi/mapi-abk.go | 34 ++++++++++++++++++++++++-- mapi/mapi.go | 5 ++-- rpc-http/rpctransport.go | 53 ++++++++++++++++++++++++++++++++++++---- ruler.go | 8 +++--- 5 files changed, 98 insertions(+), 13 deletions(-) diff --git a/mapi/datastructs-abk.go b/mapi/datastructs-abk.go index ec70cca..1840a7c 100644 --- a/mapi/datastructs-abk.go +++ b/mapi/datastructs-abk.go @@ -15,6 +15,12 @@ type BindRequest struct { AuxiliaryBuffer []byte } +type BindRequestRPC struct { + Flags uint32 + State []byte //optional 36 bytes + ServerGUID []byte +} + //BindResponse struct type BindResponse struct { StatusCode uint32 @@ -178,6 +184,11 @@ func (bindRequest BindRequest) Marshal() []byte { return utils.BodyToBytes(bindRequest) } +//Marshal turn BindRequestRPC into Bytes +func (bindRequest BindRequestRPC) Marshal() []byte { + return utils.BodyToBytes(bindRequest) +} + //Marshal turn GetSpecialTableRequest into Bytes func (specialTableRequest GetSpecialTableRequest) Marshal() []byte { return utils.BodyToBytes(specialTableRequest) diff --git a/mapi/mapi-abk.go b/mapi/mapi-abk.go index 7af665f..e977666 100644 --- a/mapi/mapi-abk.go +++ b/mapi/mapi-abk.go @@ -3,6 +3,7 @@ package mapi import ( "fmt" + rpchttp "github.com/sensepost/ruler/rpc-http" "github.com/sensepost/ruler/utils" ) @@ -10,7 +11,10 @@ func sendAddressBookRequest(mapiType string, mapi []byte) ([]byte, error) { if AuthSession.Transport == HTTP { return mapiRequestHTTP(AuthSession.ABKURL.String(), mapiType, mapi) } - return nil, nil //mapiRequestRPC(mapi) + + //return rpchttp.EcDoRPCExt2(mapi, 0) + return rpchttp.EcDoRPCAbk(mapi, 0) + //return nil, nil } //ExtractMapiAddressBookURL extract the External mapi url from the autodiscover response @@ -29,7 +33,7 @@ func BindAddressBook() (*BindResponse, error) { bindReq := BindRequest{} bindReq.Flags = 0x00 bindReq.HasState = 0xFF - bindReq.State = STAT{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1252, 1033, 2057}.Marshal() + bindReq.State = STAT{0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFFFFFF, 1252, 1033, 2057}.Marshal() bindReq.AuxiliaryBufferSize = 0x00 responseBody, err := sendAddressBookRequest("BIND", bindReq.Marshal()) @@ -37,6 +41,32 @@ func BindAddressBook() (*BindResponse, error) { if err != nil { return nil, fmt.Errorf("A HTTP server side error occurred.\n %s", err) } + + bindResp := BindResponse{} + _, err = bindResp.Unmarshal(responseBody) + if err != nil { + return nil, err + } + return &bindResp, nil + + //return nil, fmt.Errorf("unexpected error occurred") +} + +//BindAddressBookRPC function to bind to the AddressBook provider +func BindAddressBookRPC() (*BindResponse, error) { + + bindReq := BindRequestRPC{} + bindReq.Flags = 0x00 + bindReq.State = STAT{0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFFFFFF, 1252, 1033, 2057}.Marshal() + bindReq.ServerGUID = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x01, 0x30, 0x1f, 0x00, 0x17, 0x3a, 0x1f, 0x00, 0x08, 0x3a, 0x1f, 0x00, 0x19, 0x3a, 0x1f, 0x00, 0x18, 0x3a, 0x1f, 0x00, 0xfe, 0x39, 0x1f, 0x00, 0x16, 0x3a, 0x1f, 0x00, 0x00, 0x3a, 0x1f, 0x00, 0x02, 0x30, 0x02, 0x01, 0xff, 0x0f, 0x03, 0x00, 0xfe, 0x0f, 0x03, 0x00, 0x00, 0x39, 0x03, 0x00, 0x05, 0x39} + + data := bindReq.Marshal() + responseBody, err := rpchttp.EcDoRPCAbk(data, len(bindReq.ServerGUID)-10) + + if err != nil { + return nil, fmt.Errorf("A HTTP server side error occurred.\n %s", err) + } + bindResp := BindResponse{} _, err = bindResp.Unmarshal(responseBody) if err != nil { diff --git a/mapi/mapi.go b/mapi/mapi.go index 050195d..eebdd60 100644 --- a/mapi/mapi.go +++ b/mapi/mapi.go @@ -156,12 +156,11 @@ func mapiConnectRPC(body ConnectRequestRPC) ([]byte, error) { ready := make(chan bool) //this is our ready channel, chanError := make(chan error) //get the error message from our channel setup + utils.Trace.Println("Setting up channels") //we should add a channel to check if there was an error setting up the channels //there will currently be a deadlock here if something goes wrong go rpchttp.RPCOpen(AuthSession.RPCURL, ready, chanError) - utils.Trace.Println("Setting up channels") - //wait for channels to be setup if v := <-ready; v == false { //check if the setup was successful or premission Denied e := <-chanError @@ -982,7 +981,7 @@ func DeleteFolder(folderid []byte) (*RopDeleteFolderResponse, error) { deleteFolder := RopDeleteFolderRequest{RopID: 0x1D, LogonID: AuthSession.LogonID} deleteFolder.InputHandle = 0x00 deleteFolder.FolderID = folderid - deleteFolder.DeleteFolderFlags = 0x05 + deleteFolder.DeleteFolderFlags = 0x10 | 0x04 | 0x01 fullReq := deleteFolder.Marshal() diff --git a/rpc-http/rpctransport.go b/rpc-http/rpctransport.go index ab219ec..35157cb 100644 --- a/rpc-http/rpctransport.go +++ b/rpc-http/rpctransport.go @@ -22,6 +22,7 @@ var rpcOutR, rpcOutW = io.Pipe() var rpcRespBody *bufio.Reader var callcounter int var responses = make([]RPCResponse, 0) +var httpResponses = make([][]byte, 0) var rpcntlmsession ntlm.ClientSession //AuthSession Keep track of session data @@ -152,7 +153,6 @@ func setupHTTP(rpctype string, URL string, ntlmAuth bool, full bool) (net.Conn, } } - if cookiestr != "" { request = fmt.Sprintf("%sCookie: %s\r\n", request, cookiestr) } @@ -179,6 +179,15 @@ func RPCOpen(URL string, readySignal chan bool, errOccurred chan error) (err err //this will be sent back to the caller through "readySignal", while error is sent through errOccurred go RPCOpenOut(URL, readySignal, errOccurred) + select { + case <-readySignal: + readySignal <- false + errOccurred <- err + return err + case <-time.After(time.Second * 2): // call timed out + readySignal <- true + } + for { data := make([]byte, 2048) n, err := rpcInR.Read(data) @@ -204,13 +213,15 @@ func RPCOpenOut(URL string, readySignal chan bool, errOccurred chan error) (err errOccurred <- err return err } - readySignal <- true scanner := bufio.NewScanner(rpcOutConn) scanner.Split(SplitData) for scanner.Scan() { if b := scanner.Bytes(); b != nil { + if string(b[0:4]) == "HTTP" { + httpResponses = append(httpResponses, b) + } r := RPCResponse{} r.Unmarshal(b) r.Body = b @@ -248,8 +259,12 @@ func RPCBind() error { RPCWrite(bind.Marshal()) - RPCRead(0) - RPCRead(0) + if _, err := RPCRead(0); err != nil { + return err + } + if _, err := RPCRead(0); err != nil { + return err + } //parse out and setup security if AuthSession.RPCNetworkAuthLevel == RPC_C_AUTHN_LEVEL_PKT_PRIVACY { @@ -319,9 +334,23 @@ func EcDoRPCExt2(MAPI []byte, auxLen uint32) ([]byte, error) { sec.Unmarshal(resp.SecTrailer, int(resp.Header.AuthLen)) return dec[20:], err } + return resp.PDU[28:], err } +func EcDoRPCAbk(MAPI []byte, l int) ([]byte, error) { + RPCWriteN(MAPI, uint32(l), 0x03) + //RPCWrite(req.Marshal()) + + resp, err := RPCRead(callcounter - 1) + + if err != nil { + return nil, err + } + fmt.Printf("%x\n", resp.PDU) + return resp.PDU, err +} + //DoConnectExRequest makes our connection request. After this we can use //EcDoRPCExt2 to make our MAPI requests func DoConnectExRequest(MAPI []byte, auxLen uint32) ([]byte, error) { @@ -405,6 +434,7 @@ func RPCWriteN(MAPI []byte, auxlen uint32, opnum byte) { pdu.ContextHandle = AuthSession.ContextHandle } pdu.Data = MAPI + pdu.CbAuxIn = uint32(auxlen) pdu.AuxOut = 0x000001008 @@ -466,6 +496,7 @@ func RPCOutWrite(data []byte) { //our list of received responses. Blocks until it finds a response func RPCRead(callID int) (RPCResponse, error) { c := make(chan RPCResponse, 1) + cerr := make(chan error, 1) go func() { stop := false for stop != true { @@ -480,9 +511,21 @@ func RPCRead(callID int) (RPCResponse, error) { } }() + go func() { + for _, v := range httpResponses { + st := string(v) + if er := strings.Split(strings.Split(st, "\r\n")[0], " "); er[1] != "200" { + cerr <- fmt.Errorf("Invalid HTTP response: %s", er) + break + } + } + }() + select { case resp := <-c: return resp, nil + case er := <-cerr: + return RPCResponse{}, er case <-time.After(time.Second * 10): // call timed out return RPCResponse{}, fmt.Errorf("Time-out reading from RPC") } @@ -500,7 +543,7 @@ func SplitData(data []byte, atEOF bool) (advance int, token []byte, err error) { for k := range data { if data[k] == 0x0d && data[k+1] == 0x0a && data[k+2] == 0x0d && data[k+3] == 0x0a { //utils.Trace.Print(string(data[:k+4])) - return k + 4, nil, nil //data[0:k], nil + return k + 4, data[0:k], nil //data[0:k], nil } } } diff --git a/ruler.go b/ruler.go index 72da23b..0c38d3a 100644 --- a/ruler.go +++ b/ruler.go @@ -384,11 +384,13 @@ func printRules() error { //Function to display all addressbook entries func abkList(c *cli.Context) error { + utils.Trace.Println("Let's play addressbook") if config.Transport == mapi.RPC { - return fmt.Errorf("Address book support is currently limited to MAPI/HTTP") + return fmt.Errorf("Only MAPI/HTTP is currently supported for addressbook interaction") } - utils.Trace.Println("Let's play addressbook") + mapi.BindAddressBook() + columns := make([]mapi.PropertyTag, 2) columns[0] = mapi.PidTagDisplayName columns[1] = mapi.PidTagSMTPAddress @@ -463,7 +465,7 @@ func abkDump(c *cli.Context) error { if len(rows.RowData[k].AddressBookPropertyValue) == 2 { disp := utils.FromUnicode(rows.RowData[k].AddressBookPropertyValue[0].Value) email := utils.FromUnicode(rows.RowData[k].AddressBookPropertyValue[1].Value) - if _, err := fout.WriteString(fmt.Sprintf("%s , %s\n", disp, email)); err != nil { + if _, err := fout.WriteString(fmt.Sprintf("%s | %s\n", disp, email)); err != nil { return fmt.Errorf("Couldn't write to file... %s", err) } }