Unpack
Malware Analysis
Last updated
Malware Analysis
Last updated
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.
Để 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.
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.
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.
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
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.
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à:
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:
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
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à:
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:
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.
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à:
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:
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
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à:
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:
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
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à:
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:
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ớ.
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
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à mmap
và mprotect
, 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, ta tiến hành vào xem hàm main. Sử dụng F5 trong IDA để đọc psuedo
Có thể thấy, chương trình này thực hiện những việc sau:
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.
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.
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.
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.
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.