Unpack

Malware Analysis

Phân tích và debug file thực thi trên linux

Phân tích file elf

Có thể sử dụng công cụ IDA Pro để đọc mã nguồn, tương tự như phân tích file PE trên Windows.

Remote debug file thực thi trên linux bằng IDA.

Để debug, có thể sử dụng 1 máy ảo linux remote để dựng môi trường chạy mã độc. Các bước cấu hình như sau:

  • Bước 1: cấu hình cho máy ảo và máy thật thông kết nối. Tốt nhất sử dụng Host-only Adapter để tránh mã độc kết nối ra ngoài hoặc mạng nội bộ.

  • Bước 2: copy thư mục dbgsrv vào máy ảo và chạy file linux_server hoặc linux_serverx64.

  • Trên máy tính chạy IDA, chọn cấu hình Remote Linux debugger. Sau đó vào tab Debugger, chọn Process Options:

  • Sau đó, đặt breakpoint và run.

Unpack mã độc trên linux

Nguyên lý pack và unpack

  • Pack (đóng gói) 1 file thực thi là quá trình nén, đóng gói code của 1 file thực thi. File sau khi pack có kích thước nhỏ hơn, phục vụ cho việc truyền file.

  • Tuy nhiên, hiện giờ ngoài những file setup, file thực thi thông thường còn bị pack khiến việc đọc code, reverse trở nên khó hơn, không ngăn chặn cũng có thể làm chậm quá trình phân tích hành vi.

  • Dù có rất nhiều trình pack, chúng đều có 1 nguyên lý chung: giải mã lại code gốc trên memory. Có những trình pack giải mã file cũ đúng imagebase, cũng có những trình pack alloc vùng nhớ mới và manual load file thực thi cũ lên. Trên mem luôn có 2 vùng nhớ, 1 là của trình unpack, 1 là để giải mã file thực thi.

  • Nắm được nguyên lý trên, để unpack 1 file ta cần tìm đặc trưng của đoạn code giải mã file cũ tương ứng với từng OS (LoadLibrary, GetProcAddress, VirtualAlloc, VirtualProtect trong Windows; syscall alloc, syscall protect trong Linux). Sau đó, đến đoạn jump từ vùng unpack sang vùng giải mã, dump code ra và sửa code trên mem thành code trên file là unpack thành công.

Unpack trên Windows

Trước khi tìm hiểu unpack trên Linux, chúng ta điểm lại các bước unpack trên Windows, để hiểu các bước đang làm có ý nghĩa gì. Không ít người khi mới học unpack cứ đặt breakpoint tại các hàm LoadLibrary, GetProcAddress rồi F9 loạn xạ dump file mới ra và cho rằng mình unpack thành công. Phương pháp đó có thể mang lại kết quả nhất thời, nhưng nếu không hiểu bản chất sẽ không unpack được các trình pack hoặc trên OS khác

  • Bước 1: tìm đoạn code giải mã, khôi phục file gốc trên mem. Trên Windows, có các đặc trưng để nhận biết hành vi này như LoadLibrary, GetProcAddress (để khôi phục bảng IAT của file PE), ngoài ra còn có các API như VirtualAlloc, VirtualProtect hoặc gặp các api có chức năng tương tự cũng nên để ý (chức năng alloc và set quyền memory). Ta debug và kết hợp đọc code ida nếu có thể (c, asm), phát hiện khi nào hoàn tất việc giải mã PE cũ để dump memory xuống. Bước này chỉ có dấu hiệu nhận biết, không có công thức cụ thể luôn đúng nên đòi hỏi unpack-man/woman sự mò mẫn, tính kiên trì, không bao giờ được bỏ cuộc.

  • Bước 2: Đoạn code dump xuống là đoạn code trên memory, không phải là trên file ban đầu. Ta cần phải fix lại trạng thái trên file mới có thể tiếp tục dùng các công cụ dịch ngược để phân tích file gốc.

Áp dụng unpack UPX trên Linux

  • Bước 1: đặt breakpoint tại syscall alloc và syscall protect.

  • Bước 2: Debug đến khi jump đến vùng nhớ vừa được alloc ra.

  • Bước 3: Debug qua đoạn ghi lại code base address.

  • Bước 4: Dump vùng mem từ base address xuống.

  • Bước 5: Fix lại header trên mem hoặc section trên file

BÀI TẬP

Trước khi Unpack

Note:

