<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    像fofa一樣解析RDP信息,RDP提取操作系統,RDP登錄截屏 (Golang實現)

    VSole2022-12-22 10:34:11

    從fofa中搜索RDP,會看到它會解析出RDP的信息。

    本文探索如何自己實現一個。

    1. Nmap指紋

    https://raw.githubusercontent.com/nmap/nmap/master/nmap-service-probes 可以找到關于RDP發包的定義

    ##############################NEXT PROBE##############################
    # This is an RDP connection request with the MSTS cookie set. Some RDP
    # listeners (with NLA?) only respond to this one.
    # This must be sent before TLSSessionReq because Windows RDP will handshake TLS
    # immediately and we don't have a way of identifying RDP at that point.
    Probe TCP TerminalServerCookie q|\x03\0\0*%\xe0\0\0\0\0\0Cookie: mstshash=nmap\r\x01\0\x08\0\x03\0\0\0|
    rarity 7
    ports 3388,3389
    fallback TerminalServer
    Probe TCP TerminalServer q|\x03\0\0\x0b\x06\xe0\0\0\0\0\0|
    rarity 6
    ports 515,1028,1068,1503,1720,1935,2040,3388,3389
    # Windows 2000 Server
    # Windows 2000 Advanced Server
    # Windows XP Professional
    match ms-wbt-server m|^\x03\0\0\x0b\x06\xd0\0\0\x12.\0$|s p/Microsoft Terminal Service/ o/Windows/ cpe:/o:microsoft:windows/a
    match ms-wbt-server m|^\x03\0\0\x17\x08\x02\0\0Z~\0\x0b\x05\x05@\x06\0\x08\x91J\0\x02X$| p/Microsoft Terminal Service/ i/Used with Netmeeting, Remote Desktop, Remote Assistance/ o/Windows/ cpe:/o:microsoft:windows/a
    match ms-wbt-server m|^\x03\0\0\x11\x08\x02..}\x08\x03\0\0\xdf\x14\x01\x01$|s p/Microsoft NetMeeting Remote Desktop Service/ o/Windows/ cpe:/a:microsoft:netmeeting/ cpe:/o:microsoft:windows/a
    match ms-wbt-server m|^\x03\0\0\x0b\x06\xd0\0\0\x03.\0$|s p/Microsoft NetMeeting Remote Desktop Service/ o/Windows/ cpe:/a:microsoft:netmeeting/ cpe:/o:microsoft:windows/a
    # Need more samples!
    match ms-wbt-server m|^\x03\0\0\x0b\x06\xd0\0\0\0\0\0| p/xrdp/ cpe:/a:jay_sorg:xrdp/
    match ms-wbt-server m|^\x03\0\0\x0e\t\xd0\0\0\0[\x02\xa1]\0\xc0\x01$| p/IBM Sametime Meeting Services/ o/Windows/ cpe:/a:ibm:sametime/ cpe:/o:microsoft:windows/a
    match ms-wbt-server m|^\x03\0\0\x0b\x06\xd0\0\x004\x12\0| p/VirtualBox VM Remote Desktop Service/ o/Windows/ cpe:/a:oracle:vm_virtualbox/ cpe:/o:microsoft:windows/a
    match ms-wbt-server-proxy m|^nmproxy: Procotol byte is not 8$| p/nmproxy NetMeeting proxy/
    

    它在tcp連接上之后會發包 \x03\0\0*%\xe0\0\0\0\0\0Cookie: mstshash=nmap\r\x01\0\x08\0\x03\0\0\0,nmap關于rdp的版本指紋比較少,而且發的包還有特征。

    nmap有一個rdp.lua,封裝了rdp連接的前幾層協議,后面深入學習協議時可以對照著看。

    2. 深入協議

    官方文檔:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/023f1e69-cfe8-4ee6-9ee0-7e759fb4e4ee 有協議的交互流程圖

    2.1. 發包

    看了文檔后,發現連接順序分為十個不同的階段,但是獲得一些基礎信息,只用看第一階段Connection Initiation就行了。

    Connection Initiation:客戶端通過向服務器發送 X.224 連接請求 PDU(第2.2.1.1節)來啟動連接。服務器響應 0 類 X.224 連接確認 PDU(第2.2.1.2節)。
    從這一點開始,客戶端和服務器之間發送的所有后續數據都包裝在 X.224 數據協議數據單元 (PDU) (1) 中。

    請求結構

    結構體如下:

    「tpktHeader(4 字節):」 TPKT 標頭,如[T123]第 8 節中所指定。

    nmap中的定義

    __tostring = function(self)
      return string.pack(">BBI2",
        self.version,  //  一般是3 
        self.reserved or 0, // 一般是0
        (self.data and #self.data + 4 or 4)) // 整個結構體的大小,包括后面的數據
      ..self.data  // 后面的數據
    

    「x224Crq(7 字節):」 一個 X.224 類 0 連接請求傳輸協議數據單元 (TPDU),如[X224] 第 13.3 節中所指定。

    • 第一個是后面結構體長度,第二個是hex(int('11100000',2)),即0xe0 ,后面5個字節都是0,這個數據結構即length+0xe0,0x0,0x0,0x0,0x0,0x0

    「routingToken(可變):一個可選的可變長度路由令牌(用于負載平衡),由 0x0D0A 兩字節序列終止。有關路由令牌格式的詳細信息,請參閱」「[MSFT-SDLBTS]」** “路由令牌格式”。路由令牌和 CR+LF 序列的長度包含在「X.224 連接請求長度指示符 字段中。如果此字段存在,則」cookie**字段不得存在。

    「cookie(變量):可選且長度可變的」「ANSI」** 字符串,以 0x0D0A 兩字節序列結尾。此文本字符串必須是“Cookie:mstshash=IDENTIFIER”,其中 IDENTIFIER 是一個 ANSI 字符串(示例 cookie 字符串顯示在第**「4.1.1」「節中)。整個 cookie 字符串和 CR+LF 序列的長度包含在」X.224 連接請求長度指示符字段中。如果「routingToken」字段存在,則該字段不得存在。

    「rdpNegReq(8 字節):一個可選的」RDP 協商請求(第2.2.1.1.1節)結構。該字段的長度包含在「X.224 連接請求長度指示符」字段中。

    • 文檔描述很詳細了,這個結構體很重要,用于設置請求協議

    「rdpCorrelationInfo(36 字節):一個可選的」關聯信息(第2.2.1.1.2節)結構。該字段的長度包含在「X.224 連接請求長度指示符」字段中。如果在RDP 協商請求結構的「標志」字段中設置了 CORRELATION_INFO_PRESENT (0x08) 標志,則該字段必須存在,封裝在可選的「rdpNegReq」 字段中。如果未設置 CORRELATION_INFO_PRESENT (0x08) 標志,則該字段不得存在。

    • 這個結構體沒啥用,不用寫

    用golang實現這個結構體

    type RdpReq struct {
      requestedProtocols uint32
      cookie             []byte
    }
    func NewReq(protocol uint32, cookie []byte) *RdpReq {
      return &RdpReq{requestedProtocols: protocol, cookie: cookie}
    }
    func (r *RdpReq) Serialize() []byte {
      buff := &bytes.Buffer{}
      // cookie
      if r.cookie != nil {
        cookie := []byte(fmt.Sprintf("Cookie: mstshash=%s\r", r.cookie))
        buff.Write(cookie)
      }
      // rdpNegReq
      buff.Write([]byte{0x1, 0x0, 0x8, 0x0})
      requestedProtocolData := make([]byte, 4)
      binary.LittleEndian.PutUint32(requestedProtocolData, r.requestedProtocols)
      buff.Write(requestedProtocolData)
      buff2 := &bytes.Buffer{}
      // x224Crq (7 字節)
      buff2.Write([]byte{
        uint8(buff.Len() + 6),
        0xe0,
        0x00, 0x00,
        0x00, 0x00, 0x00,
      })
      buff2.Write(buff.Bytes())
      // tpktHeader(4 字節)
      buff3 := &bytes.Buffer{}
      buff3.Write([]byte{3, 0})
      lengthData := make([]byte, 2)
      binary.BigEndian.PutUint16(lengthData, uint16(buff2.Len()+4))
      buff3.Write(lengthData)
      buff3.Write(buff2.Bytes())
      return buff3.Bytes()
    }
    

    測試

    func main() {
      rdp := NewReq(PROTOCOL_RDP|PROTOCOL_SSL|PROTOCOL_HYBRID, []byte("w8ay"))
      buff := rdp.Serialize()
      fmt.Println(hex.Dump(buff))
    }
    

    輸出

    和nmap的probe\x03\0\0*%\xe0\0\0\0\0\0Cookie: mstshash=nmap\r\x01\0\x08\0\x03\0\0\0也能對應上

    2.2. 收包

    在發包完畢后,會收到如下結構體

    前面的結構可以跳過,直接看rdpNegData結構

    文檔:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/b2975bdc-6d56-49ee-9c57-f2ff3a0b6817

    成功的話,它會返回一個服務器指定的通信協議。

    根據結構用golang寫個解析程序

    type RdpResp struct {
      data   []byte
      Type   int
      Flags  int
      Result uint32
    }
    func ParseRdpResp(data []byte) (*RdpResp, error) {
      GenericRDPSignature := []byte{
        0x03, 0x00, 0x00, 0x13, 0x0e, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00,
      }
      if !checkSignature(data[:11], GenericRDPSignature) {
        return nil, errors.New("not rdp response")
      }
      reader := bytes.NewReader(data)
      reader.Seek(11, io.SeekStart)
      r := new(RdpResp)
      r.data = data
      _type, err := reader.ReadByte()
      if err != nil {
        return r, err
      }
      r.Type = int(_type)
      _flag, err := reader.ReadByte()
      if err != nil {
        return r, err
      }
      r.Flags = int(_flag)
      reader.Seek(2, io.SeekCurrent)
      result := make([]byte, 4)
      _, err = reader.Read(result)
      if err != nil {
        return r, err
      }
      r.Result = binary.LittleEndian.Uint32(result)
      return r, nil
    }
    

    3. 簡單OS識別

    第一層連接協議中,我們可以控制請求的協議,并從返回包中解析出服務器選擇的協議以及flags參數。

    協議的支持在windows不同版本是不一樣的,根據這個,將協議設定為PROTOCOL_RDP|PROTOCOL_SSL|PROTOCOL_HYBRID_EX,即可根據返回包結果來識別不同OS。

    func (r *RdpResp) FingerPrintOs() string {
      Windows2000 := []byte{
        0x03, 0x00, 0x00, 0x0b, 0x06, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00,
      }
      WindowsServer2012R2 := []byte{
        0x03, 0x00, 0x00, 0x13, 0x0e, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00,
        0x03, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00,
      }
      WindowsServer2008 := []byte{
        0x03, 0x00, 0x00, 0x13, 0x0e, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00, 0x02,
        0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00,
      }
      Windows7OrServer2008R2 := []byte{
        0x03, 0x00, 0x00, 0x13, 0x0e, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00, 0x02,
        0x09, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00,
      }
      WindowsServer2008R2DC := []byte{
        0x03, 0x00, 0x00, 0x13, 0x0e, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00, 0x02,
        0x01, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00,
      }
      Windows10 := []byte{
        0x03, 0x00, 0x00, 0x13, 0x0e, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00, 0x02,
        0x1f, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00,
      }
      WindowsServer2012Or8 := []byte{
        0x03, 0x00, 0x00, 0x13, 0x0e, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00, 0x02,
        0x0f, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00,
      }
      WindowsServer2016or2019 := []byte{
        0x03, 0x00, 0x00, 0x13, 0x0e, 0xd0, 0x00, 0x00, 0x12, 0x34, 0x00, 0x02,
        0x0f, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00,
      }
      signatures := map[string][]byte{
        "Windows 2000":                Windows2000,
        "WindowsServer2012R2":         WindowsServer2012R2,
        "Windows Server 2008":         WindowsServer2008,
        "Windows 7 or Server 2008 R2": Windows7OrServer2008R2,
        "Windows Server 2008 R2 DC":   WindowsServer2008R2DC,
        "Windows 10":                  Windows10,
        "Windows 8 or Server 2012":    WindowsServer2012Or8,
        "Windows Server 2016 or 2019": WindowsServer2016or2019,
      }
      for fingerprint, signature := range signatures {
        signatureLength := len(signature)
        if len(r.data) < signatureLength {
          continue
        }
        responseSlice := r.data[:signatureLength]
        tof := checkSignature(responseSlice, signature)
        if tof {
          return fingerprint
        }
      }
      return ""
    }
    

    簡單識別通過包對比進行識別,有時能識別fofa所識別不了的地方。如

    4. 協議枚舉

    可以看nmap rdp加密協議枚舉的腳本 https://github.com/nmap/nmap/blob/master/scripts/rdp-enum-encryption.nse

    -- @output
    -- PORT     STATE SERVICE
    -- 3389/tcp open  ms-wbt-server
    -- |   Security layer
    -- |     CredSSP (NLA): SUCCESS
    -- |     CredSSP with Early User Auth: SUCCESS
    -- |     Native RDP: SUCCESS
    -- |     RDSTLS: SUCCESS
    -- |     SSL: SUCCESS
    -- |   RDP Encryption level: High
    -- |     40-bit RC4: SUCCESS
    -- |     56-bit RC4: SUCCESS
    -- |     128-bit RC4: SUCCESS
    -- |     FIPS 140-1: SUCCESS
    -- |_  RDP Protocol Version:  RDP 5.x, 6.x, 7.x, or 8.x server
    

    它實現獲取Security layer,是遍歷發送協議,如果返回包支持則支持。這個可以很容易實現。

    在之前封裝的返回包結構中加上獲取支持協議的文本

    func (r *RdpResp) SupportProtocol() string {
      if r.Type == TYPE_RDP_NEG_FAILURE {
        return ""
      }
      switch r.Result {
      case PROTOCOL_HYBRID_EX:
        return "PROTOCOL_HYBRID_EX"
      case PROTOCOL_RDSAAD:
        return "PROTOCOL_RDSAAD"
      case PROTOCOL_HYBRID:
        return "PROTOCOL_HYBRID"
      case PROTOCOL_SSL:
        return "PROTOCOL_SSL"
      case PROTOCOL_RDP:
        return "PROTOCOL_RDP"
      case PROTOCOL_RDSTLS:
        return "PROTOCOL_RDSTLS"
      }
      return ""
    }
    

    封裝協議枚舉函數

    // 獲取RDP支持的協議
    func GetSupportProtocol(address string, port uint16, timeout time.Duration) []string {
      ret := make([]string, 0)
      for _, v := range []uint32{PROTOCOL_RDP, PROTOCOL_SSL, PROTOCOL_HYBRID, PROTOCOL_HYBRID_EX, PROTOCOL_RDSTLS, PROTOCOL_RDSAAD} {
        conn, err := DialTCP(address, port, timeout)
        if err != nil {
          panic(err)
        }
        rdp := NewReq(v, []byte("w8ay"))
        buff := rdp.Serialize()
        err = Send(conn, buff, timeout)
        if err != nil {
          continue
        }
        response, err := Recv(conn, timeout)
        if err != nil {
          continue
        }
        resp, _ := ParseRdpResp(response)
        if resp != nil {
          if resp.Type == TYPE_RDP_NEG_RSP {
            ret = append(ret, resp.SupportProtocol())
          }
        }
        time.Sleep(time.Millisecond * 100)
      }
      return ret
    }
    

    不清楚fofa的flag是怎么實現的,fofa的似乎不太準確。相同的IP使用這種方式能識別出的協議更多。如圖。

    另外還有RDP Protocol Version的獲取,但是要實現MCS結構,太麻煩不做了,資料在

    https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/379a020e-9925-4b4f-98f3-7d634e10b411

    5. NTLM 信息獲取

    大佬說:「tls連接后會進行ntlmssp的挑戰響應,能夠非常準確的提取出來主機名和操作系統的版本」

    RDP文檔中寫道,為了安全考慮,可以直接走TLS協議并使用CredSSP進行驗證。CredSSP可以使用ntlm驗證進行信息獲取。

    nmap有一個腳本用于提取ntlm信息 https://github.com/nmap/nmap/blob/master/scripts/rdp-ntlm-info.nse

    -- @output
    -- 3389/tcp open     ms-wbt-server syn-ack ttl 128 Microsoft Terminal Services
    -- | rdp-ntlm-info:
    -- |   Target_Name: W2016
    -- |   NetBIOS_Domain_Name: W2016
    -- |   NetBIOS_Computer_Name: W16GA-SRV01
    -- |   DNS_Domain_Name: W2016.lab
    -- |   DNS_Computer_Name: W16GA-SRV01.W2016.lab
    -- |   DNS_Tree_Name: W2016.lab
    -- |   Product_Version: 10.0.14393
    -- |_  System_Time: 2019-06-13T10:38:35+00:00
    -
    

    文檔:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/6aac4dea-08ef-47a6-8747-22ea7f6d8685?source=recommendations

    請求體,negoToken字段是ntlm的結構,整個結構體要進行ASN.1編碼。

    嫌麻煩可以直接用nmap組好的數據包

    它的返回信息見文檔

    https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/801a4681-8809-4be9-ab0d-61dcfe762786

    里面值得關注的數據有 OSVersion,TargetName 以及以下的結構

      AvIDMap := map[uint16]string{
        1: "NetBIOSComputerName",
        2: "NetBIOSDomainName",
        3: "FQDN", // DNS Computer Name
        4: "DNSDomainName",
        5: "DNSTreeName",
        7: "Timestamp",
        9: "MsvAvTargetName",
      }
    

    編寫程序解析后就能獲得想要的信息了。

    獲得的操作系統版本信息是基于`Major-Minor-Build`的版本號,找到一個比較全的列表

    https://www.gaijin.at/en/infos/windows-version-numbers

    寫個爬蟲就給爬下來了

    再次運行下,就能得到操作系統的詳細信息了。輸出

    請求包
    00000000  03 00 00 2a 25 e0 00 00  00 00 00 43 6f 6f 6b 69  |...*%......Cooki|
    00000010  65 3a 20 6d 73 74 73 68  61 73 68 3d 77 38 61 79  |e: mstshash=w8ay|
    00000020  0d 0a 01 00 08 00 0b 00  00 00                    |..........|
    返回包
    00000000  03 00 00 13 0e d0 00 00  12 34 00 02 1f 08 00 08  |.........4......|
    00000010  00 00 00                                          |...|
    簡單OS識別: Windows 10 
    支持協議 [PROTOCOL_RDP PROTOCOL_SSL PROTOCOL_HYBRID PROTOCOL_HYBRID_EX PROTOCOL_RDSTLS]
    NTLMSSP 返回包
    00000000  30 81 a8 a0 03 02 01 06  a1 81 a0 30 81 9d 30 81  |0..........0..0.|
    00000010  9a a0 81 97 04 81 94 4e  54 4c 4d 53 53 50 00 02  |.......NTLMSSP..|
    00000020  00 00 00 0c 00 0c 00 38  00 00 00 75 82 9a e2 5e  |.......8...u...^|
    00000030  53 34 ae 68 91 2c 56 00  00 00 00 00 00 00 00 50  |S4.h.,V........P|
    00000040  00 50 00 44 00 00 00 0a  00 61 4a 00 00 00 0f 53  |.P.D.....aJ....S|
    00000050  00 45 00 52 00 56 00 45  00 52 00 02 00 0c 00 53  |.E.R.V.E.R.....S|
    00000060  00 45 00 52 00 56 00 45  00 52 00 01 00 0c 00 53  |.E.R.V.E.R.....S|
    00000070  00 45 00 52 00 56 00 45  00 52 00 04 00 0c 00 53  |.E.R.V.E.R.....S|
    00000080  00 45 00 52 00 56 00 45  00 52 00 03 00 0c 00 53  |.E.R.V.E.R.....S|
    00000090  00 45 00 52 00 56 00 45  00 52 00 07 00 08 00 74  |.E.R.V.E.R.....t|
    000000a0  df 2b ea 65 14 d9 01 00  00 00 00                 |.+.e.......|
    NetBIOSComputerName:SERVER
    DNSDomainName:SERVER
    FQDN:SERVER
    Timestamp:2022-12-20 19:26:33
    Product_Version:10.0.19041
    Os_Verion:Windows 10, Version 2004/Windows Server, Version 2004
    TargetName:SERVER
    NetBIOSDomainName:SERVER
    

    對比下fofa中的信息

    基本上將rdp信息都解析出來了。

    NTLM解析代碼如下

    func RdpWithNTLM(conn net.Conn, timeout time.Duration) (map[string]any, error) {
      info := make(map[string]any)
      // CredSSP protocol - NTLM authentication
      // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp
      // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp
      // http://davenport.sourceforge.net/ntlm.html
      NegotiatePacket := []byte{
        0x30, 0x37, 0xA0, 0x03, 0x02, 0x01, 0xff, 0xA1, 0x30, 0x30, 0x2E, 0x30, 0x2C, 0xA0, 0x2A, 0x04, 0x28,
        // Signature
        'N', 'T', 'L', 'M', 'S', 'S', 'P', 0x00,
        // Message Type
        0x01, 0x00, 0x00, 0x00,
        // Negotiate Flags
        0xF7, 0xBA, 0xDB, 0xE2,
        // Domain Name Fields
        0x00, 0x00, // DomainNameLen
        0x00, 0x00, // DomainNameMaxLen
        0x00, 0x00, 0x00, 0x00, // DomainNameBufferOffset
        // Workstation Fields
        0x00, 0x00, // WorkstationLen
        0x00, 0x00, // WorkstationMaxLen
        0x00, 0x00, 0x00, 0x00, // WorkstationBufferOffset
        // Version
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      }
      err := Send(conn, NegotiatePacket, timeout)
      if err != nil {
        return nil, err
      }
      response, err := Recv(conn, timeout)
      if err != nil {
        return nil, err
      }
      fmt.Println("NTLMSSP 返回包")
      fmt.Println(hex.Dump(response))
      type NTLMChallenge struct {
        Signature              [8]byte
        MessageType            uint32
        TargetNameLen          uint16
        TargetNameMaxLen       uint16
        TargetNameBufferOffset uint32
        NegotiateFlags         uint32
        ServerChallenge        uint64
        Reserved               uint64
        TargetInfoLen          uint16
        TargetInfoMaxLen       uint16
        TargetInfoBufferOffset uint32
        Version                [8]byte
        // Payload (variable)
      }
      var challengeLen = 56
      challengeStartOffset := bytes.Index(response, []byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0})
      if challengeStartOffset == -1 {
        return info, nil
      }
      if len(response) < challengeStartOffset+challengeLen {
        return info, nil
      }
      var responseData NTLMChallenge
      response = response[challengeStartOffset:]
      responseBuf := bytes.NewBuffer(response)
      err = binary.Read(responseBuf, binary.LittleEndian, &responseData)
      if err != nil {
        return info, err
      }
      // Check if valid NTLM challenge response message structure
      if responseData.MessageType != 0x00000002 ||
        responseData.Reserved != 0 ||
        !reflect.DeepEqual(responseData.Version[4:], []byte{0, 0, 0, 0xF}) {
        return info, nil
      }
      // Parse: Version
      type version struct {
        MajorVersion byte
        MinorVersion byte
        BuildNumber  uint16
      }
      var versionData version
      versionBuf := bytes.NewBuffer(responseData.Version[:4])
      err = binary.Read(versionBuf, binary.LittleEndian, &versionData)
      if err != nil {
        return info, err
      }
      ProductVersion := fmt.Sprintf("%d.%d.%d", versionData.MajorVersion,
        versionData.MinorVersion,
        versionData.BuildNumber)
      info["Product_Version"] = ProductVersion
      v, ok := OsVersion[ProductVersion]
      if ok {
        info["Os_Verion"] = v
      }
      // Parse: TargetName
      targetNameLen := int(responseData.TargetNameLen)
      if targetNameLen > 0 {
        startIdx := int(responseData.TargetNameBufferOffset)
        endIdx := startIdx + targetNameLen
        targetName := strings.ReplaceAll(string(response[startIdx:endIdx]), "\x00", "")
        info["TargetName"] = targetName
      }
      // Parse: TargetInfo
      AvIDMap := map[uint16]string{
        1: "NetBIOSComputerName",
        2: "NetBIOSDomainName",
        3: "FQDN", // DNS Computer Name
        4: "DNSDomainName",
        5: "DNSTreeName",
        7: "Timestamp",
        9: "MsvAvTargetName",
      }
      type AVPair struct {
        AvID  uint16
        AvLen uint16
        // Value (variable)
      }
      var avPairLen = 4
      targetInfoLen := int(responseData.TargetInfoLen)
      if targetInfoLen > 0 {
        startIdx := int(responseData.TargetInfoBufferOffset)
        if startIdx+targetInfoLen > len(response) {
          return info, fmt.Errorf("Invalid TargetInfoLen value")
        }
        var avPair AVPair
        avPairBuf := bytes.NewBuffer(response[startIdx : startIdx+avPairLen])
        err = binary.Read(avPairBuf, binary.LittleEndian, &avPair)
        if err != nil {
          return info, err
        }
        currIdx := startIdx
        for avPair.AvID != 0 {
          if field, exists := AvIDMap[avPair.AvID]; exists {
            var value string
            r := response[currIdx+avPairLen : currIdx+avPairLen+int(avPair.AvLen)]
            if avPair.AvID == 7 {
              unixStamp := binary.LittleEndian.Uint64(r)/10000000 - 11644473600
              tm := time.Unix(int64(unixStamp), 0)
              value = tm.Format("2006-01-02 15:04:05")
            } else {
              value = strings.ReplaceAll(string(r), "\x00", "")
            }
            info[field] = value
          }
          currIdx += avPairLen + int(avPair.AvLen)
          if currIdx+avPairLen > startIdx+targetInfoLen {
            return info, fmt.Errorf("Invalid AV_PAIR list")
          }
          avPairBuf = bytes.NewBuffer(response[currIdx : currIdx+avPairLen])
          err = binary.Read(avPairBuf, binary.LittleEndian, &avPair)
          if err != nil {
            return info, err
          }
        }
      }
      return info, nil
    }
    

    6. RDP登錄截圖

    不是所有協議都支持RDP截圖

    要實現RDP登錄交互,這個涉及更后面的交互了,所以找了個go rdp的庫 https://github.com/tomatome/grdp (這個庫有點小bug,而且只能編譯windows版本,不過我已經一通魔改,修了一些bug,并且支持全平臺編譯)

    它里面提供了一個接口,可以獲得位圖數據

    g.pdu.On("update", func(rectangles []pdu.BitmapData) {}
    

    bitmap的數據結構可以看文檔:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/f4ed1422-2eed-4474-bafb-42ab35ad3707

    「獲得截圖要做的事情也很多很雜」

    • 獲得的位圖只是一小塊圖片,后需要將這些圖片拼接起來
    • 位圖有的是壓縮的,需要解壓
    • 涉及到將位圖數據轉換為圖片等等

    偽代碼如下

    ScreenImage := image.NewRGBA(image.Rect(0, 0, 1024, 768))
    host := g.Host
    g.pdu.On("update", func(rectangles []pdu.BitmapData) {
      glog.Info("on update bitmap:", len(rectangles))
      bs := make([]Bitmap, 0)
      for _, v := range rectangles {
        IsCompress := v.IsCompress()
        data := v.BitmapDataStream
        if IsCompress {
          data = BitmapDecompress(&v)
          IsCompress = false
        }
        b := Bitmap{int(v.DestLeft), int(v.DestTop), int(v.DestRight), int(v.DestBottom),
          int(v.Width), int(v.Height), Bpp(v.BitsPerPixel), IsCompress, data}
        bs = append(bs, b)
      }
      var (
        pixel      int
        i          int
        r, g, b, a uint8
      )
      for _, bm := range bs {
        i = 0
        pixel = bm.BitsPerPixel
        m := image.NewRGBA(image.Rect(0, 0, bm.Width, bm.Height))
        for y := 0; y < bm.Height; y++ {
          for x := 0; x < bm.Width; x++ {
            r, g, b, a = ToRGBA(pixel, i, bm.Data)
            c := color.RGBA{r, g, b, a}
            i += pixel
            m.Set(x, y, c)
          }
        }
        draw.Draw(ScreenImage, ScreenImage.Bounds().Add(image.Pt(bm.DestLeft, bm.DestTop)), m, m.Bounds().Min, draw.Src)
      }
      // Encode to jpeg.
      var imageBuf bytes.Buffer
      err = jpeg.Encode(&imageBuf, ScreenImage, nil)
      if err != nil {
        log.Panic(err)
      }
      // Write to file.
      fo, err := os.Create(fmt.Sprintf("img/%s-%d.jpg", host, index))
      index += 1
      if err != nil {
        panic(err)
      }
      fw := bufio.NewWriter(fo)
      fw.Write(imageBuf.Bytes())
    })
    

    這個代碼會在前一幀的基礎行保存圖片,每一幀都會保存為一個完整的圖片。

    連起來就像是一個登錄的gif

    7. 源碼

    完整源碼放在知識星球了,微信公眾號關注“Hacking就是好玩”,回復“知識星球”即可。

    8. 參考

    stringnmap
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    近期對nmap的操作系統識別功能造了個輪子,用golang實現了一遍,想未來能用于掃描器,資產發現/管理系統
    nmap -PN -sS -sV --script=vulscan –script-args vulscancorrelation=1 -p80 target. nmap -PN -sS -sV --script=all –script-args vulscancorrelation=1 target. NetCat,它的實際可運行的名字叫nc,應該早很就被提供,就象另一個沒有公開但是標準的Unix工具。
    實戰打靶之Obscurity
    2022-11-24 13:50:00
    訪問其8080端口,發現是一個web界面。瀏覽頁面內容,提升有一些提示。發現提示是4042.目錄爆破使用gobuster進行目錄爆破。gobuster dir-u http://10.10.10.168:8080 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt ,發現都是404.3.使用wfuzz進行fuzz由于我們不知道文件存放在那個具體路徑下,所以將使用wfuzzurl 來定位http://10.10.10.168:8080/FUZZ/SuperSecureServer.py其路徑。wfuzz -c-w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt -u http://10.10.10.168:8080/FUZZ/SuperSecureServer.py --hl 6 --hw 367發現它在/developer目錄之下。成功看到腳本內容。"400": "BAD REQUEST", "401": "UNAUTHORIZED", "403": "FORBIDDEN", "404": "NOT FOUND",
    滲透技巧總結
    2022-01-23 13:30:33
    整理一些滲透測試相關技巧總結~
    域滲透實戰之 vsmoon
    2023-11-14 10:40:10
    域滲透實戰之 vsmoon
    成功getshell后通過冰蝎上傳了一個哥斯拉shell接下來就是socks5代理了,上傳一個frp后發現服務端關了,事發突然并沒有做什么權限維持,到手的shell飛了經過分析和思考,造成這種情況的原因是直接拿了編譯好的frp沒做免殺,也許內網有全流量,設備報警提醒了,管理員發現異常后直接關機了。
    從fofa中搜索RDP,會看到它會解析出RDP的信息。本文探索如何自己實現一個。1. Nmap指紋在http
    很早就想專門寫一篇關于內網的文章,一直沒有騰出空來,萬萬沒想到,寫下這篇文章的時候,竟然是我來某實驗室實習的時間段:)
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类