mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-03-30 14:00:16 +02:00
This commit is contained in:
219
README.md
219
README.md
@@ -33,11 +33,47 @@ Each article directory contains three subdirectories:
|
||||
Core program resulting from the Snake&Apple article series for binary analysis. You may find older versions of this script in each article directory in this repository.
|
||||
* Usage
|
||||
```console
|
||||
usage: CrimsonUroboros [-h] -p PATH [--file_type] [--header_flags] [--endian] [--header] [--load_commands] [--segments] [--sections] [--symbols] [--imported_symbols] [--chained_fixups] [--exports_trie] [--uuid] [--main] [--encryption_info [(optional) save_path.bytes]] [--strings_section] [--all_strings]
|
||||
[--save_strings all_strings.txt] [--info] [--verify_signature] [--cd_info] [--cd_requirements] [--entitlements [human|xml|var]] [--extract_cms cms_signature.der] [--extract_certificates certificate_name] [--remove_sig unsigned_binary] [--sign_binary [adhoc|identity]] [--has_pie] [--has_arc]
|
||||
[--is_stripped] [--has_canary] [--has_nx_stack] [--has_nx_heap] [--has_xn] [--is_notarized] [--is_encrypted] [--is_restricted] [--is_hr] [--is_as] [--is_fort] [--has_rpath] [--has_lv] [--checksec] [--dylibs] [--rpaths] [--rpaths_u] [--dylibs_paths] [--dylibs_paths_u]
|
||||
[--broken_relative_paths] [--dylibtree [cache_path,output_path,is_extracted]] [--dylib_id] [--reexport_paths] [--hijack_sec] [--dylib_hijacking [(optional) cache_path]] [--dylib_hijacking_a [cache_path]] [--prepare_dylib [(optional) target_dylib_name]] [--is_built_for_sim] [--get_dyld_env]
|
||||
[--compiled_with_dyld_env] [--has_interposing] [--interposing_symbols]
|
||||
usage: CrimsonUroboros [-h] -p PATH [--file_type] [--header_flags] [--endian]
|
||||
[--header] [--load_commands] [--has_cmd LC_MAIN]
|
||||
[--segments] [--has_segment __SEGMENT] [--sections]
|
||||
[--has_section __SEGMENT,__section] [--symbols]
|
||||
[--imports] [--exports] [--imported_symbols]
|
||||
[--chained_fixups] [--exports_trie] [--uuid] [--main]
|
||||
[--encryption_info [(optional) save_path.bytes]]
|
||||
[--strings_section] [--all_strings]
|
||||
[--save_strings all_strings.txt] [--info]
|
||||
[--dump_data [offset,size,output_path]]
|
||||
[--calc_offset vm_offset] [--constructors]
|
||||
[--verify_signature] [--cd_info] [--cd_requirements]
|
||||
[--entitlements [human|xml|var]]
|
||||
[--extract_cms cms_signature.der]
|
||||
[--extract_certificates certificate_name]
|
||||
[--remove_sig unsigned_binary]
|
||||
[--sign_binary [adhoc|identity]] [--cs_offset]
|
||||
[--cs_flags] [--has_pie] [--has_arc] [--is_stripped]
|
||||
[--has_canary] [--has_nx_stack] [--has_nx_heap]
|
||||
[--has_xn] [--is_notarized] [--is_encrypted]
|
||||
[--is_restricted] [--is_hr] [--is_as] [--is_fort]
|
||||
[--has_rpath] [--has_lv] [--checksec] [--dylibs]
|
||||
[--rpaths] [--rpaths_u] [--dylibs_paths]
|
||||
[--dylibs_paths_u] [--broken_relative_paths]
|
||||
[--dylibtree [cache_path,output_path,is_extracted]]
|
||||
[--dylib_id] [--reexport_paths] [--hijack_sec]
|
||||
[--dylib_hijacking [(optional) cache_path]]
|
||||
[--dylib_hijacking_a [cache_path]]
|
||||
[--prepare_dylib [(optional) target_dylib_name]]
|
||||
[--is_built_for_sim] [--get_dyld_env]
|
||||
[--compiled_with_dyld_env] [--has_interposing]
|
||||
[--interposing_symbols]
|
||||
[--dump_prelink_info [(optional) out_name]]
|
||||
[--dump_prelink_text [(optional) out_name]]
|
||||
[--dump_prelink_kext [kext_name]]
|
||||
[--kext_prelinkinfo [kext_name]]
|
||||
[--kmod_info kext_name] [--kext_entry kext_name]
|
||||
[--kext_exit kext_name] [--mig] [--has_suid]
|
||||
[--has_sgid] [--has_sticky] [--injectable_dyld]
|
||||
[--test_insert_dylib] [--test_prune_dyld]
|
||||
[--test_dyld_print_to_file]
|
||||
|
||||
Mach-O files parser for binary analysis
|
||||
|
||||
@@ -51,84 +87,188 @@ MACH-O ARGS:
|
||||
--endian Print binary endianess
|
||||
--header Print binary header
|
||||
--load_commands Print binary load commands names
|
||||
--has_cmd LC_MAIN Check of binary has given load command
|
||||
--segments Print binary segments in human-friendly form
|
||||
--has_segment __SEGMENT
|
||||
Check if binary has given '__SEGMENT'
|
||||
--sections Print binary sections in human-friendly form
|
||||
--has_section __SEGMENT,__section
|
||||
Check if binary has given '__SEGMENT,__section'
|
||||
--symbols Print all binary symbols
|
||||
--imported_symbols Print symbols imported from external libraries
|
||||
--imports Print imported symbols
|
||||
--exports Print exported symbols
|
||||
--imported_symbols Print symbols imported from external libraries with
|
||||
dylib names
|
||||
--chained_fixups Print Chained Fixups information
|
||||
--exports_trie Print Export Trie information
|
||||
--uuid Print UUID
|
||||
--main Print entry point and stack size
|
||||
--encryption_info [(optional) save_path.bytes]
|
||||
Print encryption info if any. Optionally specify an output path to dump the encrypted data (if cryptid=0, data will be in plain text)
|
||||
Print encryption info if any. Optionally specify an
|
||||
output path to dump the encrypted data (if cryptid=0,
|
||||
data will be in plain text)
|
||||
--strings_section Print strings from __cstring section
|
||||
--all_strings Print strings from all sections
|
||||
--save_strings all_strings.txt
|
||||
Parse all sections, detect strings, and save them to a file
|
||||
--info Print header, load commands, segments, sections, symbols, and strings
|
||||
Parse all sections, detect strings, and save them to a
|
||||
file
|
||||
--info Print header, load commands, segments, sections,
|
||||
symbols, and strings
|
||||
--dump_data [offset,size,output_path]
|
||||
Dump {size} bytes starting from {offset} to a given
|
||||
{filename} (e.g. '0x1234,0x1000,out.bin')
|
||||
--calc_offset vm_offset
|
||||
Calculate the real address (file on disk) of the given
|
||||
Virtual Memory {vm_offset} (e.g. 0xfffffe000748f580)
|
||||
--constructors Print binary constructors
|
||||
|
||||
CODE SIGNING ARGS:
|
||||
--verify_signature Code Signature verification (if the contents of the binary have been modified)
|
||||
--verify_signature Code Signature verification (if the contents of the
|
||||
binary have been modified)
|
||||
--cd_info Print Code Signature information
|
||||
--cd_requirements Print Code Signature Requirements
|
||||
--entitlements [human|xml|var]
|
||||
Print Entitlements in a human-readable, XML, or DER format (default: human)
|
||||
Print Entitlements in a human-readable, XML, or DER
|
||||
format (default: human)
|
||||
--extract_cms cms_signature.der
|
||||
Extract CMS Signature from the Code Signature and save it to a given file
|
||||
Extract CMS Signature from the Code Signature and save
|
||||
it to a given file
|
||||
--extract_certificates certificate_name
|
||||
Extract Certificates and save them to a given file. To each filename will be added an index at the end: _0 for signing, _1 for intermediate, and _2 for root CA certificate
|
||||
Extract Certificates and save them to a given file. To
|
||||
each filename will be added an index at the end: _0
|
||||
for signing, _1 for intermediate, and _2 for root CA
|
||||
certificate
|
||||
--remove_sig unsigned_binary
|
||||
Save the new file on a disk with removed signature
|
||||
--sign_binary [adhoc|identity]
|
||||
Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to get the identity (default: adhoc)
|
||||
Sign binary using specified identity - use : 'security
|
||||
find-identity -v -p codesigning' to get the identity
|
||||
(default: adhoc)
|
||||
--cs_offset Print Code Signature file offset
|
||||
--cs_flags Print Code Signature flags
|
||||
|
||||
CHECKSEC ARGS:
|
||||
--has_pie Check if Position-Independent Executable (PIE) is set
|
||||
--has_arc Check if Automatic Reference Counting (ARC) is in use (can be false positive)
|
||||
--has_arc Check if Automatic Reference Counting (ARC) is in use
|
||||
(can be false positive)
|
||||
--is_stripped Check if binary is stripped
|
||||
--has_canary Check if Stack Canary is in use (can be false positive)
|
||||
--has_canary Check if Stack Canary is in use (can be false
|
||||
positive)
|
||||
--has_nx_stack Check if stack is non-executable (NX stack)
|
||||
--has_nx_heap Check if heap is non-executable (NX heap)
|
||||
--has_xn Check if binary is protected by eXecute Never (XN) ARM protection
|
||||
--is_notarized Check if the application is notarized and can pass the Gatekeeper verification
|
||||
--is_encrypted Check if the application is encrypted (has LC_ENCRYPTION_INFO(_64) and cryptid set to 1)
|
||||
--is_restricted Check if binary has __RESTRICT segment or CS_RESTRICT flag set
|
||||
--has_xn Check if binary is protected by eXecute Never (XN) ARM
|
||||
protection
|
||||
--is_notarized Check if the application is notarized and can pass the
|
||||
Gatekeeper verification
|
||||
--is_encrypted Check if the application is encrypted (has
|
||||
LC_ENCRYPTION_INFO(_64) and cryptid set to 1)
|
||||
--is_restricted Check if binary has __RESTRICT segment or CS_RESTRICT
|
||||
flag set
|
||||
--is_hr Check if the Hardened Runtime is in use
|
||||
--is_as Check if the App Sandbox is in use
|
||||
--is_fort Check if the binary is fortified
|
||||
--has_rpath Check if the binary utilise any @rpath variables
|
||||
--has_lv Check if the binary has Library Validation (protection against Dylib Hijacking)
|
||||
--has_lv Check if the binary has Library Validation (protection
|
||||
against Dylib Hijacking)
|
||||
--checksec Run all checksec module options on the binary
|
||||
|
||||
DYLIBS ARGS:
|
||||
--dylibs Print shared libraries used by specified binary with compatibility and the current version (loading paths unresolved, like @rpath/example.dylib)
|
||||
--rpaths Print all paths (resolved) that @rpath can be resolved to
|
||||
--rpaths_u Print all paths (unresolved) that @rpath can be resolved to
|
||||
--dylibs_paths Print absolute dylib loading paths (resolved @rpath|@executable_path|@loader_path) in order they are searched for
|
||||
--dylibs Print shared libraries used by specified binary with
|
||||
compatibility and the current version (loading paths
|
||||
unresolved, like @rpath/example.dylib)
|
||||
--rpaths Print all paths (resolved) that @rpath can be resolved
|
||||
to
|
||||
--rpaths_u Print all paths (unresolved) that @rpath can be
|
||||
resolved to
|
||||
--dylibs_paths Print absolute dylib loading paths (resolved
|
||||
@rpath|@executable_path|@loader_path) in order they
|
||||
are searched for
|
||||
--dylibs_paths_u Print unresolved dylib loading paths.
|
||||
--broken_relative_paths
|
||||
Print 'broken' relative paths from the binary (cases where the dylib source is specified for an executable directory without @executable_path)
|
||||
Print 'broken' relative paths from the binary (cases
|
||||
where the dylib source is specified for an executable
|
||||
directory without @executable_path)
|
||||
--dylibtree [cache_path,output_path,is_extracted]
|
||||
Print the dynamic dependencies of a Mach-O binary recursively. You can specify the Dyld Shared Cache path in the first argument, the output directory as the 2nd argument, and if you have already extracted DSC in the 3rd argument (0 or 1). The output_path will be used as a base for
|
||||
dylibtree. For example, to not extract DSC, use: --dylibs ",,1", or to extract from default to default use just --dylibs or --dylibs ",,0" which will extract DSC to extracted_dyld_share_cache/ in the current directory
|
||||
Print the dynamic dependencies of a Mach-O binary
|
||||
recursively. You can specify the Dyld Shared Cache
|
||||
path in the first argument, the output directory as
|
||||
the 2nd argument, and if you have already extracted
|
||||
DSC in the 3rd argument (0 or 1). The output_path will
|
||||
be used as a base for dylibtree. For example, to not
|
||||
extract DSC, use: --dylibs ",,1", or to extract from
|
||||
default to default use just --dylibs or --dylibs ",,0"
|
||||
which will extract DSC to extracted_dyld_share_cache/
|
||||
in the current directory
|
||||
--dylib_id Print path from LC_ID_DYLIB
|
||||
--reexport_paths Print paths from LC_REEXPORT_DLIB
|
||||
--hijack_sec Check if binary is protected against Dylib Hijacking
|
||||
--dylib_hijacking [(optional) cache_path]
|
||||
Check for possible Direct and Indirect Dylib Hijacking loading paths. The output is printed to console and saved in JSON format to /tmp/dylib_hijacking_log.json(append mode). Optionally, specify the path to the Dyld Shared Cache
|
||||
Check for possible Direct and Indirect Dylib Hijacking
|
||||
loading paths. The output is printed to console and
|
||||
saved in JSON format to
|
||||
/tmp/dylib_hijacking_log.json(append mode).
|
||||
Optionally, specify the path to the Dyld Shared Cache
|
||||
--dylib_hijacking_a [cache_path]
|
||||
Like --dylib_hijacking, but shows only possible vectors (without protected binaries)
|
||||
Like --dylib_hijacking, but shows only possible
|
||||
vectors (without protected binaries)
|
||||
--prepare_dylib [(optional) target_dylib_name]
|
||||
Compile rogue dylib. Optionally, specify target_dylib_path, it will search for the imported symbols from it in the dylib specified in the --path argument and automatically add it to the source code of the rogue lib. Example: --path lib1.dylib --prepare_dylib /path/to/lib2.dylib
|
||||
Compile rogue dylib. Optionally, specify
|
||||
target_dylib_path, it will search for the imported
|
||||
symbols from it in the dylib specified in the --path
|
||||
argument and automatically add it to the source code
|
||||
of the rogue lib. Example: --path lib1.dylib
|
||||
--prepare_dylib /path/to/lib2.dylib
|
||||
|
||||
DYLD ARGS:
|
||||
--is_built_for_sim Check if binary is built for simulator platform.
|
||||
--get_dyld_env Extract Dyld environment variables from the loader binary.
|
||||
--get_dyld_env Extract Dyld environment variables from the loader
|
||||
binary.
|
||||
--compiled_with_dyld_env
|
||||
Check if binary was compiled with -dyld_env flag and print the environment variables and its values.
|
||||
Check if binary was compiled with -dyld_env flag and
|
||||
print the environment variables and its values.
|
||||
--has_interposing Check if binary has interposing sections.
|
||||
--interposing_symbols
|
||||
Print interposing symbols if any.
|
||||
|
||||
AMFI ARGS:
|
||||
--dump_prelink_info [(optional) out_name]
|
||||
Dump "__PRELINK_INFO,__info" to a given file (default:
|
||||
"PRELINK_info.txt")
|
||||
--dump_prelink_text [(optional) out_name]
|
||||
Dump "__PRELINK_TEXT,__text" to a given file (default:
|
||||
"PRELINK_text.txt")
|
||||
--dump_prelink_kext [kext_name]
|
||||
Dump prelinked KEXT {kext_name} from decompressed
|
||||
Kernel Cache PRELINK_TEXT segment to a file named:
|
||||
prelinked_{kext_name}.bin
|
||||
--kext_prelinkinfo [kext_name]
|
||||
Print _Prelink properties from PRELINK_INFO,__info for
|
||||
a give {kext_name}
|
||||
--kmod_info kext_name
|
||||
Parse kmod_info structure for the given {kext_name}
|
||||
from Kernel Cache
|
||||
--kext_entry kext_name
|
||||
Calculate the virtual memory address of the __start
|
||||
(entrpoint) for the given {kext_name} Kernel Extension
|
||||
--kext_exit kext_name
|
||||
Calculate the virtual memory address of the __stop
|
||||
(exitpoint) for the given {kext_name} Kernel Extension
|
||||
--mig Search for MIG subsystem and prints message handlers
|
||||
--has_suid Check if the file has SetUID bit set
|
||||
--has_sgid Check if the file has SetGID bit set
|
||||
--has_sticky Check if the file has sticky bit set
|
||||
--injectable_dyld Check if the binary is injectable using
|
||||
DYLD_INSERT_LIBRARIES
|
||||
--test_insert_dylib Check if it is possible to inject dylib using
|
||||
DYLD_INSERT_LIBRARIES (INVASIVE - the binary is
|
||||
executed)
|
||||
--test_prune_dyld Check if Dyld Environment Variables are cleared (using
|
||||
DYLD_PRINT_INITIALIZERS=1) (INVASIVE - the binary is
|
||||
executed)
|
||||
--test_dyld_print_to_file
|
||||
Check if YLD_PRINT_TO_FILE Dyld Environment Variables
|
||||
works (INVASIVE - the binary is executed)
|
||||
|
||||
```
|
||||
* Example:
|
||||
```bash
|
||||
@@ -268,6 +408,14 @@ Print the total Mach-O files analyzed and how many DYLIB-related LCs existed
|
||||
```console
|
||||
MachODylibLoadCommandsFinder 2>/dev/null
|
||||
```
|
||||
***
|
||||
### [check_amfi](VI.%20AMFI/python/check_amfi.py)
|
||||
Simple script for calculating `amfiFlags` (described [here](https://karol-mazurek.medium.com/dyld-do-you-like-death-vi-1013a69118ff) in `ProcessConfig — AMFI properties`)
|
||||
* Usage:
|
||||
```console
|
||||
python3 check_amfi.py 0x1df
|
||||
```
|
||||
|
||||
|
||||
## INSTALL
|
||||
```
|
||||
@@ -300,10 +448,11 @@ Each Snake class will be a child of the previous one and infinitely "eat itself"
|
||||
* Every method in the Snake class that use Entitlements should parse first XML > DER (currently, only XML parser exists)
|
||||
* After making a SuperBlob parser and CodeDirectory blob parser, modify hasHardenedRuntime to check Runtime flag by using bitmask, instead of string.
|
||||
* Build Dyld Shared Cache parser and extractor to make SnakeIV independant of dyld-shared-cache-extractor.
|
||||
* Make testing branch and implement tests, before pushing new updates.
|
||||
* Create `RottenApple.app` in another repository and use it for testing.
|
||||
* Add Dyld Closure chapter to Snake&Apple V - Dyld
|
||||
* Move `kext_prelinkinfo`, `dumpPrelink_info` and `dumpPrelink_text` to Snake & Apple chapter about Kernel Extensions when ready.
|
||||
* Add kernelcache parser.
|
||||
* Add `LC_FILESET_ENTRY` method to `dumpKernelExtension`.
|
||||
* Consider moving methods like `removeNullBytesAlignment`, `calcTwoComplement64` etc. to `Utils` class.
|
||||
* Consider moving methods like `removeNullBytesAlignment`, `calcTwoComplement64` etc. to `Utils` class.
|
||||
* Move `--mig` option to Snake & Apple chapter about Mach Kernel when ready.
|
||||
* Make Thread manager class and improve the Threading.thread with tracing methods and `kill()`.
|
||||
|
||||
10
VI. AMFI/custom/entitlements.plist
Normal file
10
VI. AMFI/custom/entitlements.plist
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
143
VI. AMFI/mac/AMFI_RE/GHIDRA_verify_code_directory.c
Normal file
143
VI. AMFI/mac/AMFI_RE/GHIDRA_verify_code_directory.c
Normal file
@@ -0,0 +1,143 @@
|
||||
|
||||
ulong _verify_code_directory
|
||||
(undefined8 param_1,undefined8 param_2,undefined8 param_3,undefined4 param_4,
|
||||
undefined4 param_5,undefined4 param_6,undefined4 param_7,undefined4 *param_8,
|
||||
undefined4 *param_9,undefined4 *param_10,undefined4 *param_11,undefined4 *param_12,
|
||||
undefined4 *param_13_00,undefined4 *param_13,undefined8 param_15_00,
|
||||
undefined8 *param_14,undefined8 *param_17,undefined4 *param_15,undefined8 *param_19)
|
||||
|
||||
{
|
||||
ulong uVar1;
|
||||
ulong uVar2;
|
||||
ulong uVar3;
|
||||
uint uVar4;
|
||||
undefined8 uVar5;
|
||||
undefined8 uVar6;
|
||||
undefined8 uVar7;
|
||||
undefined auVar8 [16];
|
||||
int local_1128 [2];
|
||||
long local_1120;
|
||||
undefined local_1118 [8];
|
||||
undefined local_1110 [8];
|
||||
int local_1108;
|
||||
undefined4 uStack_1104;
|
||||
uint local_1100;
|
||||
undefined4 uStack_10fc;
|
||||
undefined8 local_10f8;
|
||||
undefined4 auStack_10f0 [7];
|
||||
undefined4 local_10d4;
|
||||
uint local_10cc;
|
||||
undefined8 auStack_10c8 [2];
|
||||
int aiStack_10b8 [1044];
|
||||
long local_68;
|
||||
|
||||
auVar8 = (*DAT_fffffe0007e6bb38)();
|
||||
local_68 = *(long *)PTR_DAT_fffffe0007e6ba68;
|
||||
func_0xfffffe0008538b60(local_1128,0x10bc);
|
||||
local_1108 = (int)*(undefined8 *)PTR_DAT_fffffe0007e6b9d8;
|
||||
uStack_1104 = (undefined4)((ulong)*(undefined8 *)PTR_DAT_fffffe0007e6b9d8 >> 0x20);
|
||||
if (DAT_fffffe0007e6bb40 == 0) {
|
||||
uStack_10fc = func_0xfffffe0008599ccc(&local_10f8,auVar8._8_8_,0x400);
|
||||
}
|
||||
else {
|
||||
uStack_10fc = func_0xfffffe0008599d30(&local_10f8,auVar8._8_8_,0x400);
|
||||
}
|
||||
local_1100 = 0;
|
||||
uVar4 = uStack_10fc + 3U & 0xfffffffc;
|
||||
uVar2 = (ulong)uVar4;
|
||||
*(undefined8 *)((long)&local_10f8 + uVar2) = param_3;
|
||||
*(undefined4 *)((long)auStack_10f0 + uVar2) = param_4;
|
||||
*(undefined4 *)((long)auStack_10f0 + uVar2 + 4) = param_5;
|
||||
*(undefined4 *)((long)auStack_10f0 + uVar2 + 8) = param_6;
|
||||
*(undefined4 *)((long)auStack_10f0 + uVar2 + 0xc) = param_7;
|
||||
local_1118 = (undefined [8])func_0xfffffe0008599cb0();
|
||||
local_1128[0] = 0x1513;
|
||||
local_1110 = (undefined [8])0x3e800000000;
|
||||
local_1120 = auVar8._0_8_;
|
||||
uVar2 = func_0xfffffe0008599758(local_1128,uVar4 + 0x48,0x10bc);
|
||||
uVar4 = (int)uVar2 + 0xeffffffe;
|
||||
if ((uVar4 < 0xf) && ((1 << (ulong)(uVar4 & 0x1f) & 0x4003U) != 0)) {
|
||||
func_0xfffffe0008599cc4(local_1118);
|
||||
goto LAB_fffffe0009acbbc8;
|
||||
}
|
||||
if ((int)uVar2 != 0) {
|
||||
func_0xfffffe0008599cbc(local_1118);
|
||||
goto LAB_fffffe0009acbbc8;
|
||||
}
|
||||
if (local_1110._4_4_ == 0x47) {
|
||||
uVar2 = 0xfffffecc;
|
||||
}
|
||||
else if (local_1110._4_4_ == 0x44c) {
|
||||
if (local_1128[0] < 0) {
|
||||
uVar2 = 0xfffffed4;
|
||||
if ((((local_1108 == 1) && (0x77 < (uint)local_1128[1])) && ((uint)local_1128[1] < 0x1079)) &&
|
||||
(local_1120 == 0)) {
|
||||
if ((uStack_10fc._3_1_ == '\x01') && (local_10cc < 0x1001)) {
|
||||
uVar2 = 0xfffffed4;
|
||||
if ((local_1128[1] - 0x78U < local_10cc) ||
|
||||
(uVar4 = local_10cc + 3 & 0xfffffffc, local_1128[1] != uVar4 + 0x78))
|
||||
goto LAB_fffffe0009acbbc0;
|
||||
uVar1 = (ulong)uVar4;
|
||||
if ((int)local_10f8 == *(int *)((long)aiStack_10b8 + uVar1 + 4)) {
|
||||
uVar3 = (ulong)(uint)local_1128[1] + 3 & 0x1fffffffc;
|
||||
if ((*(int *)((long)local_1128 + uVar3) == 0) &&
|
||||
(0x1f < *(uint *)((long)local_1128 + uVar3 + 4))) {
|
||||
*param_8 = auStack_10f0[1];
|
||||
*param_9 = auStack_10f0[2];
|
||||
*param_10 = auStack_10f0[3];
|
||||
*param_11 = auStack_10f0[4];
|
||||
*param_12 = auStack_10f0[5];
|
||||
*param_13_00 = auStack_10f0[6];
|
||||
*param_13 = local_10d4;
|
||||
func_0xfffffe0008599ccc(param_15_00,auStack_10c8,0x1000);
|
||||
uVar2 = 0;
|
||||
uVar6 = *(undefined8 *)((long)auStack_10c8 + uVar1 + 8);
|
||||
uVar5 = *(undefined8 *)((long)auStack_10c8 + uVar1);
|
||||
*(undefined4 *)(param_14 + 2) = *(undefined4 *)((long)aiStack_10b8 + uVar1);
|
||||
param_14[1] = uVar6;
|
||||
*param_14 = uVar5;
|
||||
*param_17 = CONCAT44(local_1100,uStack_1104);
|
||||
*param_15 = *(undefined4 *)((long)aiStack_10b8 + uVar1 + 4);
|
||||
uVar6 = *(undefined8 *)((long)&uStack_10fc + uVar3);
|
||||
uVar5 = *(undefined8 *)((long)&uStack_1104 + uVar3);
|
||||
uVar7 = *(undefined8 *)(local_1118 + uVar3 + 4);
|
||||
param_19[1] = *(undefined8 *)(local_1110 + uVar3 + 4);
|
||||
*param_19 = uVar7;
|
||||
param_19[3] = uVar6;
|
||||
param_19[2] = uVar5;
|
||||
}
|
||||
else {
|
||||
uVar2 = 0xfffffecb;
|
||||
}
|
||||
goto LAB_fffffe0009acbbc8;
|
||||
}
|
||||
}
|
||||
LAB_fffffe0009acbbbc:
|
||||
uVar2 = 0xfffffed4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (local_1128[1] != 0x2c) goto LAB_fffffe0009acbbbc;
|
||||
uVar2 = 0xfffffed4;
|
||||
if (local_1100 != 0) {
|
||||
uVar4 = local_1100;
|
||||
if (local_1120 != 0) {
|
||||
uVar4 = 0xfffffed4;
|
||||
}
|
||||
uVar2 = (ulong)uVar4;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
uVar2 = 0xfffffed3;
|
||||
}
|
||||
LAB_fffffe0009acbbc0:
|
||||
func_0xfffffe0008599b4c(local_1128);
|
||||
LAB_fffffe0009acbbc8:
|
||||
if (*(long *)PTR_DAT_fffffe0007e6ba68 == local_68) {
|
||||
return uVar2;
|
||||
}
|
||||
uVar2 = func_0xfffffe000854c1ec();
|
||||
return uVar2;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import json
|
||||
import sys
|
||||
import treelib
|
||||
import ctypes
|
||||
import stat
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
|
||||
### --- I. MACH-O --- ###
|
||||
class MachOProcessor:
|
||||
@@ -152,6 +156,9 @@ class MachOProcessor:
|
||||
if args.calc_offset: # Calculate the real address of the Virtual Memory in the file.
|
||||
snake_instance.printCalcRealAddressFromVM(args.calc_offset)
|
||||
|
||||
if args.constructors: # Print constructors
|
||||
snake_instance.printConstructors()
|
||||
|
||||
class SnakeI:
|
||||
def __init__(self, binaries, file_path):
|
||||
'''
|
||||
@@ -160,8 +167,11 @@ class SnakeI:
|
||||
'''
|
||||
self.binary = self.parseFatBinary(binaries)
|
||||
self.file_path = file_path
|
||||
self.segments_count, self.file_start, self.file_size, self.file_end = self.getSegmentsInfo()
|
||||
self.load_commands = self.getLoadCommands()
|
||||
self.endianess = self.getEndianess()
|
||||
self.format_specifier = '<I' if self.getEndianess() == 'little' else '>I' # For struct.pack
|
||||
self.reversed_format_specifier = '>I' if self.getEndianess() == 'little' else '<I' # For CS blob which is in Big Endian.
|
||||
self.fat_offset = self.binary.fat_offset # For various calculations, if ARM64 Mach-O extracted from Universal Binary
|
||||
self.prot_map = {
|
||||
0: '---',
|
||||
@@ -194,6 +204,24 @@ class SnakeI:
|
||||
}
|
||||
}
|
||||
|
||||
def getSegmentsInfo(self):
|
||||
''' Helper function for gathering various initialization information about the binary if extracted from FAT. '''
|
||||
segments_count = 0
|
||||
|
||||
for s in self.binary.segments:
|
||||
segments_count+=1
|
||||
|
||||
for s in self.binary.segments:
|
||||
if s.index == 0:
|
||||
file_start = s.file_offset + self.binary.fat_offset
|
||||
|
||||
elif s.index == segments_count-1:
|
||||
file_end = s.file_offset + s.file_size + self.binary.fat_offset
|
||||
pass # self.binary.fat_offset
|
||||
|
||||
file_size = file_end - file_start
|
||||
return segments_count, file_start, file_size, file_end
|
||||
|
||||
def mapProtection(self, numeric_protection):
|
||||
'''Maps numeric protection to its string representation.'''
|
||||
return self.prot_map.get(numeric_protection, 'Unknown')
|
||||
@@ -371,7 +399,7 @@ class SnakeI:
|
||||
''' Printing only exported symbol names. '''
|
||||
for symbol in self.getExports():
|
||||
print(symbol.name)
|
||||
|
||||
|
||||
def getChainedFixups(self):
|
||||
'''Return Chained Fixups information: https://lief-project.github.io/doc/latest/api/python/macho.html#chained-binding-info'''
|
||||
return self.binary.dyld_chained_fixups
|
||||
@@ -598,7 +626,7 @@ class SnakeI:
|
||||
if self.hasSegment('__TEXT'):
|
||||
for segment in self.binary.segments:
|
||||
if segment.name == '__TEXT':
|
||||
vm_base = segment.virtual_address + self.fat_offset
|
||||
vm_base = segment.virtual_address
|
||||
return vm_base
|
||||
|
||||
def calcRealAddressFromVM(self, vm_offset):
|
||||
@@ -625,6 +653,11 @@ class SnakeI:
|
||||
real_offset_hex = hex(real_offset)
|
||||
print(f'{vm_offset} : {real_offset_hex}')
|
||||
|
||||
def printConstructors(self):
|
||||
''' Print all constructors functions from the binary. '''
|
||||
for ctor in self.binary.ctor_functions:
|
||||
print(ctor)
|
||||
|
||||
### --- II. CODE SIGNING --- ###
|
||||
class CodeSigningProcessor:
|
||||
def __init__(self):
|
||||
@@ -660,6 +693,12 @@ class CodeSigningProcessor:
|
||||
if args.sign_binary: # Sign the given binary using specified identity:
|
||||
snake_instance.signBinary(args.sign_binary)
|
||||
|
||||
if args.cs_offset: # Print Code Signature offset
|
||||
snake_instance.printCodeSignatureOffset()
|
||||
|
||||
if args.cs_flags: # Print Code Signature flags
|
||||
snake_instance.printCodeSignatureFlags()
|
||||
|
||||
class SnakeII(SnakeI):
|
||||
def __init__(self, binaries, file_path):
|
||||
super().__init__(binaries, file_path)
|
||||
@@ -724,6 +763,61 @@ class SnakeII(SnakeI):
|
||||
except Exception as e:
|
||||
print(f"An error occurred during Code Signing using {security_identity}\n {e}")
|
||||
|
||||
def getCodeSignatureOffset(self):
|
||||
''' Return the file offset of the Code Signature. Takes into account Fat binaries. '''
|
||||
return self.binary.code_signature.data_offset + self.fat_offset
|
||||
|
||||
def printCodeSignatureOffset(self):
|
||||
print(f'Code Signature offset: {hex(self.getCodeSignatureOffset())}')
|
||||
|
||||
def getCodeSignatureSize(self):
|
||||
''' Return Code Signature size. '''
|
||||
return self.binary.code_signature.data_size
|
||||
|
||||
def extractCodeSignatureBytes(self):
|
||||
''' Extract the content of the Code Signature as raw bytes. Takes into account Fat binaries. '''
|
||||
#The self.binary.code_signature.content.tobytes() takes into account Fat binaries, so no need to calculate the offset of valid signature manually.
|
||||
#cs_offset = self.getCodeSignatureOffset()
|
||||
#cs_size = self.getCodeSignatureSize()
|
||||
#cs_bytes = self.extractBytesAtOffset(cs_offset, cs_size)
|
||||
#self.saveBytesToFile(cs_bytes, 'test.bin')
|
||||
cs_bytes = self.binary.code_signature.content.tobytes()
|
||||
return cs_bytes
|
||||
|
||||
def findBytes(self, magic, bytes):
|
||||
''' Find [magic] bytes in a given [bytes]. '''
|
||||
offset = bytes.find(magic)
|
||||
return offset
|
||||
|
||||
def parseCodeDirectoryBlob(self):
|
||||
''' Parse Code Directory blob from Code Signature to extract its version and then use AppleStructuresManager to parse the whole structure according to its version. '''
|
||||
# Extracting version number
|
||||
CS_MAGIC_CODEDIRECTORY = 0xFADE0C02
|
||||
cs_magic_codedirectory_as_bytes = struct.pack(self.reversed_format_specifier, CS_MAGIC_CODEDIRECTORY)
|
||||
cs_blob = self.extractCodeSignatureBytes()
|
||||
cs_directory_offset = self.findBytes(cs_magic_codedirectory_as_bytes, cs_blob)
|
||||
version_offset = cs_directory_offset + 8
|
||||
version_bytes = cs_blob[version_offset:version_offset+4]
|
||||
version = struct.unpack(self.reversed_format_specifier, version_bytes)[0]
|
||||
|
||||
# Extracting size
|
||||
size_offset = version_offset - 4
|
||||
size_bytes = cs_blob[size_offset:size_offset+4]
|
||||
size = struct.unpack(self.reversed_format_specifier, size_bytes)[0]
|
||||
|
||||
# Parsing __CodeDirectory
|
||||
code_directory_struct_instance = AppleStructuresManager.CodeDirectory(version)
|
||||
code_directory_dict = code_directory_struct_instance.parse(cs_blob[cs_directory_offset:size])
|
||||
return code_directory_dict
|
||||
|
||||
def getCodeSignatureFlags(self):
|
||||
''' Extract CS flags: https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/osfmk/kern/cs_blobs.h#L35'''
|
||||
code_directory_dict = self.parseCodeDirectoryBlob()
|
||||
return code_directory_dict['flags']
|
||||
|
||||
def printCodeSignatureFlags(self):
|
||||
print(f'CS_FLAGS: {hex(self.getCodeSignatureFlags())}')
|
||||
|
||||
### --- III. CHECKSEC --- ###
|
||||
class ChecksecProcessor:
|
||||
def __init__(self):
|
||||
@@ -1826,8 +1920,29 @@ class AMFIProcessor:
|
||||
if args.kext_exit: # Print kext exitpoint
|
||||
snake_instance.printKextExitPoint(args.kext_exit)
|
||||
|
||||
if args.amfi:
|
||||
snake_instance.printExports()
|
||||
if args.mig: # Search for MIG subsystem and prints message handlers
|
||||
snake_instance.printMIG()
|
||||
|
||||
if args.has_suid: # Print file SUID status
|
||||
snake_instance.printHasSetUID()
|
||||
|
||||
if args.has_sgid: # Print file SGID status
|
||||
snake_instance.printHasSetGID()
|
||||
|
||||
if args.has_sticky: # Print file sticky bit status
|
||||
snake_instance.printStickyBit()
|
||||
|
||||
if args.injectable_dyld: # Static check for DYLD_INSERT_LIBRARIES
|
||||
snake_instance.printCheckDyldInsertLibraries()
|
||||
|
||||
if args.test_insert_dylib: # INVASIVE check for DYLD_INSERT_LIBRARIES
|
||||
snake_instance.printTestDyldInsertLibraries()
|
||||
|
||||
if args.test_prune_dyld: # INVASIVE check for DYLD_PRINT_INITIALIZERS (if DEV are cleared)
|
||||
snake_instance.printTestPruneDyldEnv()
|
||||
|
||||
if args.test_dyld_print_to_file: # INVASIVE check for DYLD_PRINT_TO_FILE
|
||||
snake_instance.printTestDyldPrintToFile()
|
||||
|
||||
class SnakeVI(SnakeV):
|
||||
def __init__(self, binaries, file_path):
|
||||
@@ -1978,7 +2093,7 @@ class SnakeVI(SnakeV):
|
||||
# debug +
|
||||
#Utils.printQuadWordsLittleEndian64(extracted_kmod_info_bytes)
|
||||
# debug -
|
||||
kmod_info_as_dict = AppleStructuresManager.parsekmod_info(extracted_kmod_info_bytes)
|
||||
kmod_info_as_dict = AppleStructuresManager.kmod_info.parse(extracted_kmod_info_bytes)
|
||||
return kmod_info_as_dict
|
||||
|
||||
def printParsedkmod_info(self, kext_name):
|
||||
@@ -2017,6 +2132,262 @@ class SnakeVI(SnakeV):
|
||||
kext_exitpoint = hex(self.calcKextEntryPoint(kext_name))
|
||||
print(f'{kext_name} exitpoint: {kext_exitpoint}')
|
||||
|
||||
def parseMIG(self):
|
||||
''' Search for MIG subsystem messages. I was using this Hopper script as an inspiration: https://github.com/knightsc/hopper/blob/master/scripts/MIG%20Detect.py
|
||||
|
||||
Returns a dictionary like: {'_MIG_subsystem_1000': {'_MIG_msg_1000': routine_for_msg}}
|
||||
'''
|
||||
va_start = self.getVirtualMemoryStartingAddress()
|
||||
mig_subsystem_size = ctypes.sizeof(AppleStructuresManager.mig_subsystem)
|
||||
routine_descriptor_size = ctypes.sizeof(AppleStructuresManager.routine_descriptor)
|
||||
mig_subsystems = {}
|
||||
|
||||
# The MIG should be in __DATA,__const | __DATA_CONST,__const | __CONST,__constdata, but it is not always the case.
|
||||
# Great example is decompressed kernelcache, there are no __const section. Conclusion, would be to iterate over each segment, but there is a problem with alignment.
|
||||
for section in self.binary.sections:
|
||||
if ('const' in section.name):# and 'DATA' in section.segment.name):
|
||||
section_bytes = section.content.tobytes()
|
||||
section_size = section.size
|
||||
alignment = pow(2,section.alignment)
|
||||
|
||||
# Loop through section bytes using alignment to speed up
|
||||
current_offset = 0
|
||||
while current_offset < section_size:
|
||||
chunk = section_bytes[current_offset:current_offset+mig_subsystem_size]
|
||||
mig_subsystem_dict = AppleStructuresManager.mig_subsystem.parse(chunk)
|
||||
number_of_msgs = mig_subsystem_dict['end'] - mig_subsystem_dict['start']
|
||||
|
||||
# Check for possible mig_subsystem structure:
|
||||
if (number_of_msgs > 0 and
|
||||
number_of_msgs < 1024 and
|
||||
mig_subsystem_dict['server'] != 0 and
|
||||
mig_subsystem_dict['start'] > 0 and
|
||||
mig_subsystem_dict['end'] > 0 and
|
||||
mig_subsystem_dict['reserved'] == 0 and
|
||||
mig_subsystem_dict['routine_0'] == 0):
|
||||
'''
|
||||
# print(f'{hex(mig_subsystem_dict["server"])} {hex(mig_subsystem_dict["start"])}')
|
||||
# At this stage I get 0x8028000000007e74 instead of 0x100007e74 and I do not know why. The same goes for every impl_routine later too...
|
||||
# I can manually repair it by: & 0xffff | __TEXT
|
||||
# It is temp fix, there must be a "proper way" - todo
|
||||
'''
|
||||
mig_subsystem_dict['server'] = mig_subsystem_dict['server'] & 0xffff | va_start # Fix according to the above comment
|
||||
mig_subsystem_number = mig_subsystem_dict['start']
|
||||
subsystem_name = "MIG_subsystem_{0}".format(mig_subsystem_number)
|
||||
mig_subsystems[subsystem_name] = {}
|
||||
current_offset += mig_subsystem_size
|
||||
|
||||
# If mig_subsystem structure was found, iterate over all routines
|
||||
msg = 0
|
||||
while msg < number_of_msgs:
|
||||
routine_name = "MIG_msg_{0}".format(mig_subsystem_number+msg)
|
||||
chunk = section_bytes[current_offset:current_offset+routine_descriptor_size]
|
||||
routine_descriptor_dict = AppleStructuresManager.routine_descriptor.parse(chunk)
|
||||
if routine_descriptor_dict['impl_routine'] != 0:
|
||||
routine_descriptor_dict['impl_routine'] = routine_descriptor_dict['impl_routine'] & 0xffff | va_start # Fix like subsystem
|
||||
mig_subsystems[subsystem_name].update({routine_name: routine_descriptor_dict})
|
||||
current_offset += routine_descriptor_size
|
||||
msg += 1
|
||||
|
||||
continue # To find more subsystems we continue the parent while without adding below alignment, because we added routine_descriptor_size
|
||||
|
||||
current_offset += alignment
|
||||
|
||||
return(mig_subsystems)
|
||||
|
||||
def printMIG(self):
|
||||
''' Iterates over each subsystem and its associated messages, printing them in the nice format. '''
|
||||
mig_subsystems = self.parseMIG()
|
||||
|
||||
for subsystem, messages in mig_subsystems.items():
|
||||
print(subsystem + ":")
|
||||
|
||||
for message, details in messages.items():
|
||||
print(f"- {message}: {hex(details['impl_routine'])}")
|
||||
|
||||
def hasSetUID(self):
|
||||
"""
|
||||
Check if a file has the SUID (Set User ID) bit set.
|
||||
|
||||
Args:
|
||||
filename (str): Path to the file to be checked.
|
||||
|
||||
Returns:
|
||||
bool: True if SUID bit is set, False otherwise.
|
||||
"""
|
||||
st_mode = os.stat(self.file_path).st_mode
|
||||
return bool(st_mode & stat.S_ISUID)
|
||||
|
||||
def hasSetGID(self):
|
||||
"""
|
||||
Check if a file has the setgid (Set Group ID) bit set.
|
||||
|
||||
Args:
|
||||
filename (str): Path to the file to be checked.
|
||||
|
||||
Returns:
|
||||
bool: True if setgid bit is set, False otherwise.
|
||||
"""
|
||||
st_mode = os.stat(self.file_path).st_mode
|
||||
return bool(st_mode & stat.S_ISGID)
|
||||
|
||||
def hasStickyBit(self):
|
||||
"""
|
||||
Check if a file has the sticky bit set.
|
||||
|
||||
Args:
|
||||
filename (str): Path to the file to be checked.
|
||||
|
||||
Returns:
|
||||
bool: True if sticky bit is set, False otherwise.
|
||||
"""
|
||||
st_mode = os.stat(self.file_path).st_mode
|
||||
return bool(st_mode & stat.S_ISVTX)
|
||||
|
||||
def printHasSetUID(self):
|
||||
print(f'SUID: {self.hasSetUID()}')
|
||||
|
||||
def printHasSetGID(self):
|
||||
print(f'SGID: {self.hasSetGID()}')
|
||||
|
||||
def printStickyBit(self):
|
||||
print(f'STICKY: {self.hasStickyBit()}')
|
||||
|
||||
def checkDyldInsertLibraries(self):
|
||||
''' Check if binary is vulnerable to code injection using DYLD_INSERT_LIBRARIES. '''
|
||||
cs_flags = self.getCodeSignatureFlags()
|
||||
if cs_flags & 0x12800:
|
||||
return False
|
||||
|
||||
if self.hasSetUID() or self.hasSetGID() or self.hasRestrictSegment():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def printCheckDyldInsertLibraries(self):
|
||||
#print(f'{self.file_path} injectable DYLD_INSERT_LIBRARIES: {self.checkDyldInsertLibraries()}')
|
||||
print(f'Injectable DYLD_INSERT_LIBRARIES: {self.checkDyldInsertLibraries()}')
|
||||
|
||||
def listenSyslog(self, test_string, test_string_found, stop_event, timeout=2):
|
||||
''' Function to listen (for 2 seconds by default) to macOS system logs for a specific string. '''
|
||||
# Run the log command to retrieve system log messages
|
||||
process = subprocess.Popen(['log', 'stream', '--timeout', str(timeout)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
for line in process.stdout:
|
||||
if test_string in line:
|
||||
test_string_found.set()
|
||||
return
|
||||
|
||||
if stop_event.is_set():
|
||||
return
|
||||
|
||||
def testDyldInsertLibraries(self):
|
||||
''' Checking if DYLD_INSERT_LIBRARIES is allowed.
|
||||
INVASIVE:
|
||||
0. Check if /tmp/crimson_stalker.dylib exists.
|
||||
1. If not - the library /tmp/crimson_stalker.dylib is compiled.
|
||||
2. Binary is executed with DYLD_INSERT_LIBRARIES=/tmp/crimson_stalker.dylib
|
||||
3. Library is NOT REMOVED it stays in /tmp/ in case of massive checks with loops.
|
||||
'''
|
||||
stalker_path = '/tmp/crimson_stalker.dylib'
|
||||
env_variable = f'DYLD_INSERT_LIBRARIES={stalker_path}'
|
||||
test_string = 'crimson_stalker library injected into '
|
||||
|
||||
# Compile dylib if not exist:
|
||||
if not os.path.exists(stalker_path):
|
||||
file_name_c = '/tmp/crimson_stalker.c'
|
||||
source_code = SourceCodeManager.crimson_stalker
|
||||
output_filename = stalker_path
|
||||
flag_list = ['-dynamiclib']
|
||||
SourceCodeManager.clangCompilerWrapper(file_name_c, source_code, output_filename, flag_list)
|
||||
|
||||
# Create a threading event to signal when the test string is found in syslog or to stop listenSyslog thread (using Event as a flag and .set() as a switch)
|
||||
test_string_found = threading.Event() # Used in listenSyslog -> when test_string_found.set() is called, it is final check if test_string was found in syslogs.
|
||||
stop_event = threading.Event() # Used in this function -> when stop_event.set() is called below, it inform listenSyslog to stop.s
|
||||
|
||||
# Start listening for syslog messages in a separate thread
|
||||
syslog_listener_thread = threading.Thread(target=self.listenSyslog, args=(test_string, test_string_found, stop_event))
|
||||
syslog_listener_thread.start()
|
||||
# To avoid Race Codition false positives because syslog_listener_thread just started
|
||||
# We must wait for at least 0.1 for listenSyslog to start reading logs
|
||||
# Then we can execute the command below without a fear it will be omited in by the syslog_listener_thread.
|
||||
time.sleep(0.2) # 0.2 here and 2 for the timeout in listenSyslog is enough
|
||||
|
||||
# Execute the command and capture stdout and stderr
|
||||
command = f'{env_variable} {self.file_path}'
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
# Wait for the subprocess to finish
|
||||
process.wait()
|
||||
stdout = stdout.decode('utf-8')
|
||||
stderr = stderr.decode('utf-8')
|
||||
|
||||
# Check if the test string was found in stdout (it should not appear in stderr, but I check for that, you never know :D)
|
||||
if (test_string in stdout) or (test_string in stderr):
|
||||
stop_event.set()
|
||||
return True
|
||||
|
||||
# Wait for the thread to finish
|
||||
syslog_listener_thread.join()
|
||||
|
||||
# Check if the test string was found in syslog
|
||||
if test_string_found.is_set(): #
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def printTestDyldInsertLibraries(self):
|
||||
print(f'DYLD_INSERT_LIBRARIES is allowed: {self.testDyldInsertLibraries()}')
|
||||
|
||||
def testPruneDyldEnv(self):
|
||||
''' Checking if Dyld Environment Variables are cleared (INVASIVE - the binary is executed) '''
|
||||
env_variable = 'DYLD_PRINT_INITIALIZERS=1'
|
||||
test_string = 'running initializer '
|
||||
|
||||
# Execute the command and capture stdout and stderr
|
||||
command = f'{env_variable} {self.file_path}'
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
stdout, stderr = process.communicate()
|
||||
|
||||
stdout = stdout.decode('utf-8')
|
||||
stderr = stderr.decode('utf-8')
|
||||
if test_string in stderr:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def printTestPruneDyldEnv(self):
|
||||
#print(f'{self.file_path} DEV Pruned: {self.testPruneDyldEnv()}')
|
||||
print(f'DEV Pruned: {self.testPruneDyldEnv()}')
|
||||
|
||||
def testDyldPrintToFile(self):
|
||||
''' Checking if DYLD_PRINT_TO_FILE Dyld Environment Variables works.
|
||||
INVASIVE:
|
||||
1. The binary is executed.
|
||||
2. The file /tmp/crimson_1029384756_testDyldPrintToFile.txt is created if env works.
|
||||
3. The file is then removed
|
||||
'''
|
||||
test_file_path = '/tmp/crimson_1029384756_testDyldPrintToFile.txt'
|
||||
env_variable = f'DYLD_PRINT_TO_FILE={test_file_path}'
|
||||
|
||||
# Execute the command and capture stdout and stderr
|
||||
command = f'{env_variable} {self.file_path}'
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
stdout, stderr = process.communicate()
|
||||
#process.wait()
|
||||
|
||||
stdout = stdout.decode('utf-8')
|
||||
stderr = stderr.decode('utf-8')
|
||||
|
||||
if os.path.exists(test_file_path):
|
||||
os.remove(test_file_path)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def printTestDyldPrintToFile(self):
|
||||
print(f'DYLD_PRINT_TO_FILE allowed: {self.testDyldPrintToFile()}')
|
||||
|
||||
### --- ARGUMENT PARSER --- ###
|
||||
class ArgumentParser:
|
||||
def __init__(self):
|
||||
@@ -2060,6 +2431,7 @@ class ArgumentParser:
|
||||
macho_group.add_argument('--info', action='store_true', default=False, help="Print header, load commands, segments, sections, symbols, and strings")
|
||||
macho_group.add_argument('--dump_data', help="Dump {size} bytes starting from {offset} to a given {filename} (e.g. '0x1234,0x1000,out.bin')", metavar=('offset,size,output_path'), nargs="?")
|
||||
macho_group.add_argument('--calc_offset', help="Calculate the real address (file on disk) of the given Virtual Memory {vm_offset} (e.g. 0xfffffe000748f580)", metavar='vm_offset')
|
||||
macho_group.add_argument('--constructors', action='store_true', help="Print binary constructors")
|
||||
|
||||
def addCodeSignArgs(self):
|
||||
codesign_group = self.parser.add_argument_group('CODE SIGNING ARGS')
|
||||
@@ -2071,6 +2443,8 @@ class ArgumentParser:
|
||||
codesign_group.add_argument('--extract_certificates', help="Extract Certificates and save them to a given file. To each filename will be added an index at the end: _0 for signing, _1 for intermediate, and _2 for root CA certificate", metavar='certificate_name')
|
||||
codesign_group.add_argument('--remove_sig', help="Save the new file on a disk with removed signature", metavar='unsigned_binary')
|
||||
codesign_group.add_argument('--sign_binary', help="Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to get the identity (default: adhoc)", nargs='?', const='adhoc', metavar='adhoc|identity')
|
||||
codesign_group.add_argument('--cs_offset', action='store_true', help="Print Code Signature file offset")
|
||||
codesign_group.add_argument('--cs_flags', action='store_true', help="Print Code Signature flags")
|
||||
|
||||
def addChecksecArgs(self):
|
||||
checksec_group = self.parser.add_argument_group('CHECKSEC ARGS')
|
||||
@@ -2122,11 +2496,16 @@ class ArgumentParser:
|
||||
dyld_group.add_argument('--dump_prelink_kext', metavar='kext_name', nargs="?", help='Dump prelinked KEXT {kext_name} from decompressed Kernel Cache PRELINK_TEXT segment to a file named: prelinked_{kext_name}.bin')
|
||||
dyld_group.add_argument('--kext_prelinkinfo', metavar='kext_name', nargs="?", help='Print _Prelink properties from PRELINK_INFO,__info for a give {kext_name}')
|
||||
dyld_group.add_argument('--kmod_info', metavar='kext_name', help="Parse kmod_info structure for the given {kext_name} from Kernel Cache")
|
||||
dyld_group.add_argument('--kext_entry', metavar='kext_name', help="Calculate the virtual memory address of the __start (entrpoint) for the given {kext_name} Kernel Extension")
|
||||
dyld_group.add_argument('--kext_entry', metavar='kext_name', help="Calculate the virtual memory address of the __start (entrypoint) for the given {kext_name} Kernel Extension")
|
||||
dyld_group.add_argument('--kext_exit', metavar='kext_name', help="Calculate the virtual memory address of the __stop (exitpoint) for the given {kext_name} Kernel Extension")
|
||||
dyld_group.add_argument('--amfi', help="a")
|
||||
|
||||
|
||||
dyld_group.add_argument('--mig', action='store_true', help="Search for MIG subsystem and prints message handlers")
|
||||
dyld_group.add_argument('--has_suid', action='store_true', help="Check if the file has SetUID bit set")
|
||||
dyld_group.add_argument('--has_sgid', action='store_true', help="Check if the file has SetGID bit set")
|
||||
dyld_group.add_argument('--has_sticky', action='store_true', help="Check if the file has sticky bit set")
|
||||
dyld_group.add_argument('--injectable_dyld', action='store_true', help="Check if the binary is injectable using DYLD_INSERT_LIBRARIES")
|
||||
dyld_group.add_argument('--test_insert_dylib', action='store_true', help="Check if it is possible to inject dylib using DYLD_INSERT_LIBRARIES (INVASIVE - the binary is executed)")
|
||||
dyld_group.add_argument('--test_prune_dyld', action='store_true', help="Check if Dyld Environment Variables are cleared (using DYLD_PRINT_INITIALIZERS=1) (INVASIVE - the binary is executed)")
|
||||
dyld_group.add_argument('--test_dyld_print_to_file', action='store_true', help="Check if YLD_PRINT_TO_FILE Dyld Environment Variables works (INVASIVE - the binary is executed)")
|
||||
|
||||
def parseArgs(self):
|
||||
return self.parser.parse_args()
|
||||
@@ -2138,6 +2517,18 @@ class ArgumentParser:
|
||||
|
||||
### --- SOURCE CODE --- ###
|
||||
class SourceCodeManager:
|
||||
crimson_stalker = r'''
|
||||
// clang -dynamiclib /tmp/crimson_stalker.c -o /tmp/crimson_stalker.dylib
|
||||
#include <syslog.h>
|
||||
#include <stdio.h>
|
||||
|
||||
__attribute__((constructor))
|
||||
void myconstructor(int argc, const char **argv) {
|
||||
syslog(LOG_ERR, "crimson_stalker library injected into %s\n", argv[0]);
|
||||
printf("crimson_stalker library injected into %s\n", argv[0]);
|
||||
}
|
||||
'''
|
||||
|
||||
dylib_hijacking = r'''
|
||||
// clang -dynamiclib m.c -o m.dylib //-o $PWD/TARGET_DYLIB
|
||||
#include <syslog.h>
|
||||
@@ -2155,6 +2546,7 @@ void myconstructor(int argc, const char **argv)
|
||||
//system("/bin/sh");
|
||||
}
|
||||
'''
|
||||
|
||||
@staticmethod
|
||||
def clangCompilerWrapper(file_name_c, source_code, output_filename, flag_list=None):
|
||||
# Save the source code to a file
|
||||
@@ -2185,32 +2577,197 @@ class AppleStructuresManager:
|
||||
("start", ctypes.c_uint64),
|
||||
("stop", ctypes.c_uint64)
|
||||
]
|
||||
def parse(data):
|
||||
# Create an instance of the kmod_info structure
|
||||
info = AppleStructuresManager.kmod_info()
|
||||
# Cast the binary data to the structure
|
||||
ctypes.memmove(ctypes.byref(info), data, ctypes.sizeof(info))
|
||||
|
||||
def parsekmod_info(data):
|
||||
# Create an instance of the kmod_info structure
|
||||
info = AppleStructuresManager.kmod_info()
|
||||
# Cast the binary data to the structure
|
||||
ctypes.memmove(ctypes.byref(info), data, ctypes.sizeof(info))
|
||||
# Convert name and version to strings
|
||||
name = info.name.decode('utf-8').rstrip('\x00')
|
||||
version = info.version.decode('utf-8').rstrip('\x00')
|
||||
|
||||
# Convert name and version to strings
|
||||
name = info.name.decode('utf-8').rstrip('\x00')
|
||||
version = info.version.decode('utf-8').rstrip('\x00')
|
||||
# Return parsed data as a dictionary
|
||||
return {
|
||||
"next": info.next,
|
||||
"info_version": info.info_version,
|
||||
"id": hex(info.id),
|
||||
"name": name,
|
||||
"version": version,
|
||||
"reference_count": info.reference_count,
|
||||
"reference_list": hex(info.reference_list),
|
||||
"address": hex(info.address),
|
||||
"size": hex(info.size),
|
||||
"hdr_size": hex(info.hdr_size),
|
||||
"start": hex(info.start),
|
||||
"stop": hex(info.stop)
|
||||
}
|
||||
|
||||
# Return parsed data as a dictionary
|
||||
return {
|
||||
"next": info.next,
|
||||
"info_version": info.info_version,
|
||||
"id": hex(info.id),
|
||||
"name": name,
|
||||
"version": version,
|
||||
"reference_count": info.reference_count,
|
||||
"reference_list": hex(info.reference_list),
|
||||
"address": hex(info.address),
|
||||
"size": hex(info.size),
|
||||
"hdr_size": hex(info.hdr_size),
|
||||
"start": hex(info.start),
|
||||
"stop": hex(info.stop)
|
||||
}
|
||||
class mig_subsystem(ctypes.Structure):
|
||||
''' REF: https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/osfmk/mach/mig.h#L121C16-L121C29 '''
|
||||
_pack_ = 1 # Specify the byte order (little-endian)
|
||||
_fields_ = [
|
||||
("server", ctypes.c_uint64), # Pointer to demux routine
|
||||
("start", ctypes.c_uint32), # Min routine number
|
||||
("end", ctypes.c_uint32), # Max routine number + 1
|
||||
("maxsize", ctypes.c_uint64), # Max reply message size
|
||||
("reserved", ctypes.c_uint64), # Reserved for MIG use
|
||||
("routine_0", ctypes.c_uint64) # Routine descriptor array
|
||||
]
|
||||
|
||||
def parse(data):
|
||||
# Create an instance of the structure
|
||||
info = AppleStructuresManager.mig_subsystem()
|
||||
# Cast the binary data to the structure
|
||||
ctypes.memmove(ctypes.byref(info), data, ctypes.sizeof(info))
|
||||
|
||||
# Return parsed data as a dictionary
|
||||
return {
|
||||
"server": info.server,
|
||||
"start": info.start,
|
||||
"end": info.end,
|
||||
"maxsize": info.maxsize,
|
||||
"reserved": info.reserved,
|
||||
"routine_0": info.routine_0,
|
||||
}
|
||||
|
||||
class routine_descriptor(ctypes.Structure):
|
||||
''' REF: https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/osfmk/mach/mig.h#L105C8-L105C26 '''
|
||||
_pack_ = 1 # Specify the byte order (little-endian)
|
||||
_fields_ = [
|
||||
("impl_routine", ctypes.c_uint64), # Server work func pointer
|
||||
("stub_routine", ctypes.c_uint64), # Unmarshalling func pointer
|
||||
("argc", ctypes.c_uint32), # Number of argument words
|
||||
("descr_count", ctypes.c_uint32), # Number complex descriptors
|
||||
("arg_descr", ctypes.c_uint64), # Pointer to descriptor array
|
||||
("max_reply_msg", ctypes.c_uint64) # Max size for reply msg
|
||||
]
|
||||
|
||||
def parse(data):
|
||||
# Create an instance of the structure
|
||||
info = AppleStructuresManager.routine_descriptor()
|
||||
# Cast the binary data to the structure
|
||||
ctypes.memmove(ctypes.byref(info), data, ctypes.sizeof(info))
|
||||
|
||||
# Return parsed data as a dictionary
|
||||
return {
|
||||
"impl_routine": info.impl_routine,
|
||||
"stub_routine": info.stub_routine,
|
||||
"argc": info.argc,
|
||||
"descr_count": info.descr_count,
|
||||
"arg_descr": info.arg_descr,
|
||||
"max_reply_msg": info.max_reply_msg,
|
||||
}
|
||||
|
||||
class CodeDirectory(ctypes.BigEndianStructure):
|
||||
''' REF: https://github.com/Karmaz95/Snake_Apple/blob/0b5b02fdb954ca5f63eb240092cf98a68fa4e19f/II.%20Code%20Signing/mac/cs_blobs.h#L212C16-L212C31'''
|
||||
class v0(ctypes.BigEndianStructure):
|
||||
_fields_ = [
|
||||
("magic", ctypes.c_uint32),
|
||||
("length", ctypes.c_uint32),
|
||||
("version", ctypes.c_uint32),
|
||||
("flags", ctypes.c_uint32),
|
||||
("hashOffset", ctypes.c_uint32),
|
||||
("identOffset", ctypes.c_uint32),
|
||||
("nSpecialSlots", ctypes.c_uint32),
|
||||
("nCodeSlots", ctypes.c_uint32),
|
||||
("codeLimit", ctypes.c_uint32),
|
||||
("hashSize", ctypes.c_uint8),
|
||||
("hashType", ctypes.c_uint8),
|
||||
("platform", ctypes.c_uint8),
|
||||
("pageSize", ctypes.c_uint8),
|
||||
("spare2", ctypes.c_uint32),
|
||||
]
|
||||
|
||||
class v20100(v0):
|
||||
_fields_ = [
|
||||
("scatterOffset", ctypes.c_uint32),
|
||||
]
|
||||
|
||||
class v20200(v20100):
|
||||
_fields_ = [
|
||||
("teamOffset", ctypes.c_uint32),
|
||||
]
|
||||
|
||||
class v20300(v20200):
|
||||
_fields_ = [
|
||||
("spare3", ctypes.c_uint32),
|
||||
("codeLimit64", ctypes.c_uint64),
|
||||
]
|
||||
|
||||
class v20400(v20300):
|
||||
_fields_ = [
|
||||
("execSegBase", ctypes.c_uint64),
|
||||
("execSegLimit", ctypes.c_uint64),
|
||||
("execSegFlags", ctypes.c_uint64),
|
||||
]
|
||||
|
||||
class v20500(v20400):
|
||||
_fields_ = [
|
||||
("runtime", ctypes.c_uint32),
|
||||
("preEncryptOffset", ctypes.c_uint32),
|
||||
]
|
||||
|
||||
class v20600(v20500):
|
||||
_fields_ = [
|
||||
("linkageHashType", ctypes.c_uint8),
|
||||
("linkageApplicationType", ctypes.c_uint8),
|
||||
("linkageApplicationSubType", ctypes.c_uint16),
|
||||
("linkageOffset", ctypes.c_uint32),
|
||||
("linkageSize", ctypes.c_uint32),
|
||||
]
|
||||
|
||||
def __init__(self, version):
|
||||
self.version = version
|
||||
if version == 0x20100:
|
||||
self.info = self.v20100()
|
||||
elif version == 0x20200:
|
||||
self.info = self.v20200()
|
||||
elif version == 0x20300:
|
||||
self.info = self.v20300()
|
||||
elif version == 0x20400:
|
||||
self.info = self.v20400()
|
||||
elif version == 0x20500:
|
||||
self.info = self.v20500()
|
||||
elif version == 0x20600:
|
||||
self.info = self.v20600()
|
||||
else:
|
||||
self.info = self.v0()
|
||||
|
||||
def parse(self, data):
|
||||
ctypes.memmove(ctypes.byref(self.info), data, min(ctypes.sizeof(self.info), len(data)))
|
||||
|
||||
# Return parsed data as a dictionary
|
||||
return {
|
||||
"magic": getattr(self.info, "magic", None),
|
||||
"length": getattr(self.info, "length", None),
|
||||
"version": getattr(self.info, "version", None),
|
||||
"flags": getattr(self.info, "flags", None),
|
||||
"hashOffset": getattr(self.info, "hashOffset", None),
|
||||
"identOffset": getattr(self.info, "identOffset", None),
|
||||
"nSpecialSlots": getattr(self.info, "nSpecialSlots", None),
|
||||
"nCodeSlots": getattr(self.info, "nCodeSlots", None),
|
||||
"codeLimit": getattr(self.info, "codeLimit", None),
|
||||
"hashSize": getattr(self.info, "hashSize", None),
|
||||
"hashType": getattr(self.info, "hashType", None),
|
||||
"platform": getattr(self.info, "platform", None),
|
||||
"pageSize": getattr(self.info, "pageSize", None),
|
||||
"spare2": getattr(self.info, "spare2", None),
|
||||
"scatterOffset": getattr(self.info, "scatterOffset", None),
|
||||
"teamOffset": getattr(self.info, "teamOffset", None),
|
||||
"spare3": getattr(self.info, "spare3", None),
|
||||
"codeLimit64": getattr(self.info, "codeLimit64", None),
|
||||
"execSegBase": getattr(self.info, "execSegBase", None),
|
||||
"execSegLimit": getattr(self.info, "execSegLimit", None),
|
||||
"execSegFlags": getattr(self.info, "execSegFlags", None),
|
||||
"runtime": getattr(self.info, "runtime", None),
|
||||
"preEncryptOffset": getattr(self.info, "preEncryptOffset", None),
|
||||
"linkageHashType": getattr(self.info, "linkageHashType", None),
|
||||
"linkageApplicationType": getattr(self.info, "linkageApplicationType", None),
|
||||
"linkageApplicationSubType": getattr(self.info, "linkageApplicationSubType", None),
|
||||
"linkageOffset": getattr(self.info, "linkageOffset", None),
|
||||
"linkageSize": getattr(self.info, "linkageSize", None),
|
||||
}
|
||||
|
||||
### --- UTILS / DEBUG --- ###
|
||||
class Utils:
|
||||
@@ -2242,6 +2799,34 @@ class Utils:
|
||||
i+=1
|
||||
print()
|
||||
|
||||
def printQuadWordsBigEndian64(byte_string, columns=2):
|
||||
''' Print Q values from given {byte_string} in {columns} columns (default 2)
|
||||
0000000000000000 FFFFFFFF00000001
|
||||
6C7070612E6D6F63 7265766972642E65
|
||||
'''
|
||||
# Ensure the byte string length is a multiple of 8
|
||||
while len(byte_string) % 8 != 0:
|
||||
byte_string += b'\x00' # Add padding to make it divisible by 8
|
||||
|
||||
# Convert the byte string to a list of integers
|
||||
byte_list = list(byte_string)
|
||||
|
||||
# Group the bytes into 8-byte chunks
|
||||
chunks = [byte_list[i:i+8] for i in range(0, len(byte_list), 8)]
|
||||
|
||||
# Print the raw bytes in 64-bit big-endian order
|
||||
print("Raw bytes (64-bit big-endian):")
|
||||
i = 1
|
||||
for chunk in chunks:
|
||||
chunk_value = int.from_bytes(chunk, byteorder='big') # Changed to 'big'
|
||||
if i < columns:
|
||||
print(f"{chunk_value:016X}", end=" ")
|
||||
else:
|
||||
print(f"{chunk_value:016X}", end="\n")
|
||||
i = 0
|
||||
i += 1
|
||||
print()
|
||||
|
||||
def printRawHex(byte_string):
|
||||
'''
|
||||
Print bytes as raw hexes (without endianess).
|
||||
@@ -2250,7 +2835,6 @@ class Utils:
|
||||
hex_string = ' '.join(f'{byte:02x}' for byte in byte_string)
|
||||
print(hex_string)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
arg_parser = ArgumentParser()
|
||||
args = arg_parser.parseArgs()
|
||||
|
||||
81
VI. AMFI/python/MIG_detect.py
Normal file
81
VI. AMFI/python/MIG_detect.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# The script is not mine. Here is the source: https://github.com/knightsc/hopper/blob/master/scripts/MIG%20Detect.py
|
||||
|
||||
# This script attempts to identify mach_port_subsystem structures in the
|
||||
# __DATA section of executables or kernels
|
||||
#
|
||||
# const struct mach_port_subsystem {
|
||||
# mig_server_routine_t server; /* Server routine */
|
||||
# mach_msg_id_t start; /* Min routine number */
|
||||
# mach_msg_id_t end; /* Max routine number + 1 */
|
||||
# unsigned int maxsize; /* Max msg size */
|
||||
# vm_address_t reserved; /* Reserved */
|
||||
# struct routine_descriptor routine[X]; /* Array of routine descriptors */
|
||||
# }
|
||||
#
|
||||
# struct routine_descriptor {
|
||||
# mig_impl_routine_t impl_routine; /* Server work func pointer */
|
||||
# mig_stub_routine_t stub_routine; /* Unmarshalling func pointer */
|
||||
# unsigned int argc; /* Number of argument words */
|
||||
# unsigned int descr_count; /* Number complex descriptors */
|
||||
# routine_arg_descriptor_t arg_descr; /* pointer to descriptor array*/
|
||||
# unsigned int max_reply_msg; /* Max size for reply msg */
|
||||
# };
|
||||
#
|
||||
# If it finds the mach_port_subsystem structure then it will label the structure as
|
||||
# well as labelling each MIG msg stub function.
|
||||
|
||||
sections = [
|
||||
('__DATA', '__const'),
|
||||
('__CONST', '__constdata'),
|
||||
('__DATA_CONST', '__const'),
|
||||
]
|
||||
|
||||
doc = Document.getCurrentDocument()
|
||||
|
||||
for (segname, secname) in sections:
|
||||
seg = doc.getSegmentByName(segname)
|
||||
|
||||
if seg is None:
|
||||
continue
|
||||
|
||||
seclist = seg.getSectionsList()
|
||||
for sec in seclist:
|
||||
if sec.getName() != secname:
|
||||
continue
|
||||
|
||||
# Loop through each item in the section
|
||||
start = sec.getStartingAddress()
|
||||
end = start + sec.getLength() - 0x28
|
||||
|
||||
for addr in range(start, end):
|
||||
mach_port_subsystem_reserved = seg.readUInt64LE(addr + 0x18)
|
||||
mach_port_subsystem_routine0_impl_routine = seg.readUInt64LE(addr + 0x20)
|
||||
mach_port_subsystem_start = seg.readUInt32LE(addr + 0x8)
|
||||
mach_port_subsystem_end = seg.readUInt32LE(addr + 0xc)
|
||||
number_of_msgs = mach_port_subsystem_end - mach_port_subsystem_start
|
||||
|
||||
# Check if this looks like a mach_port_subsystem structure
|
||||
if (mach_port_subsystem_reserved == 0 and
|
||||
mach_port_subsystem_routine0_impl_routine == 0 and
|
||||
mach_port_subsystem_start != 0 and
|
||||
number_of_msgs > 0 and
|
||||
number_of_msgs < 1024):
|
||||
subsystem_name = "_MIG_subsystem_{0}".format(mach_port_subsystem_start)
|
||||
doc.log("{0}: MIG Subsystem {1}: {2} messages".format(hex(addr), mach_port_subsystem_start, number_of_msgs))
|
||||
seg.setNameAtAddress(addr, subsystem_name)
|
||||
|
||||
# Loop through the routine_descriptor structs
|
||||
msg_num = 0
|
||||
for routine_addr in range(addr + 0x20, addr+0x20+(number_of_msgs*0x28), 0x28):
|
||||
stub_routine_addr = routine_addr + 0x8
|
||||
stub_routine = seg.readUInt64LE(stub_routine_addr)
|
||||
msg = mach_port_subsystem_start + msg_num
|
||||
|
||||
if stub_routine == 0:
|
||||
doc.log("{0}: skip MIG msg {1}".format(hex(stub_routine_addr), msg))
|
||||
else:
|
||||
routine_name = "_MIG_msg_{0}".format(msg)
|
||||
doc.log("{0}: MIG msg {1}".format(hex(stub_routine_addr), msg))
|
||||
doc.setNameAtAddress(stub_routine, routine_name)
|
||||
|
||||
msg_num = msg_num + 1
|
||||
@@ -17,4 +17,4 @@ set_flags = check_flags(input_value)
|
||||
|
||||
if set_flags:
|
||||
print("Flags set:")
|
||||
print(*set_flags, sep="\n"
|
||||
print(*set_flags, sep="\n")
|
||||
|
||||
@@ -612,6 +612,20 @@ class TestSnakeI():
|
||||
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_constructors(self):
|
||||
'''Test the --constructors flag of SnakeI.'''
|
||||
args_list = ['-p', 'hello_1', '--constructors']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = ""
|
||||
# todo - this is only negative test, I should also check the file with valid constructors.
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
class TestSnakeII():
|
||||
'''Testing II. CODE SIGNING'''
|
||||
@classmethod
|
||||
@@ -804,6 +818,38 @@ class TestSnakeII():
|
||||
binary3 = lief.parse('hello_2_unsigned_binary')
|
||||
assert binary3.has_code_signature"""
|
||||
|
||||
def test_cs_offset(self):
|
||||
'''Test the --cs_offset flag of SnakeII.'''
|
||||
args_list = ['-p', 'hello_2', '--cs_offset']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
code_signing_processor = CodeSigningProcessor()
|
||||
code_signing_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'Code Signature offset: 0x8100'
|
||||
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_cs_flags(self):
|
||||
'''Test the --cs_flags flag of SnakeII.'''
|
||||
args_list = ['-p', 'hello_2', '--cs_flags']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
code_signing_processor = CodeSigningProcessor()
|
||||
code_signing_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'CS_FLAGS: 0x2'
|
||||
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
class TestSnakeIII():
|
||||
'''Testing III. CHECKSEC'''
|
||||
@classmethod
|
||||
@@ -1530,6 +1576,19 @@ class TestSnakeVI():
|
||||
cls.compiler = Compiler()
|
||||
cls.compiler.compileIt("../I.\ Mach-O/custom/hello.c", "hello_6", ["-arch", "arm64"])
|
||||
assert os.path.exists("hello_6")
|
||||
|
||||
# Create copies for some tests
|
||||
os.system("cp hello_6 hello_6_s")
|
||||
os.system("chmod +s hello_6_s")
|
||||
assert os.path.exists("hello_6_s")
|
||||
|
||||
os.system("cp hello_6 hello_6_g")
|
||||
os.system("chmod g+s hello_6_g")
|
||||
assert os.path.exists("hello_6_g")
|
||||
|
||||
os.system("cp hello_6 hello_6_sticky")
|
||||
os.system("chmod +t hello_6_sticky")
|
||||
assert os.path.exists("hello_6_sticky")
|
||||
|
||||
# Decompress KernelCache
|
||||
result = decompressKernelcache()
|
||||
@@ -1543,6 +1602,14 @@ class TestSnakeVI():
|
||||
cls.compiler.purgeCompiledFiles()
|
||||
assert not os.path.exists("hello_6")
|
||||
|
||||
# Remove samples
|
||||
os.system("rm hello_6_s")
|
||||
assert not os.path.exists("hello_6_s")
|
||||
os.system("rm hello_6_g")
|
||||
assert not os.path.exists("hello_6_g")
|
||||
os.system("rm hello_6_sticky")
|
||||
assert not os.path.exists("hello_6_sticky")
|
||||
|
||||
# Purge kernelcache directory
|
||||
os.system("rm -rf kernelcache")
|
||||
assert not os.path.exists("kernelcache")
|
||||
@@ -1658,3 +1725,132 @@ class TestSnakeVI():
|
||||
expected_output = 'amfi exitpoint:'
|
||||
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_mig(self):
|
||||
'''Test the --mig flag of SnakeVI.'''
|
||||
args_list = ['-p', '/usr/libexec/amfid', '--mig']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
amfi_processor = AMFIProcessor()
|
||||
amfi_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = '''MIG_subsystem_1000:
|
||||
- MIG_msg_1000: 0x100007ea8
|
||||
- MIG_msg_1001: 0x1000080dc
|
||||
- MIG_msg_1002: 0x0
|
||||
- MIG_msg_1003: 0x1000081dc
|
||||
- MIG_msg_1004: 0x100008300
|
||||
- MIG_msg_1005: 0x100008448
|
||||
- MIG_msg_1006: 0x1000084e8
|
||||
- MIG_msg_1007: 0x100008588'''
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_has_suid(self):
|
||||
'''Test the --has_suid flag of SnakeVI.'''
|
||||
args_list = ['-p', 'hello_6_s', '--has_suid']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
amfi_processor = AMFIProcessor()
|
||||
amfi_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'SUID: True'
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_has_sgid(self):
|
||||
'''Test the --has_sgid flag of SnakeVI.'''
|
||||
args_list = ['-p', 'hello_6_g', '--has_sgid']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
amfi_processor = AMFIProcessor()
|
||||
amfi_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'SGID: True'
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_has_sticky(self):
|
||||
'''Test the --has_sticky flag of SnakeVI.'''
|
||||
args_list = ['-p', 'hello_6_sticky', '--has_sticky']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
amfi_processor = AMFIProcessor()
|
||||
amfi_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'STICKY: True'
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_injectable_dyld(self):
|
||||
'''Test the --injectable_dyld flag of SnakeVI.'''
|
||||
args_list = ['-p', 'hello_6', '--injectable_dyld']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
amfi_processor = AMFIProcessor()
|
||||
amfi_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'Injectable DYLD_INSERT_LIBRARIES: True'
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_test_insert_dylib(self):
|
||||
'''Test the --test_insert_dylib flag of SnakeVI.'''
|
||||
args_list = ['-p', 'hello_6', '--test_insert_dylib']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
amfi_processor = AMFIProcessor()
|
||||
amfi_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'DYLD_INSERT_LIBRARIES is allowed: True'
|
||||
assert expected_output in uroboros_output # todo - I should also test for the false case (need to modify pytests to be thread aware).
|
||||
|
||||
def test_test_prune_dyld(self):
|
||||
'''Test the --test_prune_dyld flag of SnakeVI.'''
|
||||
args_list = ['-p', 'hello_6', '--test_prune_dyld']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
amfi_processor = AMFIProcessor()
|
||||
amfi_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'DEV Pruned: False'
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
def test_test_dyld_print_to_file(self):
|
||||
'''Test the --test_dyld_print_to_file flag of SnakeVI.'''
|
||||
args_list = ['-p', 'hello_6', '--test_dyld_print_to_file']
|
||||
args, file_path = argumentWrapper(args_list)
|
||||
|
||||
def code_block():
|
||||
macho_processor = MachOProcessor(file_path)
|
||||
macho_processor.process(args)
|
||||
amfi_processor = AMFIProcessor()
|
||||
amfi_processor.process(args)
|
||||
|
||||
uroboros_output = executeCodeBlock(code_block)
|
||||
expected_output = 'DYLD_PRINT_TO_FILE allowed: True'
|
||||
assert expected_output in uroboros_output
|
||||
|
||||
|
||||
Reference in New Issue
Block a user