Các hàm xuất hiện khi chưa unpack:

  • sys_open (rax = 2):

    • Mở file chỉ định bởi địa chỉ trong rdi với cờ được đặt trong esi.

  • sys_mmap (rax = 9):

    • Ánh xạ một vùng bộ nhớ, có thể được sử dụng để ánh xạ file vào không gian bộ nhớ của tiến trình hoặc tạo một vùng bộ nhớ mới cho dữ liệu hoặc mã thực thi.

  • sys_mprotect (rax = 10):

    • Thay đổi quyền bảo vệ của một vùng bộ nhớ, ví dụ, đánh dấu nó như có thể thực thi, chỉ đọc, hoặc ghi.

  • sys_close (rax = 3):

    • Đóng một file descriptor được chỉ định bởi rdi. Việc đóng file giải phóng tài nguyên liên quan đến file descriptor này và làm cho descriptor có sẵn để tái sử dụng. Hàm trả về 0 nếu thành công và -1 nếu có lỗi, với mã lỗi được lưu trong errno.

  • sys_munmap (rax = 11):

    • Hủy bỏ ánh xạ một vùng bộ nhớ được chỉ định bởi địa chỉ trong rdi và kích thước trong rsi. Hàm này được sử dụng để giải phóng một vùng bộ nhớ đã được ánh xạ vào không gian địa chỉ của tiến trình, thường là sau khi đã sử dụng xong dữ liệu hoặc mã được ánh xạ. Việc giải phóng bộ nhớ giúp tránh lãng phí tài nguyên và tiềm ẩn rò rỉ bộ nhớ. sys_munmap trả về 0 nếu thành công, và -1 nếu có lỗi, với mã lỗi được đặt trong errno.

sys_open

Lệnh sys_open là một syscall trong Linux dùng để mở một tệp. Khi một chương trình cần đọc từ hoặc ghi vào một tệp, nó sẽ sử dụng sys_open để lấy một file descriptor (FD) đại diện cho tệp đó. FD sau đó có thể được sử dụng để thực hiện các thao tác đọc và ghi.

Cú pháp của sys_open trên kiến trúc x86-64 là:

int open(const char *pathname, int flags, mode_t mode);

Trong đó:

  • pathname là đường dẫn đến tệp cần mở.

  • flags xác định cách tệp được mở (ví dụ, chỉ đọc, chỉ ghi, đọc và ghi, v.v.) và có thể bao gồm các tùy chọn khác như tạo tệp nếu nó không tồn tại.

  • mode xác định các quyền tệp nếu tệp được tạo mới. mode chỉ có ý nghĩa khi tạo tệp mới (ví dụ, khi sử dụng flag O_CREAT) và thường được thiết lập bằng cách sử dụng một tổ hợp của các hằng số như S_IRUSR (đọc bởi chủ sở hữu), S_IWUSR (ghi bởi chủ sở hữu), v.v.

Khi thực hiện syscall sys_open, các tham số được truyền qua các thanh ghi như sau:

  • rdi chứa địa chỉ của pathname, tức là con trỏ đến chuỗi ký tự đường dẫn tệp.

  • rsi chứa flags.

  • rdx chứa mode.

Sau khi gọi sys_open, file descriptor cho tệp mở sẽ được trả về trong rax. Nếu có lỗi, giá trị âm được trả về, thường là một trong các mã lỗi được định nghĩa trong errno.h (ví dụ, -ENOENT cho "No such file or directory").

Ví dụ, để mở một tệp chỉ đọc, bạn có thể sử dụng các lệnh assembly sau:

mov    rdi, pathname ; Địa chỉ của chuỗi đường dẫn tệp
mov    rsi, O_RDONLY ; Flags chỉ đọc
xor    rdx, rdx      ; Mode không cần thiết vì không tạo tệp mới
mov    rax, 2        ; Syscall number cho sys_open
syscall              ; Thực hiện syscall

Trong ví dụ trên, xor rdx, rdx thiết lập rdx thành 0, vì mode không cần thiết khi mở tệp chỉ đọc và không tạo tệp mới.

sys_mmap

sys_mmap là một syscall trong hệ điều hành Linux, cho phép một chương trình ánh xạ một phần của file hoặc thiết bị vào không gian địa chỉ của nó hoặc tạo ra một vùng bộ nhớ để sử dụng chung giữa các tiến trình. mmap cung cấp một cách hiệu quả để đọc hoặc ghi dữ liệu vào file bằng cách sử dụng bộ nhớ ảo, thay vì sử dụng các lệnh đọc và ghi trực tiếp.

