Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Arm’d & Dangerous: Analyzing arm64 Malware Targ...

Arm’d & Dangerous: Analyzing arm64 Malware Targeting macOS

Apple's new M1 systems offer a myriad of benefits ...for both macOS users, and unfortunately, to malware authors as well.

In this talk we detail the first malicious programs compiled to natively target Apple Silicon (M1/arm64), focusing on methods of analysis.

We'll start with a few foundation topics, such as methods of identifying native M1 code (which will aid us when hunting for M1 malware), as well as introductory arm64 reversing concepts.

With an uncovered corpus of malware compiled to natively run on M1 (and in some cases notarized by Apple!), we'll spend the remainder of the talk demonstrating effective analysis techniques, including many specific to the analysis of arm64 code on macOS.

Armed with this information and analysis techniques, you'll leave a proficient macOS M1 malware analyst!

Patrick Wardle

August 03, 2021
Tweet

More Decks by Patrick Wardle

Other Decks in Technology

Transcript

  1. WHOIS @patrickwardle tools, blog, & malware collection "Objective by the

    Sea" (macOS security conference) Book(s): "The Art of Mac Malware"
  2. Understanding arm64 OUTLINE Introduction Analyzing M1 malware Hunting Malware natively

    compiled to run on Apple Silicon. More specifically, arm64 malware targeting Macs (macOS). "M1 malware" defined:
  3. THE GROWTH OF MACS …and unsurprisingly, the growth of macOS

    malware More Macs More Mac Malware 
 (credit: MalwareBytes) As macOS becomes more prevalent, (rather obviously), so too does malware targeting this platform. more than 
 Windows !?
  4. THE GROWTH OF MACS …driven largely by [the] M1? "Fueled

    by the M1, we set an all-time [Mac] revenue record continuing the momentum for the product category" -Tim Cook (Apple)
  5. WHAT IS M1? (AKA "APPLE SILICON") an arm-based system on

    a chip (SoC) {Multiple technologies combined on a single chip CPU: 
 arm64 instruction set ...malware native to this CPU, will disassemble into Arm (vs. Intel). The M1 chip
  6. AND ROSETTA(2) …run intel-based apps on apple silicon (was) Prone

    to crashes Translation -> slow-down Rosetta (oahd-helper) crash Translation Intel app Arm instructions M1 CPU "not a substitute for creating a native version of your app" -apple
  7. WHY TALK ABOUT M1 MALWARE well, several important reasons! Is

    (was) inevitable no rosetta crashes Missed AV detections (known malware: 10%+ drop) movz x0, #0x1a movz x1, #0x1f movz x2, #0x0 movz x3, #0x0 movz x16, #0x0 svc #0x80 01 02 03 04 05 06 07 08 }same 
 malware Intel version: detected Arm version: not detected undetected Disassembly is arm64 an unfamiliar instructions set!? native code, faster
  8. HOW TO IDENTIFY M1 CODE in short, a macOS binary

    with arm64/e % file Calculator.app/Contents/MacOS/Calculator Mach-O universal binary with 2 architectures: Mach-O 64-bit executable x86_64 Mach-O 64-bit executable arm64e % lipo -archs Calculator.app/Contents/MacOS/Calculator x86_64 arm64e % otool -lv Calculator.app/Contents/MacOS/Calculator … Load command 10 cmd LC_BUILD_VERSION cmdsize 32 platform MACOS minos 11.4 sdk 11.4 arm64/arm64e code 
 (may be found in universal binary) …built for macOS (also: LC_VERSION_MIN_MACOSX) What's arm64e? arm64 enhanced +pointer auth, etc. header intel binary arm64 binary Universal binary
  9. HUNTING FOR M1 MALWARE querying virustotal for specimens type:macho tag:arm

    tag:64bits tag:multi-arch NOT engines:IOS positives:2+ tag:macho 
 apple executable tag:arm 
 contains arm code tag:64bits 
 contains 64bit code tag: multi-arch 
 universal binary NOT engines:IOS 
 not an iOS binary positives:2+ 
 flagged by 2+ AV engines 

  10. TRIAGING GOSEARCH22 a candidate (M1) binary % file GoSearch22 Mach-O

    universal binary with 2 architectures: [arm64:Mach-O 64-bit executable arm64] [x86_64:Mach-O 64-bit executable x86_64] % otool -lv GoSearch22 ... Load command 9 cmd LC_VERSION_MIN_MACOSX version 10.12 Universal macOS binary (with arm64) Flagged by several AV engines (intel code?) 
 
 + app’s cert. revoked Certificate revoked 
 (by Apple)
  11. HOW DID IT END UP ON VIRUSTOTAL? …detected and submitted

    via KnockKnock! via API (via KnockKnock) KnockKnock detection 
 (free: objective-see.com)
  12. first, some most excellent resources A BRIEF INTRODUCTION TO ARM64

    “Modern Arm Assembly 
 Language Programming” (Daniel Kusswurm) "arm64 Assembly Crash Course" 
 github.com/Siguza/ios-resources/blob/master/bits/arm64.md 
 "How to Read ARM64 Assembly Language" 
 wolchok.org/posts/how-to-read-arm64-assembly-language/ "Introduction To Arm Assembly Basics" 
 azeria-labs.com/writing-arm-assembly-part-1/ free, online
  13. REGISTERS and their uses Registers: temporary storage "slots" on the

    CPU that can referenced by name. (somewhat) synonymous to variables in your fav. programming language arm64 31 64-bit registers: x0 - x30 { 63 0 31 w* (e.g. w0) sp: stack pointer 
 pc: program counter 
 xzr: virtual register, value: 0 N Z C V ... PSTATE 
 (processor state) N: negative 
 Z: zero 
 C: carry 
 V: overflow Condition flags
  14. REGISTERS usage, during a function call During analysis, we largely

    focus on api calls and their arguments. arg 0 x0 arg 1 x1 ... x30 (lr) Return address: x0 Return value: (64/128 bits) x1 arg 7 x7 Arguments: x29 (fp) Frame pointer
  15. INSTRUCTIONS instruct the cpu what to do Instructions: map to

    a specific sequence of bytes that instructs the CPU to perform an operation. add x1 x0 42 Mnemonic: 
 a (human-readable) abbreviation of the operation that the instructions perform. in C: x1 = x0 + 42;
  16. INSTRUCTIONS the operands add x1 x0 42 Operand types: {

    Operands Immediate: 
 a constant value (e.g. 42) Register: 
 a cpu register (e.g. x0, x1) Memory: 
 a cpu register, that points to a value in memory 1st register 
 (usually) destination
  17. MEMORY ACCESS MODEL arm’s model is a "load & store"

    "ARM uses a load-store model for memory access which means that only load/store (LDR and STR) instructions can access memory. 
 
 …on ARM data must be moved from memory into registers before being operated on" -Maria Markstedter (Azeria Labs) Load (into register) Perform any operation(s) Store (into memory) …a few other variants, ldp/stp
  18. MEMORY ACCESS MODEL load via the ldr instruction (+ variants)

    ldr x1 [x0] Dest. register Src. register 
 (memory address) x1 Analogous statement (in C): x1 = *x0;
  19. MEMORY ACCESS MODEL store via the str instruction (+ variants)

    str x1 [x0] Src. register Dest. register 
 (memory address) x1 Analogous statement (in C): *x0 = x1;
  20. CONDITIONS set via cmp, etc… if( isDebugged ) exit N

    Z C V ... PSTATE 
 (processor state) Condition flags cmp x0 42 (discarded)subtract 
 updates PSTATE flags e.g. x0 is 42? Z flag is set
  21. CONDITIONS condition codes Once (condition) flags have been set, subsequent

    instructions can act upon them using condition codes bl amBeingDebugged 
 cmp w0, #1 
 b.ne continue 
 movn w0, #0x0 
 bl exit 
 
 continue: 
 … 01 02 03 04 05 06 07 08 Name Meaning EQ equal NE not equal GE greater or equal GT greater than LE lesser or equal LT less than ... b.ne label Branch (jump), if Z not set exit if debugged } Condition codes
  22. BRANCHES alter control flow of a program b/br imm/register Branch

    (unconditionally) bl amBeingDebugged 
 cmp w0, #1 
 b.ne continue 
 movn w0, #0x0 
 bl exit 
 
 continue: 
 … 01 02 03 04 05 06 07 08 b.cond imm Branch (if condition met) bl/blr imm/register Branch (store address of next instruction in x30 (lr)) bl amBeingDebugged 
 cmp w0, #1 
 ... 01 02 03 x30 (lr) &next instruction 
 (cmp w0, #1) ret e.g. function call Branch back to x30 (lr)
  23. REVERSING "HELLO, WORLD!" macOS arm64 version int main(int argc, char

    * argv[]) { 
 @autoreleasepool { 
 NSLog(@"Hello, World!"); 
 } 
 return 0; 
 } 01 02 03 04 05 06 main: 
 sub sp, sp, #0x30 
 stp x29, x30, [sp, #0x20] 
 add x29, sp, #0x20 
 movz w8, #0x0 
 stur wzr, [x29, #-0x4] 
 stur w0, [x29, #-0x8] 
 str x1, [sp, #0x10] 
 str w8, [sp, #0xc] 
 bl objc_autoreleasePoolPush 
 adrp x9, #0x0000000100004000 
 add x9, x9, #0x8 ; @"Hello, World!" 
 str x0, [sp] 
 mov x0, x9 
 bl NSLog 
 ldr x0, [sp] 
 bl objc_autoreleasePoolPop 
 ldr w0, [sp, #0xc] 
 ldp x29, x30, [sp, #0x20] 
 add sp, sp, #0x30 
 ret 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 (Apple’s) "Hello, World!" Note, @autoreleasepool: objc_autoreleasePoolPush objc_autoreleasePoolPop "Hello, World!" 
 disassembled +
  24. REVERSING "HELLO, WORLD!" function prologue Offset Value 0x30 0x28 x30

    0x20 x29 ... 0x00 sp sp x29 Subtract 0x30 from stack pointer Store x29 & x30 at SP + 0x20 Set frame pointer (x29) to sp + 0x20 main: 
 sub sp, sp, #0x30 
 stp x29, x30, [sp, #0x20] 
 add x29, sp, #0x20 
 movz w8, #0x0 
 stur wzr, [x29, #-0x4] 
 stur w0, [x29, #-0x8] 
 str x1, [sp, #0x10] 
 str w8, [sp, #0xc] 
 bl objc_autoreleasePoolPush 
 adrp x9, #0x0000000100004000 
 add x9, x9, #0x8 ; @"Hello, World!" 
 str x0, [sp] 
 mov x0, x9 
 bl NSLog 
 ldr x0, [sp] 
 bl objc_autoreleasePoolPop 
 ldr w0, [sp, #0xc] 
 ldp x29, x30, [sp, #0x20] 
 add sp, sp, #0x30 
 ret 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 Function prologue makes space on the stack for saving registers, local variables, and init’s frame pointer } Save registers/init local variables
  25. REVERSING "HELLO, WORLD!" invoking objc_autoreleasePoolPush Branch to (call) objc_autoreleasePoolPush 


    
 address of next instruction, stored in link register (x30) main: 
 sub sp, sp, #0x30 
 stp x29, x30, [sp, #0x20] 
 add x29, sp, #0x20 
 movz w8, #0x0 
 stur wzr, [x29, #-0x4] 
 stur w0, [x29, #-0x8] 
 str x1, [sp, #0x10] 
 str w8, [sp, #0xc] 
 bl objc_autoreleasePoolPush 
 adrp x9, #0x0000000100004000 
 add x9, x9, #0x8 ; @"Hello, World!" 
 str x0, [sp] 
 mov x0, x9 
 bl NSLog 
 ldr x0, [sp] 
 bl objc_autoreleasePoolPop 
 ldr w0, [sp, #0xc] 
 ldp x29, x30, [sp, #0x20] 
 add sp, sp, #0x30 
 ret 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 objc_autoreleasePoolPush takes no arguments ...returns a pointer to a pool object (in x0). Return value (x0: pool object) saved to local variable
  26. REVERSING "HELLO, WORLD!" invoking NSLog with the "Hello, World!" string

    Initialize address to 
 “Hello World!" (string) object main: 
 sub sp, sp, #0x30 
 stp x29, x30, [sp, #0x20] 
 add x29, sp, #0x20 
 movz w8, #0x0 
 stur wzr, [x29, #-0x4] 
 stur w0, [x29, #-0x8] 
 str x1, [sp, #0x10] 
 str w8, [sp, #0xc] 
 bl objc_autoreleasePoolPush 
 adrp x9, #0x0000000100004000 
 add x9, x9, #0x8 ; @"Hello, World!" 
 str x0, [sp] 
 mov x0, x9 
 bl NSLog 
 ldr x0, [sp] 
 bl objc_autoreleasePoolPop 
 ldr w0, [sp, #0xc] 
 ldp x29, x30, [sp, #0x20] 
 add sp, sp, #0x30 
 ret 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 Here, NSLog is invoked with a single argument, the address of the string object to print (passed in x0). Initialize 1st argument with address of string object Branch to (call) NSLog function
  27. REVERSING "HELLO, WORLD!" invoking objc_autoreleasePoolPop with pool object main: 


    sub sp, sp, #0x30 
 stp x29, x30, [sp, #0x20] 
 add x29, sp, #0x20 
 movz w8, #0x0 
 stur wzr, [x29, #-0x4] 
 stur w0, [x29, #-0x8] 
 str x1, [sp, #0x10] 
 str w8, [sp, #0xc] 
 bl objc_autoreleasePoolPush 
 adrp x9, #0x0000000100004000 
 add x9, x9, #0x8 ; @"Hello, World!" 
 str x0, [sp] 
 mov x0, x9 
 bl NSLog 
 ldr x0, [sp] 
 bl objc_autoreleasePoolPop 
 ldr w0, [sp, #0xc] 
 ldp x29, x30, [sp, #0x20] 
 add sp, sp, #0x30 
 ret 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 objc_autoreleasePoolPop takes a single argument, the address of the pool object to release (passed in x0). Initialize 1st argument with address pool object (previous stored on the stack) Branch to (call) objc_autoreleasePoolPop function
  28. REVERSING "HELLO, WORLD!" function epilogue main: 
 sub sp, sp,

    #0x30 
 stp x29, x30, [sp, #0x20] 
 add x29, sp, #0x20 
 movz w8, #0x0 
 stur wzr, [x29, #-0x4] 
 stur w0, [x29, #-0x8] 
 str x1, [sp, #0x10] 
 str w8, [sp, #0xc] 
 bl objc_autoreleasePoolPush 
 adrp x9, #0x0000000100004000 
 add x9, x9, #0x8 ; @"Hello, World!" 
 str x0, [sp] 
 mov x0, x9 
 bl NSLog 
 ldr x0, [sp] 
 bl objc_autoreleasePoolPop 
 ldr w0, [sp, #0xc] 
 ldp x29, x30, [sp, #0x20] 
 add sp, sp, #0x30 
 ret 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 A function epilogue restores saved registers, and (re)adjusts the stack. Return, branches to lr (x30). Initialize return value (previously set to zero) Restore x29/x30 registers (re)Adjust stack Return to caller (lr/x30)
  29. A FUNDAMENTAL UNDERSTANDING OF ARM64 SUFFICE? …often, yes! By leveraging

    a decompiler and dynamic analysis tools, often a fundamental understanding of arm64 will suffice! int main(int arg0, int arg1) { 
 var_20 = objc_autoreleasePoolPush(); 
 NSLog(@"Hello, World!"); 
 objc_autoreleasePoolPop(var_20); 
 return 0x0; 
 } 01 02 03 04 05 06 "Hello, World!" decompiled Dynamic Analysis Tools: Process monitor File monitor Network monitor main: 
 sub sp, sp, #0x30 
 stp x29, x30, [sp, #0x20] 
 add x29, sp, #0x20 
 movz w8, #0x0 
 stur wzr, [x29, #-0x4] 
 stur w0, [x29, #-0x8] 
 str x1, [sp, #0x10] 
 str w8, [sp, #0xc] 
 bl objc_autoreleasePoolPush 
 adrp x9, #0x0000000100004000 
 add x9, x9, #0x8 ; @"Hello, World!" 
 str x0, [sp] 
 mov x0, x9 
 bl NSLog 
 ldr x0, [sp] 
 bl objc_autoreleasePoolPop 
 ldr w0, [sp, #0xc] 
 ldp x29, x30, [sp, #0x20] 
 add sp, sp, #0x30 
 ret 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21
  30. DYNAMIC ANALYSIS TOOLS may (trivially) reveal malware's capabilities # FileMonitor.app/Contents/MacOS/FileMonitor

    -pretty { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/Users/user/Library/LaunchAgents/mdworker.plist", "process" : { "uid" : 501, "arguments" : [ "/bin/sh", "-c", "/Users/user/Desktop/eTrader.app/Contents/Utils/mdworker" ], "path" : "/Users/user/Desktop/eTrader.app/Contents/Utils/mdworker", "name" : "mdworker" } } } Analysis 
 machine Uncovering persistence 
 (via a file monitor) launch agent persistence
  31. ANTI-ANALYSIS LOGIC aim to thwart (dynamic) analysis environments/tools Introspection One

    must identify and bypass anti-analysis mechanisms before comprehensive analysis of a malicious sample can commence! Am I being debugged? Am I in a virtual machine? % lldb GoSearch22.app (lldb) target create "GoSearch22.app" (lldb) c Process 654 resuming Process 654 exited with status = 45 (0x0000002d) GoSearch22 vs. debugger simply terminates :(
  32. GOSEARCH22 …also contains static analysis obfuscations See, "Using LLVM to

    Obfuscate Your Code During Compilation"(www.apriorit.com) Garbage instructions? Spurious function calls Popular obfuscator
  33. GOSEARCH22'S ANTI-ANALYSIS LOGIC debugger detection via ptrace/PT_DENY_ATTACH % lldb GoSearch22.app

    Process 654 exited with status = 45 (0x0000002d) GoSearch22 vs. debugger movz x0, #0x1a 
 movz x1, #0x1f 
 movz x2, #0x0 
 movz x3, #0x0 
 ... 
 svc #0x80 01 02 03 04 05 06 45 (02xd) ENOTSUP (from PT_DENY_ATTACH) Supervisor (system) call } 0x1a: SYS_ptrace 0x1f: PT_DENY_ATTACH 0x0 0x0 Args: ptrace() + PT_DENY_ATTACH, prevents future attachments or terminates (with 45) if a debugger is currently attached.
  34. BYPASSING ANTI-ANALYSIS LOGIC once detected and identified, trivial to bypass

    0x00000001000541f4 movz x3, #0x0 
 0x00000001000541f8 movz x16, #0x0 
 0x00000001000541fc svc #0x80 
 
 0x0000000100054200 movz w11, #0x6b8f 
 01 02 03 04 05 % lldb GoSearch22.app (lldb) b 0x00000001000541fc 
 Breakpoint 1: address = 0x00000001000541fc 
 
 (lldb) Process 1486 stopped * thread #1, queue = 'com.apple.main-thread' stop reason = breakpoint 1.1: -> 0x00000001000541fc svc #0x80 
 (lldb) reg write $pc 0x100054200 simply skip over 
 ptrace system call :) modify PC Modify pc register
  35. GOSEARCH22'S ANTI-ANALYSIS LOGIC system integrity protection (sip) status detection ldr

    x8, [sp, #0x190 + var_120] 
 ldr x0, [sp, #0x190 + var_100] 
 ldr x1, [sp, #0x190 + var_F8] 
 blr x8 01 02 03 04 Two arguments } % lldb GoSearch22.app 
 ... 
 (lldb) x/i $pc -> 0x1000538dc: 0xd63f0100 blr x8 (lldb) reg read $x8 x8 = 0x0000000193a5f160 libobjc.A.dylib`objc_msgSend Debugger introspection As we've identified (and thwarted) the malware's anti- debugging logic, we can now fully leverage the debugger! call to objc_msgSend …but what's the branch target?
  36. GOSEARCH22'S ANTI-ANALYSIS LOGIC Arg 0: self 
 object method is

    invoked upon Arg 1: op 
 selector of method % lldb GoSearch22.app ... 
 
 (lldb) po $x0 
 <NSConcreteTask: 0x1058306c0> 
 
 (lldb) x/s $x1 0x1e9fd4fae: “launch" Debugger introspection [NSTask launch]; system integrity protection (sip) status detection
  37. GOSEARCH22'S ANTI-ANALYSIS LOGIC SIP status detection (lldb) po [$x0 launchPath]

    /bin/sh (lldb) po [$x0 arguments] <__NSArrayI 0x10580dfd0>( -c, command -v csrutil > /dev/null && csrutil status | 
 grep -v "enabled" > /dev/null && echo 1 || echo 0 ) NSTask 
 + it's properties % csrutil status System Integrity Protection status: disabled. analysis machine SIP status detection 
 (via "csrutil status") SIP: disabled? 
 (malware exits!)
  38. GOSEARCH22'S ANTI-ANALYSIS LOGIC virtual machine detection ldr x8, [sp, #0x190

    + var_120] 
 ldr x0, [sp, #0x190 + var_100] 
 ldr x1, [sp, #0x190 + var_F8] 
 blr x8 01 02 03 04 (another) call 
 to obj_msgSend (lldb) po $x0 <NSConcreteTask: 0x1058306c0> 
 
 (lldb) po [$x0 launchPath] /bin/sh (lldb) po [$x0 arguments] <__NSArrayI 0x10580c1f0> ( -c, readonly VM_LIST="VirtualBox\|Oracle\|VMware\|Parallels\|qemu";is_hwmodel_vm(){ ! sysctl -n hw.model|grep "Mac">/dev/null;};is_ram_vm(){(($(($(sysctl -n hw.memsize)/ 1073741824))<4));};is_ped_vm(){ local -r ped=$ (ioreg -rd1 -c IOPlatformExpertDevice);echo "${ped}"|grep -e "board-id" -e "product-name" -e "model"|grep -qi "${VM_LIST}"||echo "${ped}"|grep "manufacturer"|grep -v "Apple">/dev/null;};is_vendor_name_vm(){ ioreg -l|grep -e "Manufacturer" -e "Vendor Name"|grep -qi "${VM_LIST}";};is_hw_data_vm(){ system_profiler SPHardwareDataType 2>&1 /dev/null|grep -e "Model Identifier"|grep -qi "${VM_LIST}";};is_vm() { is_hwmodel_vm||is_ram_vm||is_ped_vm||is_vendor_name_vm||is_hw_data_vm;};main(){ is_vm&&echo 1||echo 0;};main “${@}" ) looks for artifacts from 
 various virtualization products virtual machine detection
  39. ...AND MORE! notarization, infection numbers, etc... "OSX/Hydromac: New Mac adware,

    leaked from a flashcards app" 
 (Taha Karim (@lordx64) objective-see.com/blog/blog_0x65.html) 
 OSX.Hydromac (notarized!) notarized by Apple OSX.SilverSparrow (30k+ infections!)
  40. KEY TAKEAWAYS } Hunting for 
 native M1 malware Understanding

    arm64 Practical M1 
 malware analysis M1 malware 
 is here to stay Armed with the topics presented here today, you're well on the way to becoming a proficient analyst of m1 malware!
  41. LEARN MORE? arm64, malware analysis, macOS security topics “Modern Arm

    Assembly 
 Language Programming” "Objective by the Sea" Sept 30/Oct 1 Maui, Hawaii, USA ObjectiveByTheSea.com "The Art of Mac Malware” 
 taomm.org
  42. MAHALO! "Friends of Objective-See" Guardian Mobile Firewall SecureMac SmugMug iVerify

    Halo Privacy Grab the Slides: 
 speakerdeck.com/patrickwardle uberAgent
  43. RESOURCES: Arm’d & Dangerous "Modern Arm Assembly Language Programming" 


    www.apress.com/gp/book/9781484262665 
 
 "arm64 Assembly Crash Course" 
 github.com/Siguza/ios-resources/blob/master/bits/arm64.md 
 
 "How to Read arm64 Assembly Language" 
 wolchok.org/posts/how-to-read-arm64-assembly-language/ 
 
 "Introduction To Arm Assembly Basics" 
 azeria-labs.com/writing-arm-assembly-part-1/