Cú pháp của syscall mmap trên kiến trúc x86-64 là:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

Trong đó:

  • addr là địa chỉ mong muốn cho vùng bộ nhớ được ánh xạ. Thông thường, giá trị này là NULL, cho phép hệ thống chọn địa chỉ.

  • length là kích thước của vùng bộ nhớ cần ánh xạ.

  • prot xác định quyền truy cập cho vùng bộ nhớ (ví dụ, PROT_READ, PROT_WRITE).

  • flags xác định kiểu ánh xạ và hành vi (ví dụ, MAP_SHARED hoặc MAP_PRIVATE).

  • fd là file descriptor của file cần ánh xạ vào bộ nhớ. Đối với một vùng bộ nhớ không liên kết với file, giá trị này là -1.

  • offset là vị trí bắt đầu trong file để ánh xạ.

Trong kiến trúc x86-64, các tham số cho mmap được truyền qua các thanh ghi như sau:

  • rdi cho addr

  • rsi cho length

  • rdx cho prot

  • r10 cho flags

  • r8 cho fd

  • r9 cho offset

Ví dụ, để ánh xạ một file vào bộ nhớ với quyền chỉ đọc, bạn có thể thiết lập các thanh ghi và gọi mmap như sau:

mov    rdi, 0          ; addr = NULL, để hệ thống chọn địa chỉ
mov    rsi, length     ; length của vùng bộ nhớ cần ánh xạ
mov    rdx, PROT_READ  ; prot = PROT_READ
mov    r10, MAP_SHARED ; flags = MAP_SHARED
mov    r8, fd          ; fd của file
mov    r9, 0           ; offset = 0
mov    rax, 9          ; syscall number cho mmap
syscall                ; gọi syscall

Sau khi mmap được thực thi, rax sẽ chứa địa chỉ bắt đầu của vùng bộ nhớ được ánh xạ nếu thành công, hoặc một giá trị lỗi (âm) nếu có lỗi xảy ra.

sys_mprotect

Lệnh sys_mprotect là một syscall trong Linux được sử dụng để thay đổi quyền truy cập của một vùng bộ nhớ đã được ánh xạ. Quyền này bao gồm khả năng đọc, ghi, hoặc thực thi mã trong vùng bộ nhớ đó. mprotect giúp tăng cường bảo mật bằng cách ngăn chặn sự thay đổi không mong muốn hoặc thực thi mã độc hại từ các vùng bộ nhớ nhạy cảm.

Cú pháp của mprotect trên kiến trúc x86-64 là:

int mprotect(void *addr, size_t len, int prot);

Trong đó:

  • addr là địa chỉ bắt đầu của vùng bộ nhớ mà quyền truy cập cần được thay đổi.

  • len là kích thước của vùng bộ nhớ.

  • prot là tập hợp mới của quyền truy cập được yêu cầu cho vùng bộ nhớ, được chỉ định bởi các hằng số như PROT_READ, PROT_WRITE, và PROT_EXEC.

Khi mprotect được gọi, các tham số được truyền qua các thanh ghi như sau:

  • rdi chứa addr

  • rsi chứa len

  • rdx chứa prot

Ví dụ, nếu một chương trình muốn thay đổi quyền truy cập của một vùng bộ nhớ để cho phép thực thi mã (ví dụ: sau khi tải mã máy từ một file hoặc sau khi sinh mã máy động), nó có thể sử dụng sys_mprotect như sau:

mov    rdi, addr      ; Địa chỉ bắt đầu của vùng bộ nhớ
mov    rsi, len       ; Kích thước của vùng bộ nhớ
mov    rdx, PROT_READ | PROT_EXEC ; Cập nhật quyền truy cập để cho phép đọc và thực thi
mov    rax, 10        ; syscall number cho mprotect
syscall               ; Gọi syscall

rax trước khi gọi syscall chứa số hiệu của mprotect, và sau khi gọi, rax sẽ chứa kết quả của cuộc gọi: 0 nếu thành công, hoặc -errno nếu có lỗi. Điều này cho phép chương trình kiểm tra và xử lý lỗi nếu cần.

mprotect rất quan trọng trong việc bảo vệ bộ nhớ và quản lý quyền truy cập, đặc biệt là trong các ứng dụng cần thực thi mã sinh động hoặc bảo mật bộ nhớ chống lại sự thay đổi không mong muốn.

sys_close

sys_close là một syscall trong Linux dùng để đóng một file descriptor, giải phóng tài nguyên mà file hoặc socket đó sử dụng. Điều này giúp hệ điều hành quản lý tài nguyên hiệu quả hơn, ngăn chặn lãng phí tài nguyên do file descriptors không được đóng sau khi sử dụng.

Cú pháp của close trên kiến trúc x86-64 là:

int close(int fd);

Trong đó:

  • fd là file descriptor của file hoặc socket cần được đóng.

Khi close được gọi, file descriptor fd được truyền qua thanh ghi RDI.

Ví dụ, nếu một chương trình muốn đóng file descriptor 3, nó có thể sử dụng sys_close như sau:

mov    rdi, 3         ; File descriptor cần đóng
mov    rax, 3         ; syscall number cho close
syscall               ; Gọi syscall

rax trước khi gọi syscall chứa số hiệu của close, và sau khi gọi, rax sẽ chứa kết quả của cuộc gọi: 0 nếu thành công, hoặc -errno nếu có lỗi. Điều này cho phép chương trình kiểm tra và xử lý lỗi nếu cần.

sys_close rất quan trọng trong việc quản lý tài nguyên hệ thống, đảm bảo rằng tài nguyên không bị lãng phí và hệ điều hành có thể tái sử dụng file descriptors hiệu quả.

sys_munmap

sys_munmap là một syscall trong Linux dùng để giải phóng một vùng bộ nhớ đã được ánh xạ (mapped) vào không gian địa chỉ của tiến trình. Điều này thường xảy ra sau khi tiến trình không còn cần đến dữ liệu trong vùng bộ nhớ đó, hoặc khi tiến trình kết thúc và muốn giải phóng tài nguyên một cách sạch sẽ.

Cú pháp của munmap trên kiến trúc x86-64 là:

int munmap(void *addr, size_t length);

Trong đó:

  • addr là địa chỉ bắt đầu của vùng bộ nhớ mà tiến trình muốn giải phóng.

  • length là kích thước của vùng bộ nhớ đó.

Khi munmap được gọi, các tham số được truyền qua các thanh ghi như sau:

  • rdi chứa addr, địa chỉ bắt đầu của vùng bộ nhớ.

  • rsi chứa length, kích thước của vùng bộ nhớ.

Ví dụ, nếu một chương trình muốn giải phóng một vùng bộ nhớ mà nó đã ánh xạ trước đó, nó có thể sử dụng sys_munmap như sau:

mov    rdi, addr      ; Địa chỉ bắt đầu của vùng bộ nhớ
mov    rsi, length    ; Kích thước của vùng bộ nhớ
mov    rax, 11        ; syscall number cho munmap
syscall               ; Gọi syscall

rax trước khi gọi syscall chứa số hiệu của munmap, và sau khi gọi, rax sẽ chứa kết quả của cuộc gọi: 0 nếu thành công, hoặc -errno nếu có lỗi. Điều này cho phép chương trình kiểm tra và xử lý lỗi nếu cần.

sys_munmap đóng một vai trò quan trọng trong quản lý bộ nhớ của tiến trình, giúp đảm bảo rằng tài nguyên bộ nhớ được giải phóng một cách hiệu quả sau khi không còn cần thiết, từ đó giảm thiểu lãng phí bộ nhớ và nguy cơ xảy ra lỗi bộ nhớ.

Cụ thể:

Sử dụng Detect It Easy, có thể thấy chương trình này bị pack sử dụng UPX

Có thể sử dụng upx -d để unpack. Tuy nhiên ở đây mình sẽ thử unpack bằng tay coi sao. Đầu tiên mình thử tạo ra 1 file đơn giản để test

#include <stdio.h>

int main(){

        printf("Hello World!");

        return 0;
}

Sau đó mình compile và sử dụng UPX để pack nó lại.

Mình sử dụng lệnh strace để so sánh luồng thực thi của 2 chương trình trước và sau unpack

Mình nhận thấy có một số các syscall được gọi thêm trước khi chương trình thực thi. Dựa trên các lệnh được gọi chủ yếu là mmapmprotect, mình có thể nhận thấy chương trình đang đưa code được unpack vào một vùng nhớ mới để excecute nó trên vùng nhớ đó. Việc setup vùng nhớ này sẽ dừng lại khi ta munmap.

Ở đây mình sẽ sử dụng Radare2 để debug:

Mình sử dụng lệnh dcs để thực thi chương trình cho tới khi gặp syscall. Lặp lại cho tới khi tới được syscall munmap (Dựa trên strace thì sẽ có 2 cái)

Mở assembly code, có thể thấy chúng ta chuẩn bị pop stack và nhảy tới địa chỉ nào đó. Đó chính là nơi chương trình của chúng ta được unpack ra.

Sau khi ret, ta được đoạn code như sau:

Có thể thấy, đây chưa phải hàm main của chúng ta nhưng từ địa chỉ 0x00401620 tới 0x00401645 khá giống mã assembly của __libc_start_main, ta có thể đoán địa chỉ trong rdi chính là địa chỉ của hàm main. Sử dụng lệnh dr để set rip thành địa chỉ trên, mình tìm ra hàm main

Giờ thì áp dụng đối với file recon của chúng ta.

Dựa vào strace, có vẻ chỉ có 1 lần gọi munmap. Tiến hành debug trên Radare2

Đây là OEP của chương trình

Mình thử step tiếp để tìm hàm main. Cho tới khi mình đến đoạn jmp r12 thì mình nhảy được tới đoạn code như sau:

Dựa trên file helloworld mình dùng để test thì dễ dàng nhận ra đây là hàm __libc_start_main. Vậy địa chỉ hàm main chính là địa chỉ được đưa vào rdi, hay 0x401f60. Trỏ RIP tới đó và mình thấy được hàm main

Để đọc code thì giờ là chuyện đơn giản. Tuy nhiên để dump code ra bằng Radare2 hay IDA thì mình vẫn còn hơi bí bách

Tuy nhiên, mình đã tìm thấy tool này: https://github.com/enbarberis/core2ELF64. Do chương trình chạy xong k tự động terminate nên mình có thể dump core của chương trình khi nó chạy sau đó sử dụng tool trên để lấy ra file ELF

Khôi phục ELF:

Test file kết quả:

File final:

Sau khi unpack (phân tích hành vi)

Sau khi unpack, ta tiến hành vào xem hàm main. Sử dụng F5 trong IDA để đọc psuedo

int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rax
  FILE *v4; // rbx
  struct ifaddrs *i; // rbx
  struct sockaddr *ifa_addr; // rsi
  struct ifaddrs *v7; // rbx
  struct sockaddr *v8; // rax
  int v9; // r8d
  int v10; // eax
  struct tm *v11; // rax
  struct ifaddrs *ifa; // [rsp+0h] [rbp-4B8h] BYREF
  time_t timer; // [rsp+8h] [rbp-4B0h] BYREF
  void *v15; // [rsp+10h] [rbp-4A8h] BYREF
  char v16; // [rsp+20h] [rbp-498h] BYREF
  char buf[96]; // [rsp+30h] [rbp-488h] BYREF
  int v18; // [rsp+90h] [rbp-428h]
  char ptr[1000]; // [rsp+A0h] [rbp-418h] BYREF
  unsigned __int64 v20; // [rsp+488h] [rbp-30h]

  v20 = __readfsqword(0x28u);
  ifa = 0LL;
  puts("=========================get pc info===========================");
  v3 = fopen("/proc/cpuinfo", "r");
  if ( v3 )
  {
    v4 = v3;
    fread(ptr, 0x3E8uLL, 1uLL, v3);
    fclose(v4);
    __printf_chk(1LL, ptr);
    putchar(10);
  }
  puts("=========================get pc info end=======================\n\n");
  puts("=========================get local ip===========================");
  getifaddrs(&ifa);
  for ( i = ifa; i; i = i->ifa_next )
  {
    while ( 1 )
    {
      ifa_addr = i->ifa_addr;
      if ( ifa_addr )
      {
        if ( ifa_addr->sa_family == 2 )
          break;
      }
      i = i->ifa_next;
      if ( !i )
        goto LABEL_9;
    }
    inet_ntop(2, &ifa_addr->sa_data[2], buf, 0x10u);
    __printf_chk(1LL, "%s IP Address %s\n", i->ifa_name, buf);
  }
LABEL_9:
  puts("=========================get local ip end===========================\n\n");
  puts("===========================nmap start==========================");
  v7 = ifa;
  if ( ifa )
  {
    do
    {
      while ( 1 )
      {
        v8 = v7->ifa_addr;
        if ( v8 )
        {
          if ( v8->sa_family == 2 )
          {
            v9 = *(_DWORD *)&v8->sa_data[2];
            if ( v9 != 16777343 )
            {
              memset(buf, 0, sizeof(buf));
              v10 = v9 + 255;
              if ( v9 >= 0 )
                v10 = v9;
              v18 = 0;
              __sprintf_chk(
                buf,
                1LL,
                100LL,
                "nmap %d.%d.0.0/16",
                (unsigned __int8)(((unsigned int)(v9 >> 31) >> 24) + v9) - ((unsigned int)(v9 >> 31) >> 24),
                (unsigned __int8)(((unsigned int)(v10 >> 31) >> 24) + BYTE1(v10)) - ((unsigned int)(v10 >> 31) >> 24));
              exec[abi:cxx11](&v15, buf);
              __printf_chk(1LL, (const char *)v15);
              putchar(10);
              if ( v15 != &v16 )
                break;
            }
          }
        }
        v7 = v7->ifa_next;
        if ( !v7 )
          goto LABEL_19;
      }
      operator delete(v15);
      v7 = v7->ifa_next;
    }
    while ( v7 );
LABEL_19:
    if ( ifa )
      freeifaddrs(ifa);
  }
  puts("===========================nmap end============================\n\n");
  timer = time(0LL);
  v11 = localtime(&timer);
  __sprintf_chk(
    buf,
    1LL,
    100LL,
    "gnome-screenshot -f %d-%d-%d_%d:%d:%d",
    (unsigned int)v11->tm_wday,
    (unsigned int)v11->tm_mon,
    (unsigned int)(v11->tm_year + 1900),
    (unsigned int)v11->tm_hour,
    (unsigned int)v11->tm_min,
    (unsigned int)v11->tm_sec);
  system(buf);
  return 0;
}

Có thể thấy, chương trình này thực hiện những việc sau:

  1. Khởi tạo và đọc thông tin CPU:

    • Mở file /proc/cpuinfo để đọc thông tin về CPU của máy. Thông tin này sau đó được in ra màn hình.

  2. Thu thập địa chỉ IP cục bộ:

    • Sử dụng hàm getifaddrs để lấy danh sách các interface mạng và địa chỉ IP tương ứng.

    • Chỉ in ra các địa chỉ IP thuộc loại IPv4 (sa_family == 2). Địa chỉ được chuyển đổi sang dạng chuỗi và in ra màn hình cùng với tên của interface.

  3. Sử dụng Nmap để quét mạng:

    • Lặp qua lại danh sách các interface mạng như trước, nhưng lần này, nếu tìm thấy địa chỉ IPv4 hợp lệ (khác với địa chỉ loopback 127.0.0.1), chương trình sẽ tạo một lệnh nmap để quét mạng dựa trên địa chỉ IP của interface.

    • Lệnh này được thiết kế để quét một subnet mạng bằng cách sử dụng địa chỉ IP của interface như một điểm bắt đầu.

    • Lưu ý rằng có một lời gọi hàm exec bị sai syntax (exec[abi:cxx11](&v15, buf);), có vẻ như là một lỗi hoặc chỗ để giữ chỗ vì nó không phải là cú pháp hợp lệ trong C.

  4. Chụp ảnh màn hình:

    • Lấy thời gian hiện tại và chuyển nó thành định dạng tm để sử dụng trong việc tạo tên file cho ảnh chụp màn hình.

    • Sử dụng hàm system để gọi lệnh gnome-screenshot với tên file được định dạng bởi ngày giờ hiện tại.

  5. Quản lý bộ nhớ:

    • Cuối cùng, chương trình giải phóng bộ nhớ đã cấp phát cho danh sách các interface mạng bằng hàm freeifaddrs nếu cần.

Giao diện console khi chương trình này được chạy cũng cho thấy rằng lệnh nmap có vẻ bị lỗi:

Phân tích thêm, ở đây khi mình nhìn vào strings của chương trình:

Ta có thể thấy có các message liên quan tới việc gửi email. Có vẻ chương trình được thiết kế để gửi các thông tin thu thập được về email của hacker. Tuy nhiên nó đã không được sử dụng bằng cách nào đó, làm cho chương trình này có vẻ vô hại.

© 2024,Pham Quoc Trung. All rights reserved.

Last updated