add old go tooling

This commit is contained in:
nganhkhoa 2023-05-31 16:17:03 +07:00
commit 54f1f3eb38
57 changed files with 5791 additions and 0 deletions

178
macho-go/.gitignore vendored Normal file
View File

@ -0,0 +1,178 @@
# Created by https://www.toptal.com/developers/gitignore/api/python,vim,vscode
# Edit at https://www.toptal.com/developers/gitignore?templates=python,vim,vscode
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
pytestdebug.log
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
doc/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
pythonenv*
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# profiling data
.prof
### Vim ###
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
### vscode ###
.vscode/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# End of https://www.toptal.com/developers/gitignore/api/python,vim,vscode
bin/
pkg/protomodel

11
macho-go/Makefile Normal file
View File

@ -0,0 +1,11 @@
build:
protoc -I=proto --go_out=. proto/*.proto
go build -o bin/ios-wrapper ./cmd/ios-wrapper
go build -o bin/extract-section ./cmd/extract-section
build-linux:
protoc -I=proto --go_out=. proto/*.proto
GOOS=linux GOARCH=amd64 go build -o bin/ios-wrapper ./cmd/ios-wrapper
module:
go get -u google.golang.org/protobuf/cmd/protoc-gen-go

View File

@ -0,0 +1,25 @@
# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml
trigger:
- master
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
inputs:
version: '1.13'
- bash: |
wget "https://github.com/protocolbuffers/protobuf/releases/download/v3.13.0/protoc-3.13.0-linux-x86_64.zip"
unzip "protoc-3.13.0-linux-x86_64.zip" -d $HOME/protobuf
export PATH=$PATH:$HOME/protobuf/bin
export GOPATH=$HOME/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOPATH:$GOBIN
make install
make
displayName: 'Build'

155
macho-go/cloud-shield.py Normal file
View File

@ -0,0 +1,155 @@
import os
import shutil
import plistlib
import subprocess
import tempfile
import stat
joinp = os.path.join
wrapper_folder = os.path.expanduser("~/bcell/ios-wrapper-stable/bin")
sdk_root_folder = os.path.expanduser("~/bcell/ios-sdk")
class Worker:
@property
def __backup_folder(self):
# return tempfile.TemporaryDirectory(prefix="ios-wrapper")
return "/tmp/ios-wrapper-backup"
@property
def ios_wrapper(self):
return joinp(wrapper_folder, "ios-wrapper")
@property
def bcell_framework(self):
if self.encryption_required:
return joinp(sdk_root_folder, "release", "bazel-bin",
"bcell", "bcell.framework_archive-root", "bcell.framework/")
else:
return joinp(sdk_root_folder, "bazel-bin",
"bcell", "bcell.framework_archive-root", "bcell.framework/")
@property
def encryptor(self):
return joinp(sdk_root_folder, "release", "generated", "encryptor/encryptor.py")
@property
def binary_modified(self):
return joinp(self.__backup_folder, self.binary_name + "_modified")
@property
def bcell_raw(self):
return joinp(self.__backup_folder, "bcell_raw.dat")
@property
def bcell_unencrypted(self):
return joinp(self.__backup_folder, "bcell_unencrypted.dat")
@property
def bcell_encrypted(self):
return joinp(self.__backup_folder, "bcell_encrypted.dat")
def __load_binary(self):
self.binary = self.infile
self.binary_name = os.path.basename(self.infile)
def __init__(self, **kwargs):
for key in kwargs:
setattr(self, key, kwargs[key])
# prepare backup folder
shutil.rmtree(self.__backup_folder, ignore_errors=True)
os.mkdir(self.__backup_folder)
# load file based on file type
self.__load_binary()
if self.binary_name is None:
raise Exception("Cannot determine binary name")
def wrap(self):
if self.encryption_required:
subprocess.run([self.ios_wrapper, "wrap",
"-c", self.config,
"-s", "-b", self.bcell_raw,
"-o", self.binary_modified,
self.binary])
else:
subprocess.run([self.ios_wrapper, "wrap",
"-c", self.config,
"-b", self.bcell_raw,
"-o", self.binary_modified,
self.binary])
return
print("ENCRYPT BCELL FILE")
subprocess.run(["python3", self.encryptor,
"--infile", self.bcell_unencrypted,
"--outfile", self.bcell_encrypted])
def finish(self):
bcell_out = os.path.dirname(self.binary)
os.makedirs(joinp(bcell_out, "Frameworks"), exist_ok=True)
bcell_file = (self.bcell_raw, self.bcell_encrypted)[self.encryption_required]
shutil.copy(bcell_file, joinp(bcell_out, "bcell.dat"))
shutil.copy(self.binary_modified, joinp(bcell_out, self.binary_name))
shutil.rmtree(joinp(bcell_out, "Frameworks", "bcell.framework"), ignore_errors=True)
shutil.copytree(self.bcell_framework, joinp(bcell_out, "Frameworks", "bcell.framework"))
st = os.stat(joinp(bcell_out, self.binary_name))
os.chmod(joinp(bcell_out, self.binary_name), st.st_mode | stat.S_IEXEC)
self.info(joinp(bcell_out, self.binary_name))
def cleanup(self):
shutil.rmtree(self.__backup_folder, ignore_errors=True)
def info(self, file):
subprocess.run([self.ios_wrapper, "info", file])
def check_files(file_path):
if not os.path.isfile(file_path):
raise Exception("The file %s does not exist" % file_path)
return
def is_encryption_required():
return os.environ.get("REQUIRE_ENCRYPTION", None) is not None
if __name__ == "__main__":
import argparse
description = """
A test tool made for use with Xcode, paths to wrapper and bcell.framework
are hard-coded in this script, modify them if needed.
Pass in Xcode Build Phase a Run script:
```
python3 /path/to/cloud-shield.py \
-c ${PROJECT_DIR}/bcell_config.json \
-i ${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}
```
"""
parser = argparse.ArgumentParser(description=description,
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("-i", "--infile", metavar="input-file",
required=True, help="Input binary built by Xcode Run script")
parser.add_argument("-c", "--config", metavar="config-file",
required=True, help="Config JSON file")
options = parser.parse_args()
check_files(options.infile)
check_files(options.config)
encryption_required = is_encryption_required()
worker = Worker(encryption_required=encryption_required,
infile=options.infile,
config=options.config)
worker.wrap()
worker.finish()
worker.cleanup()

View File

@ -0,0 +1,47 @@
package main
import (
"ios-wrapper/pkg/ios/macho"
"github.com/alecthomas/kong"
"os"
"fmt"
"bytes"
)
type Argument struct {
One string `arg short:"o" required help:"" type:"existingfile"`
Two string `arg short:"t" required help:"" type:"existingfile"`
}
func main() {
var cli Argument
kong.Parse(&cli)
compare(cli.One, cli.Two)
}
func compare(one string, two string) {
f1, _ := os.OpenFile(one, os.O_RDONLY, 0644)
f2, _ := os.OpenFile(two, os.O_RDONLY, 0644)
var mc1 macho.MachoContext
var mc2 macho.MachoContext
mc1.ParseFile(f1, 0)
mc2.ParseFile(f2, 0)
s1 := mc1.FindSection("__text")
s2 := mc2.FindSection("__text")
if (s1.Size() == s2.Size()) {
fmt.Println("Size is equal")
} else {
fmt.Printf("%x <> %x\n", s1.Size(), s2.Size())
}
data1 := mc1.Cut(uint64(s1.Offset()), s1.Size())
data2 := mc1.Cut(uint64(s2.Offset()), s2.Size())
if (bytes.Compare(data1, data2) == 0) {
fmt.Println("Data is equal")
}
}

View File

@ -0,0 +1,9 @@
package main
import (
"ios-wrapper/internal/wrapper"
)
func main() {
wrapper.Cli()
}

4
macho-go/doc/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
compiled/
*.html
!template.html

View File

@ -0,0 +1,147 @@
#lang pollen
(define (sidenote label . xs)
`(splice-me
(label ((for ,label) (class "margin-toggle sidenote-number")))
(input ((id ,label) (class "margin-toggle")(type "checkbox")))
(span ((class "sidenote")) ,@xs)))
h1{Bind and protect Mach-O Binary}
section{
p{
Our product protects the iOS application. To do that, we modifies the original application such that without our library, the binary fails to start. We also detect jailbroken device, frida hooking device and crash the app if detected.
}
p{
We write ourself the wrapper, which modifies the original binary. The wrapper also create a code{LC_LOAD_DYLIB} command to load our library on runtime.
}
}
h2{Building the library}
section{
p{
We build the library as a dynamic library and distribute as a framework, code{bcell.framework/bcell}.
}
}
h3{Run code before the binary}
section{
p{
We ultilize the load order of code{dyld} to load our library and run our library code without requiring the user to call our library. We put our code{main} function inside the code{S_MOD_INIT_FUNC_POINTERS} section for code{dyld} to call when our library is loaded. We annotate the function with code{__attribute__((constructor))} to achive the effect.
}
pre{
void __attribute__((constructor)) main()
}
p{
Then the application must tell code{dyld} to load our library. We add a load command code{LC_LOAD_DYLIB} with the path code{@rpath/bcell.framework/bcell}. The framework folder is copied into Frameworks folder of the application.
}
}
h3{Remove unecessary information}
section{
h4{LC_FUNCTION_STARTS and LC_DATA_IN_CODE}
p{
}
h4{Classic linker symbols data}
p{
}
}
h3{Erasing initialization pointers}
section{
p{
Initialization pointers are pointers to functions, these functions are called by code{dyld} before code{__start}. These pointers are located inside section marked code{S_MOD_INIT_FUNC_POINTERS}. Conveniently this section can be modified as writable at runtime. Because ASLR sidenote["aslr"]{images loaded in memory with a random offset} adds a small offset from the prefered image address on load, so hardcoded pointers are misplaced. code{dyld} takes the concrete value at this place and call the function. To make the value points correctly into the function, code{dyld} add the pointers with ASLR offset before dispatching the function sidenote["aslr dyld"]{One may ask "why don't ◊code{dyld} get the value and add the ASLR offset later". We do not know, but the current design of code{dyld} is as presented}.
}
p{
Because this section is writable at runtime, we can modify the value at runtime. That is, we erase this section data, or replace with random bytes. When our library run, if every is fine, we restore the section with the correct pointers' value (with ASLR offset). Erasing this section is easy, just replace the bytes in the Mach-O binary at the section marked code{S_MOD_INIT_FUNC_POINTERS}.
}
}
h3{Remove lazy bind information}
section{
p{
}
}
h3{Restore on runtime}
section{
h4{Finding the mapped image on memory and ASLR offset}
p{
code{dyld} exposes some functions for traversing the list of loaded image. We use this to find the mapped executable image.
}
pre{
#if __ARM_ARCH_7A__
typedef struct mach_header macho_header;
typedef struct nlist symbol_t;
#elif __ARM_ARCH_7S__
typedef struct mach_header macho_header;
typedef struct nlist symbol_t;
#elif __ARM_ARCH_ISA_A64
typedef struct mach_header_64 macho_header;
typedef struct nlist_64 symbol_t;
#endif
for (unsigned int i = 0; i < _dyld_image_count(); i++) {
macho_header *mach_hdr = NULL;
mach_hdr = (macho_header *)_dyld_get_image_header(i);
if (mach_hdr == NULL) {
continue;
}
if (mach_hdr->filetype != MH_EXECUTE) {
continue;
}
return mach_hdr;
}
}
p{
Finding the ASLR offset is easy, we need only the prefered image base. The prefered image base is the vmaddr of a code{LC_SEGMENT} where fileoff == 0 but filesize != 0.
}
pre{
for _, segment := range segments {
if segment.Fileoff() == 0 && segment.Filesize() != 0 {
return segment.Vmaddr()
}
}
}
p{
We can save this prefered image base when we modify the binary or we can traverse the load commands from the mapped image header found. The ASLR offset is the subtraction of the mapped image header address and the prefered image base.
}
pre{
const uint64_t aslr_offset = (uint64_t)mach_hdr - (uint64_t)prefered_image_base;
}
h4{Restoring Initialization pointers}
p{
Restoring these pointers are easy, we need to locate the section on memory, set the section as writable, find the ASLR offset and fill the section with appropriate function pointers. Pointers should be written in the correct order as they originally appear due to functions using data initialized by previous functions.
}
p{
After finding the mapped image header on memory, we traverse the load commands looking for section marked code{S_MOD_INIT_FUNC_POINTERS}. Next we set the section as writable, with size page aligned.
}
pre{
vm_protect(mach_task_self(),
(vm_address_t)addr, size,
false, VM_PROT_WRITE | VM_PROT_READ)
}
p{
After that we write the pointers value with ASLR offset added to the section. And the Initialization pointers are restored.
}
h4{Restoring lazy bind}
p{
}
}

View File

@ -0,0 +1,23 @@
#lang pollen
h3{Manually sign the binary}
h3{Resign the binary}
section{
p{
Resign the binary is an important step after modifing a binary. The idevice will reject a falsy signed binary. To install a modified binary, we need to resign the binary. This problem is not easy because Apple checks the application certificate online. We must register a certificate online and use it to sign our application. Registering a certificate online can only be done through Xcode.
}
p{
Signin with Apple if haven't done so. Use Xcode and create a project, set signer to yourself and sign type to development. Build and install the app on a device. Go to the build folder, collect the embedded.mobileprovision file. Find the identity used to sign the binary and sign with that identity.
}
pre{
security find-identity -p codesigning -v
# copy the text or hash --> IDENTITY
security cms -D -i embedded.mobileprovision > profile.plist
/usr/libexec/PlistBuddy -x -c 'Print :Entitlements' profile.plist > entitlements.plist
codesign -fs IDENTITY --entitlements entitlements.plist BINARY
}
}

349
macho-go/doc/index.html.pm Normal file
View File

@ -0,0 +1,349 @@
#lang pollen
(require txexpr)
(require pollen/decode)
(define-meta template "template.html")
(define (sidenote label . xs)
`(splice-me
(label ((for ,label) (class "margin-toggle sidenote-number")))
(input ((id ,label) (class "margin-toggle")(type "checkbox")))
(span ((class "sidenote")) ,@xs)))
(define (stupid-sidenote label . xs)
`(stupid-sidenote
(label ((for ,label) (class "margin-toggle sidenote-number")))
(input ((id ,label) (class "margin-toggle")(type "checkbox")))
(span ((class "sidenote")) ,@xs)))
(define (splice xs)
(apply append (for/list ([x (in-list xs)])
(if (and (txexpr? x) (member (get-tag x) '(splice-me)))
(get-elements x)
(list x)))))
(define (root . xs)
(decode `(decoded-root ,@xs)
#:txexpr-elements-proc (compose1 detect-paragraphs splice)
#:exclude-tags '(pre)
))
h1{Mach-O Binary Bindings}
h2{Mach-O Binary Format}
section{
p{
Mach-O binary is the executable file format that MacOS, iOS and tvOS use. The file is splited into 3 main parts. The header, the load commands, and the data. While the binary has 3 main parts, we only need to focus on the header and load commands part. The data part are referenced by load commands part.
}
}
h3{Header}
section{
p{
The header provides summary information to the binary. First comes the file magic then the file's archtecture, number and size of load commands, and a bitmask flag.
}
p{
The Mach-O binary comes in 2 different set of magic bytes, 32-bit, 64-bit with endianess in either little or big.
}
pre{
#define MH_MAGIC 0xfeedface /* the mach magic number */
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
}
p{
The file architecture is denoted by two numbers. One specifies the main architecture (intel 386, arm, mips, ...), one specifies the sub-architecture (armv7, arm64e, ...). sidenote["machine-arch-enum"]{code{cctools/include/mach/machine.h}}
}
p{
The Mach-O binary uses load commands to structure the binary information. The header stores the number of load commands and size (in bytes) for parsing and validation.
}
p{
The header also has a bitmask flag value for others information, e.g PIE.
}
}
h3{Load Commands}
section{
p{
Load commands are the main part of a Mach-O binary. These load commands are read by code{dyld} linker to load the binary into memory. Each load commands are prepend by a simple header indicate the type and size. The body of each load commands different with each type.
}
p{
At the time of writing, there are more than 50 load command types. But we only go into a few types that are necessary to understand the Mach-O Binary Binding.
}
}
h4{LC_SEGMENT and LC_SECTION}
section{
p{
code{LC_SEGMENT} defines a region for virtual mapping into the memory on Runtime. It defines the virtual address and size for code{dyld} to map. A segment contains a list of sections defined as code{LC_SECTION}. code{LC_SECTION}s are located below the code{LC_SEGMENT}. Each section contains a data pointer to instruct code{dyld} to load bytes into memory.
}
p{
code{LC_SEGMENT} are loaded into memory with default Read-Write-Executable marked by code{initprot}. We can modify the memory region on runtime using code{vm_protect}. However, protection bit cannot exceed code{maxprot}.
}
p{
code{LC_SECTION} may contain special data, such sections are marked by the code{type} field. A section containing C literal strings are marked as code{S_CSTRING_LITERALS}.
}
}
h4{LC_LOAD_DYLIB and LC_RPATH}
section{
p{
The Mach-O binary defines the dynamically linked library using code{LC_LOAD_DYLIB}. When code{dyld} reads this load command, it loads the library into the memory. code{LC_LOAD_DYLIB} contains a path to the linked library, e.g. code{/usr/lib/libSystem.B.dylib}. The path can be absolute ◊code{/absolute/lib}, relative ◊code{../relative/lib} or prefixed. Prefixed path are path prefixed by either code{@executable_path}, code{@loader_path} or code{@rpath}. code{@executable_path} points to the directory where the executable is placed. code{@loader_path} points to code{dyld} parent directory. code{@rpath} are defined using code{LC_RPATH}.
}
p{
code{LC_RPATH} adds to the set of paths to find a library path with code{@rpath}. Often, we see a code{LC_RPATH = @executable_path/Frameworks} defined. This path is commonly used in iOS applications as third-party libraries are placed inside code{Frameworks} folder, and specify code{@rpath} to load libraries are shorter to write (and understand). sidenote["rpath sucks"]{Not yet research into same library name put in different @rpath}
}
}
h4{LC_ID_DYLIB}
section{
p{
This is a special command indicating the path to put the library when compiled. The compilation may fail if the library is placed wrongly. Renaming the library also fail the compilation. sidenote["id dylib"]{Not 100% sure}
}
}
h4{LC_DYLD_INFO or LC_DYLD_INFO_ONLY}
section{
p{
This command appears only once in a Mach-O binary and contains pointers to the encoded list of external (imported) symbols and exported symbols. External symbols reside on third-party library and must be bound by code{dyld}. There are 3 types of external symbols, normal bind, weak bind, and lazy bind.
}
p{
External symbols are defined using byte-stream. Reading and decode the byte-stream reveals the symbol's name, the library hosting the symbol.
For normal bind symbols, code{dyld} read and decode the byte-stream when loading the binary to memory and write the symbol's function address to a section marked code{S_NON_LAZY_SYMBOL_POINTERS}.
For lazy bind symbols, the decoding process is done through PLT sidenote["PLT"]{Procedural Linkage Table}. Each lazy bind symbol have a helper stub calling code{dyld_stub_binder} with a number. The number is an offset to lazy bind symbols byte-stream. When parsed, the symbol's name and the library hosting the symbol is known. With these two information, ◊code{dyld} look up the symbol's name in the export table of the hosting library and return the address to the function. code{dyld} also rewrite the PLT table on success so the next time the same function is called, it will be a direct call.
pre{
__LAZY:
...
dword _stub_write
...
foo:
push argument_of_write
jmp [__LAZY + offset_to_stub_write]
_stub_write:
push OFFSET_WRITE_IN_BYTE_STREAM
jmp _dyld_stub_binder
_dyld_stub_binder:
call fastBindLazySymbol
call eax
# where fastBindLazySymbol resolve the address of the symbol and
# replace the __LAZY _stub_write pointer with the symbol's address.
}
}
}
h4{LC_CODE_SIGNATURE}
section{
p{
The *OS system requires the running binary to be signed. Signed Mach-O binary has a load command code{LC_CODE_SIGNATURE} points to a compressed data containing the vendor information, the key used signing and the certificate. sidenote["codesign"]{Yet to read how to parse this information}
}
}
h4{LC_ENCRYPTION_INFO}
section{
p{
To prevent people copying binaries, Apple introduced PlayFair, a mechanism to prevent digital infringement. With PlayFair, the app is encrypted with a key installed on the chip when downloaded from AppStore. The content is decrypted at runtime using the hardware key. When running the binary in another machine, the app content can't be decrypted and fail to launch.
}
p{
code{LC_ENCRYPTION_INFO} points to the region of the encrypted data and indicate if the content is encrypted or decrypted. It should only be encrypted when distributed through AppStore. In later section, we introduce how one can decrypt the content with a Jailbroken iOS device.
}
}
h4{LC_FUNCTION_STARTS and LC_DATA_IN_CODE}
section{
p{
}
}
h4{LC_SYMTAB and LC_DYSYMTAB}
section{
p{
}
}
h2{Fat Binary Format}
section{
p{
Apple has a binary format for combining different architecture of an application into one, called Fat binary. The binary format is simple, it defines a list of Mach-O binaries. The Mach-O binaries inside are located with an alignment of 2. Infact:
}
pre{
func GetAlignment(h *macho.Header) uint32 {
switch h.Cputype() {
case CPU_TYPE_ARM, CPU_TYPE_ARM64:
return 0xe // log2(0x4000)
case CPU_TYPE_POWERPC,
CPU_TYPE_POWERPC64,
CPU_TYPE_I386,
CPU_TYPE_X86_64:
return 0xc // log2(0x1000)
default:
return 0xd // log2(0x2000)
}
}
}
}
h2{ipa file}
section{
p{
In iOS applications are distributed using .ipa file format. The .ipa file is a .zip file with a certain structure. A valid ipa file must have a folder code{*.app}. Inside the folder must have two files, code{Info.plist} and a binary. The binary name must conform to the code{Info.plist}'s ◊code{CFBundleExecutable} field. ◊code{Info.plist} file format is (UTF8) XML or binary encoded, both format can be read using python's a['((href "https://docs.python.org/3/library/plistlib.html"))]{plistlib}.
}
h3{Info.plist}
p{
}
}
h2{Jailbreak and Mach-O hacks}
h3{Jailbreak the iOS}
section{
p{
Jailbreak can be done by using tools like a['((href "https://checkra.in/"))]{code{checkra1n}}. At the time of writing, Jailbreak can be done easily for devices iPhone5s to iPhone X and support for iOS 12 to 14. This jailbreak is semi-tethered, means that a reboot breaks the jailbreak.
}
p{
Jailbreak disable some security features by patching the iOS kernel. It enables USB access, re-mounts code{/} as both readable and writable, removes sandbox, etc. Jailbreaking tool often come with some other applications, e.g openssh, Cydia. A device with Cydia installed is Jailbroken. In custom jailbreaking tool, we can omit the installation of Cydia. However, most jailbroken devices are jailbroken using public tooling (like checkra1n), and by default, it comes with Cydia installed.
}
p{
Semi-tethered jailbreak losts the kernel patch after a reboot, but applications installed when the jailbreak was active remain. In these cases, the device must run the jailbreak again to access jailbreak features.
}
p{
Jailbreak tools install code{openssh} to provide access through USB connection. code{checkra1n} install and run openssh at port 44. After connecting the device with the USB cable, we proxy the USB connection to code{localhost}, now code{ssh} to the device is possible. To proxy the USB connection, we use a['((href "https://libimobiledevice.org/"))]{code{iproxy}} sidenote["iproxy"]{code{brew install libimobiledevice}}.
}
pre{
iproxy 4444 44
ssh root@localhost -p 4444
# default root password: alpine
}
p{
In old books and article the path to installed applications are wrong. Use this path: code{/var/containers/Bundle/Application/ID/}, where ID is a long hash for each application.sidenote["app path"]{old path: code{/var/mobile/Applications/ID/}}
}
}
h3{Decrypt the binary}
section{
p{
As mentioned in code{LC_ENCRYPTION_INFO}, binaries downloaded from AppStore is encrypted with a hardware key. It is said that the key can't be retrieved, and the only to decrypt the binary is to let it run and dump the binary after decryption process. Here we demonstrate the use of ◊a['((href "https://github.com/stefanesser/dumpdecrypted"))]{dumpdecrypted}.
}
p{
Compile the library, add code{-miphoneos-version-min=<iOS>} as iOS version of the idevice. Sign the output binary. Use scp to copy the binary to the idevice. ssh into the idevice and run the application with the library.
}
pre{
set DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib
/var/containers/Bundle/Application/ID/APPNAME.app/BINARY
}
p{
A binary BINARY.decrypted will be written out with contents decrypted. This method is the most simple way to decrypt an application but still cannot install to another machine (even after resigning). This method only decrypt the main application, however the third-party libraries are also encrypted and not decrypted. We can overcome this with code{frida-ios-dump}. This tool uses Frida, which will be discussed in later section.
}
p{
code{dyld} maps the encrypted section using code{mremap_encrypted} sidenote["mremap_encrypted"]{code{dyld/dyld3/Loading.cpp}}. Another tool using this method to decrypt is a['((href "https://github.com/JohnCoates/flexdecrypt"))]{flexdecrypt}.
}
pre{
#if (__arm__ || __arm64__) && !TARGET_OS_SIMULATOR
// tell kernel about fairplay encrypted regions
uint32_t fpTextOffset;
uint32_t fpSize;
if ( image->isFairPlayEncrypted(fpTextOffset, fpSize) ) {
const mach_header* mh = (mach_header*)loadAddress;
int result = ::mremap_encrypted(((uint8_t*)mh) + fpTextOffset, fpSize, 1, mh->cputype, mh->cpusubtype);
if ( result != 0 ) {
diag.error("could not register fairplay decryption, mremap_encrypted() => %d", result);
::vm_deallocate(mach_task_self(), loadAddress, (vm_size_t)totalVMSize);
return;
}
}
#endif
}
}
h3{Frida hooking}
section{
p{
Frida is a hooking framework. Using Frida, one can easily intercept the running application. Frida supports both jailbroken and non-jailbroken devices. Frida has two components, the server and the client. The server is installed in our machine, the client is attached to the application.
}
pre{
python3 -m pip install frida-tools
}
p{
The Frida a['((href "https://frida.re/docs/ios/"))]{documentation} gives some instructions for using Frida with jailbroken or non-jailbroken devices. In our experience, we setup Frida manually, for both jailbroken and non-jailbroken devices, as stated below.
}
h4{Setup for jailbroken devices}
p{
Jailbroken devices with Cydia can easily install Frida by adding Frida from Cydia store. The installed Frida should have its ABI compatible with the device. Frida can also be installed by download, sign, copy (scp) onto the device and run the Frida client.
}
h4{Setup for non-jailbroken devices}
p{
Non-jailbroken devices cannot install Frida client. Frida server uses Frida client to inject Frida Gadget into the application on jailbroken devices. For non-jailbroken devices, we must setup Frida Gadget directly inside the application. We can do this by using a['((href "https://github.com/Tyilo/insert_dylib"))]{insert_dylib}. Build insert_dylib, download FridaGadget.dylib.
}
For an application having the ipa file (.app folder).
pre{
insert_dylib --strip-codesig --inplace '@executable_path/Frameworks/FridaGadget.dylib' BINARY
}
Copy FridaGadget.dylib into Frameworks folder. Resign BINARY and sign FridaGadget.dylib. Use Xcode to install the application. Frida can connect and intercept the application after the application started.
}
h4{Using Frida}
p{
Frida intercepts the application and provides a scripting utility. We can use the scripting engine to inspect memory, change registers' value. The iOS ecosystem often use Objective-C (Swift also use Objective-C at low-level) and Frida also supports conversion from Objective-C data type to raw data type, e.g. NSString to raw string.
}
h3{Others tools}
section{
p{
There are many other tools to pentest/reverse/intercept/... iOS applications. We haven't tested these tools but they are worth mentioning.
}
ul{
li{a['((href "http://www.cydiasubstrate.com/"))]{CydiaSubstrate} - Hook framework}
li{a['((href "https://github.com/akemin-dayo/AppSync"))]{AppSync} - Utilities for jailbreak devices}
li{a['((href "https://github.com/jmpews/Dobby"))]{Dobby} - Hook framework (stale)}
li{a['((href "https://github.com/KJCracks/Clutch"))]{Clutch} - Dump decrypted}
li{a['((href "https://github.com/nygard/class-dump"))]{class-dump} - Dump Objective-C class metadata}
li{a['((href "https://github.com/asLody/whale"))]{whale} - Hook framework}
li{a['((href "https://github.com/alexzielenski/optool"))]{optool} - Mach-O binary utilities}
li{a['((href "https://github.com/steakknife/unsign"))]{unsign} - unsign Mach-O binary}
}
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>◊select['h1 doc]</title>
<link rel="stylesheet" href="tufte.css">
</head>
<article>
◊(->html doc)
</article>

469
macho-go/doc/tufte.css Normal file
View File

@ -0,0 +1,469 @@
@charset "UTF-8";
/* Import ET Book styles
adapted from https://github.com/edwardtufte/et-book/blob/gh-pages/et-book.css */
@font-face {
font-family: "et-book";
src: url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot");
src: url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.woff") format("woff"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.ttf") format("truetype"), url("et-book/et-book-roman-line-figures/et-book-roman-line-figures.svg#etbookromanosf") format("svg");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "et-book";
src: url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot");
src: url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.woff") format("woff"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.ttf") format("truetype"), url("et-book/et-book-display-italic-old-style-figures/et-book-display-italic-old-style-figures.svg#etbookromanosf") format("svg");
font-weight: normal;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "et-book";
src: url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot");
src: url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.woff") format("woff"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.ttf") format("truetype"), url("et-book/et-book-bold-line-figures/et-book-bold-line-figures.svg#etbookromanosf") format("svg");
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "et-book-roman-old-style";
src: url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot");
src: url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.eot?#iefix") format("embedded-opentype"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.woff") format("woff"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.ttf") format("truetype"), url("et-book/et-book-roman-old-style-figures/et-book-roman-old-style-figures.svg#etbookromanosf") format("svg");
font-weight: normal;
font-style: normal;
font-display: swap;
}
/* Tufte CSS styles */
html {
font-size: 15px;
}
body {
width: 87.5%;
margin-left: auto;
margin-right: auto;
padding-left: 12.5%;
font-family: et-book, Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
background-color: #fffff8;
color: #111;
max-width: 1400px;
counter-reset: sidenote-counter;
}
h1 {
font-weight: 400;
margin-top: 4rem;
margin-bottom: 1.5rem;
font-size: 3.2rem;
line-height: 1;
}
h2 {
font-style: italic;
font-weight: 400;
margin-top: 2.1rem;
margin-bottom: 1.4rem;
font-size: 2.2rem;
line-height: 1;
}
h3 {
font-style: italic;
font-weight: 400;
font-size: 1.7rem;
margin-top: 2rem;
margin-bottom: 1.4rem;
line-height: 1;
}
hr {
display: block;
height: 1px;
width: 55%;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
p.subtitle {
font-style: italic;
margin-top: 1rem;
margin-bottom: 1rem;
font-size: 1.8rem;
display: block;
line-height: 1;
}
.numeral {
font-family: et-book-roman-old-style;
}
.danger {
color: red;
}
article {
padding: 5rem 0rem;
}
section {
padding-top: 1rem;
padding-bottom: 1rem;
}
p,
ol,
ul {
font-size: 1.4rem;
line-height: 2rem;
}
p {
margin-top: 1.4rem;
margin-bottom: 1.4rem;
padding-right: 0;
vertical-align: baseline;
}
/* Chapter Epigraphs */
div.epigraph {
margin: 5em 0;
}
div.epigraph > blockquote {
margin-top: 3em;
margin-bottom: 3em;
}
div.epigraph > blockquote,
div.epigraph > blockquote > p {
font-style: italic;
}
div.epigraph > blockquote > footer {
font-style: normal;
}
div.epigraph > blockquote > footer > cite {
font-style: italic;
}
/* end chapter epigraphs styles */
blockquote {
font-size: 1.4rem;
}
blockquote p {
width: 55%;
margin-right: 40px;
}
blockquote footer {
width: 55%;
font-size: 1.1rem;
text-align: right;
}
section > p,
section > footer,
section > table {
width: 55%;
}
/* 50 + 5 == 55, to be the same width as paragraph */
section > ol,
section > ul {
width: 50%;
-webkit-padding-start: 5%;
}
li:not(:first-child) {
margin-top: 0.25rem;
}
figure {
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
max-width: 55%;
-webkit-margin-start: 0;
-webkit-margin-end: 0;
margin: 0 0 3em 0;
}
figcaption {
float: right;
clear: right;
margin-top: 0;
margin-bottom: 0;
font-size: 1.1rem;
line-height: 1.6;
vertical-align: baseline;
position: relative;
max-width: 40%;
}
figure.fullwidth figcaption {
margin-right: 24%;
}
/* Links: replicate underline that clears descenders */
a:link,
a:visited {
color: inherit;
}
.no-tufte-underline:link {
background: unset;
text-shadow: unset;
}
a:link, .tufte-underline, .hover-tufte-underline:hover {
text-decoration: none;
background: -webkit-linear-gradient(#fffff8, #fffff8), -webkit-linear-gradient(#fffff8, #fffff8), -webkit-linear-gradient(currentColor, currentColor);
background: linear-gradient(#fffff8, #fffff8), linear-gradient(#fffff8, #fffff8), linear-gradient(currentColor, currentColor);
-webkit-background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
-moz-background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
background-size: 0.05em 1px, 0.05em 1px, 1px 1px;
background-repeat: no-repeat, no-repeat, repeat-x;
text-shadow: 0.03em 0 #fffff8, -0.03em 0 #fffff8, 0 0.03em #fffff8, 0 -0.03em #fffff8, 0.06em 0 #fffff8, -0.06em 0 #fffff8, 0.09em 0 #fffff8, -0.09em 0 #fffff8, 0.12em 0 #fffff8, -0.12em 0 #fffff8, 0.15em 0 #fffff8, -0.15em 0 #fffff8;
background-position: 0% 93%, 100% 93%, 0% 93%;
}
@media screen and (-webkit-min-device-pixel-ratio: 0) {
a:link, .tufte-underline, .hover-tufte-underline:hover {
background-position-y: 87%, 87%, 87%;
}
}
a:link::selection,
a:link::-moz-selection {
text-shadow: 0.03em 0 #b4d5fe, -0.03em 0 #b4d5fe, 0 0.03em #b4d5fe, 0 -0.03em #b4d5fe, 0.06em 0 #b4d5fe, -0.06em 0 #b4d5fe, 0.09em 0 #b4d5fe, -0.09em 0 #b4d5fe, 0.12em 0 #b4d5fe, -0.12em 0 #b4d5fe, 0.15em 0 #b4d5fe, -0.15em 0 #b4d5fe;
background: #b4d5fe;
}
/* Sidenotes, margin notes, figures, captions */
img {
max-width: 100%;
}
.sidenote,
.marginnote {
float: right;
clear: right;
margin-right: -60%;
width: 50%;
margin-top: 0;
margin-bottom: 0;
font-size: 1.1rem;
line-height: 1.3;
vertical-align: baseline;
position: relative;
}
.sidenote-number {
counter-increment: sidenote-counter;
}
.sidenote-number:after,
.sidenote:before {
font-family: et-book-roman-old-style;
position: relative;
vertical-align: baseline;
}
.sidenote-number:after {
content: counter(sidenote-counter);
font-size: 1rem;
top: -0.5rem;
left: 0.1rem;
}
.sidenote:before {
content: counter(sidenote-counter) " ";
font-size: 1rem;
top: -0.5rem;
}
blockquote .sidenote,
blockquote .marginnote {
margin-right: -82%;
min-width: 59%;
text-align: left;
}
div.fullwidth,
table.fullwidth {
width: 100%;
}
div.table-wrapper {
overflow-x: auto;
font-family: "Trebuchet MS", "Gill Sans", "Gill Sans MT", sans-serif;
}
.sans {
font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
letter-spacing: .03em;
}
code, pre > code {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 1.0rem;
line-height: 1.42;
-webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS. See https://github.com/edwardtufte/tufte-css/issues/81#issuecomment-261953409 */
}
.sans > code {
font-size: 1.2rem;
}
h1 > code,
h2 > code,
h3 > code {
font-size: 0.80em;
}
.marginnote > code,
.sidenote > code {
font-size: 1rem;
}
pre > code {
font-size: 0.9rem;
width: 52.5%;
margin-left: 2.5%;
overflow-x: auto;
display: block;
}
pre.fullwidth > code {
width: 90%;
}
.fullwidth {
max-width: 90%;
clear:both;
}
span.newthought {
font-variant: small-caps;
font-size: 1.2em;
}
input.margin-toggle {
display: none;
}
label.sidenote-number {
display: inline;
}
label.margin-toggle:not(.sidenote-number) {
display: none;
}
.iframe-wrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
padding-top: 25px;
height: 0;
}
.iframe-wrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
@media (max-width: 760px) {
body {
width: 84%;
padding-left: 8%;
padding-right: 8%;
}
hr,
section > p,
section > footer,
section > table {
width: 100%;
}
pre > code {
width: 97%;
}
section > ol {
width: 90%;
}
section > ul {
width: 90%;
}
figure {
max-width: 90%;
}
figcaption,
figure.fullwidth figcaption {
margin-right: 0%;
max-width: none;
}
blockquote {
margin-left: 1.5em;
margin-right: 0em;
}
blockquote p,
blockquote footer {
width: 100%;
}
label.margin-toggle:not(.sidenote-number) {
display: inline;
}
.sidenote,
.marginnote {
display: none;
}
.margin-toggle:checked + .sidenote,
.margin-toggle:checked + .marginnote {
display: block;
float: left;
left: 1rem;
clear: both;
width: 95%;
margin: 1rem 2.5%;
vertical-align: baseline;
position: relative;
}
label {
cursor: pointer;
}
div.table-wrapper,
table {
width: 85%;
}
img {
width: 100%;
}
}

12
macho-go/go.mod Normal file
View File

@ -0,0 +1,12 @@
module ios-wrapper
go 1.13
require (
github.com/akamensky/argparse v1.2.2
github.com/alecthomas/kong v0.2.16
github.com/golang/protobuf v1.5.0
github.com/sirupsen/logrus v1.8.0
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/protobuf v1.26.0
)

87
macho-go/go.sum Normal file
View File

@ -0,0 +1,87 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/akamensky/argparse v1.2.2 h1:P17T0ZjlUNJuWTPPJ2A5dM1wxarHgHqfYH+AZTo2xQA=
github.com/akamensky/argparse v1.2.2/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/alecthomas/kong v0.2.16 h1:F232CiYSn54Tnl1sJGTeHmx4vJDNLVP2b9yCVMOQwHQ=
github.com/alecthomas/kong v0.2.16/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,33 @@
package action
import (
. "ios-wrapper/internal/wrapper/ofile"
)
// Actions on MachoFile
type Action interface {
withMacho(mf *MachoFile) error
withFat(ff *FatFile) error
// TODO: error context
}
func RunAction(action Action, ofile OFile) error {
switch ofile.(type) {
case *MachoFile:
return action.withMacho(ofile.(*MachoFile))
case *FatFile:
return action.withFat(ofile.(*FatFile))
}
// not likely
return nil
}
func defaultWithFat(action Action, ff *FatFile) error {
for _, macho := range ff.Machos() {
err := action.withMacho(macho)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,31 @@
package action
import (
log "github.com/sirupsen/logrus"
. "ios-wrapper/internal/wrapper/ofile"
)
type addDylib struct {
dylib_to_add []string
}
func (action *addDylib) withMacho(mf *MachoFile) error {
for _, dylib := range action.dylib_to_add {
mf.Context().AddDylib(dylib)
log.WithFields(log.Fields{
"dylib": dylib,
}).Info("Add Load Dylib Command")
}
return nil
}
func (action *addDylib) withFat(ff *FatFile) error {
return defaultWithFat(action, ff)
}
func NewAddDylibAction(dylib_to_add []string) *addDylib {
return &addDylib{
dylib_to_add,
}
}

View File

@ -0,0 +1,31 @@
package action
import (
log "github.com/sirupsen/logrus"
. "ios-wrapper/internal/wrapper/ofile"
)
type addRpath struct {
rpath []string
}
func (action *addRpath) withMacho(mf *MachoFile) error {
for _, rpath := range action.rpath {
mf.Context().AddRPath(rpath)
log.WithFields(log.Fields{
"rpath": rpath,
}).Info("Add Rpath Command")
}
return nil
}
func (action *addRpath) withFat(ff *FatFile) error {
return defaultWithFat(action, ff)
}
func NewAddRpathAction(rpath []string) *addRpath {
return &addRpath{
rpath,
}
}

View File

@ -0,0 +1,20 @@
package action
import (
. "ios-wrapper/internal/wrapper/ofile"
)
type removeClassicSymbol struct{}
func (action *removeClassicSymbol) withMacho(mf *MachoFile) error {
mf.Context().RemoveClassicSymbol()
return nil
}
func (action *removeClassicSymbol) withFat(ff *FatFile) error {
return defaultWithFat(action, ff)
}
func NewRemoveClassicSymbolAction() *removeClassicSymbol {
return &removeClassicSymbol{}
}

View File

@ -0,0 +1,20 @@
package action
import (
. "ios-wrapper/internal/wrapper/ofile"
)
type removeCodeSignature struct{}
func (action *removeCodeSignature) withMacho(mf *MachoFile) error {
mf.Context().RemoveCodesign()
return nil
}
func (action *removeCodeSignature) withFat(ff *FatFile) error {
return defaultWithFat(action, ff)
}
func NewRemoveCodeSignatureAction() *removeCodeSignature {
return &removeCodeSignature{}
}

View File

@ -0,0 +1,20 @@
package action
import (
. "ios-wrapper/internal/wrapper/ofile"
)
type removeInitPointer struct{}
func (action *removeInitPointer) withMacho(mf *MachoFile) error {
mf.Context().RemoveInitFunctions()
return nil
}
func (action *removeInitPointer) withFat(ff *FatFile) error {
return defaultWithFat(action, ff)
}
func NewRemoveInitPointerAction() *removeInitPointer {
return &removeInitPointer{}
}

View File

@ -0,0 +1,20 @@
package action
import (
. "ios-wrapper/internal/wrapper/ofile"
)
type removeUnnecessaryInfo struct{}
func (action *removeUnnecessaryInfo) withMacho(mf *MachoFile) error {
mf.Context().RemoveUnnecessaryInfo()
return nil
}
func (action *removeUnnecessaryInfo) withFat(ff *FatFile) error {
return defaultWithFat(action, ff)
}
func NewRemoveUnnecessaryInfoAction() *removeUnnecessaryInfo {
return &removeUnnecessaryInfo{}
}

View File

@ -0,0 +1,68 @@
package action
import (
"fmt"
"io/ioutil"
"google.golang.org/protobuf/proto"
. "ios-wrapper/internal/wrapper/ofile"
"ios-wrapper/pkg/protomodel"
)
type saveBcellFile struct {
bcellfile string
content *protomodel.BcellFile
signed bool
}
func (action *saveBcellFile) addMacho(mf *MachoFile) {
action.content.MachoInfos[mf.Context().ArchName()] = mf.Info()
}
func (action *saveBcellFile) writeFile() error {
outfile := fmt.Sprintf(action.bcellfile)
content, err := proto.Marshal(action.content)
if err != nil {
return err
}
if action.signed {
blocks := []*protomodel.Block{
{
Key: 0,
Value: content,
},
}
signed_data := protomodel.SignedData{
Blocks: blocks,
}
content, err = proto.Marshal(&signed_data)
}
return ioutil.WriteFile(outfile, content, 0644)
}
func (action *saveBcellFile) withMacho(mf *MachoFile) error {
action.addMacho(mf)
return action.writeFile()
}
func (action *saveBcellFile) withFat(ff *FatFile) error {
for _, macho := range ff.Machos() {
action.addMacho(macho)
}
return action.writeFile()
}
func NewSaveBcellFileAction(userconfig *protomodel.Config, bcellfile string, signed bool) *saveBcellFile {
content := &protomodel.BcellFile{
BcellConfig: userconfig,
MachoInfos: make(map[string]*protomodel.MachoInfo),
}
return &saveBcellFile{
bcellfile,
content,
signed,
}
}

View File

@ -0,0 +1,34 @@
package action
import (
log "github.com/sirupsen/logrus"
. "ios-wrapper/internal/wrapper/ofile"
"ios-wrapper/pkg/protomodel"
)
type saveInitPointer struct{}
func (action *saveInitPointer) withMacho(mf *MachoFile) error {
init_pointers := []*protomodel.MachoInfo_InitPointer{}
for _, ptr := range mf.Context().InitFunctions() {
init_pointers = append(init_pointers,
&protomodel.MachoInfo_InitPointer{
Offset: ptr.Offset(),
Value: ptr.Value(),
})
}
log.WithFields(log.Fields{
"pointers": init_pointers,
}).Info("Saved Init Pointers")
mf.Info().InitPointers = init_pointers
return nil
}
func (action *saveInitPointer) withFat(ff *FatFile) error {
return defaultWithFat(action, ff)
}
func NewSaveInitPointerAction() *saveInitPointer {
return &saveInitPointer{}
}

View File

@ -0,0 +1,32 @@
package action
import (
"io/ioutil"
. "ios-wrapper/internal/wrapper/ofile"
"ios-wrapper/pkg/ios/fat"
)
type writeFile struct {
outfile string
}
func (action *writeFile) withMacho(mf *MachoFile) error {
data, _ := ioutil.ReadFile(mf.TmpFile())
return ioutil.WriteFile(action.outfile, data, 0644)
}
func (action *writeFile) withFat(ff *FatFile) error {
var files []string
for _, macho := range ff.Machos() {
files = append(files, macho.TmpFile())
}
fat.FatJoin(files, action.outfile)
return nil
}
func NewWriteFileAction(outfile string) *writeFile {
return &writeFile{
outfile,
}
}

View File

@ -0,0 +1,48 @@
package addr2line
import (
"strconv"
. "ios-wrapper/pkg/ios/macho"
)
// Try to get Image base of a DWARF binary
// using __mh_execute_header symbol
func TryGetImageBase(mc *MachoContext, symbols []*Symbol) uint64 {
try := mc.ImageBase()
if try != 0 {
return try
}
for _, symbol := range symbols {
if symbol.Name() == "__mh_execute_header" {
return symbol.Address()
}
}
return 0
}
// find the symbol name from dysymtab
// the address given is somewhere in the function,
// assuming that the address is sorted, we find the last symbol
// has its address smaller than `tofind`
// I'm not sure this would work always, ;)
func FindSymbol(symbols []*Symbol, tofind uint64) string {
var lastSymbol string = ""
for _, symbol := range symbols {
if symbol.Address() > tofind {
return lastSymbol
} else {
lastSymbol = symbol.Name()
}
}
return ""
}
func ParseAddressString(s string) (uint64, error) {
s_, err := strconv.ParseInt(s, 0, 64)
v := uint64(s_)
if err != nil {
return 0, err
}
return v, nil
}

View File

@ -0,0 +1,81 @@
package addr2line
import (
"errors"
"fmt"
. "ios-wrapper/pkg/ios/macho"
)
type Resolver struct {
symbols []*Symbol
debuglineInfo *DebuglineInfo
aslr uint64
addresses []string
}
type Resolved struct {
Raw uint64
Base uint64
Symbol string
File string
Line uint32
}
func (r *Resolved) Valid() bool {
return r.File == "" && r.Line == 0
}
func (resolver *Resolver) HasNext() bool {
return len(resolver.addresses) > 0
}
func (resolver *Resolver) Next() (*Resolved, error) {
if !resolver.HasNext() {
return nil, errors.New("There is no more address to resolve")
}
head, tail := resolver.addresses[0], resolver.addresses[1:]
resolver.addresses = tail
tofind_aslr, err := ParseAddressString(head)
if err != nil {
return nil, fmt.Errorf("Cannot parse hex address (%s)", head)
}
tofind := tofind_aslr - resolver.aslr
symbol := FindSymbol(resolver.symbols, tofind)
fileLine := resolver.debuglineInfo.Find(tofind)
if fileLine == nil {
return &Resolved{
Raw: tofind_aslr,
Base: tofind,
Symbol: symbol,
File: "",
Line: 0,
}, nil
}
return &Resolved{
Raw: tofind_aslr,
Base: tofind,
Symbol: symbol,
File: fileLine.File,
Line: fileLine.Line,
}, nil
}
func NewResolver(mc *MachoContext, loadaddr uint64, addresses []string) *Resolver {
symbols := mc.CollectSymbols()
debuglineInfo := mc.DebugLineInfo()
imagebase := TryGetImageBase(mc, symbols)
aslr := loadaddr - imagebase
return &Resolver{
symbols,
debuglineInfo,
aslr,
addresses,
}
}

View File

@ -0,0 +1,190 @@
package wrapper
import (
"fmt"
"io/ioutil"
"os"
"github.com/alecthomas/kong"
log "github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
"ios-wrapper/internal/wrapper/addr2line"
. "ios-wrapper/internal/wrapper/ofile"
"ios-wrapper/pkg/ios/fat"
"ios-wrapper/pkg/protomodel"
)
const envLogLevel = "LOG_LEVEL"
const defaultLogLevel = log.InfoLevel
func getLogLevel() log.Level {
levelString, exists := os.LookupEnv(envLogLevel)
if !exists {
return defaultLogLevel
}
level, err := log.ParseLevel(levelString)
if err != nil {
return defaultLogLevel
}
return level
}
func Cli() {
var cli Argument
ctx := kong.Parse(&cli)
command := ctx.Selected().Name
log.SetLevel(getLogLevel())
if command == "info" {
arg := cli.Info
printOFile(arg.OFile)
return
} else if command == "signed-bcell" {
arg := cli.SignedBcell
makeSignedBcell(arg.Out, arg.Bcell)
return
} else if command == "display-bcell" {
arg := cli.DisplayBcell
displayBcell(arg.Bcell)
return
} else if command == "addr2line" {
arg := cli.Addr2Line
resolveAddresses(arg.Dwarf, arg.Load, arg.Addresses)
return
} else if command == "lipo split" {
arg := cli.Lipo.Split
fat.FatSplit(arg.Fat)
return
} else if command == "lipo join" {
arg := cli.Lipo.Join
fat.FatJoin(arg.Macho, arg.Out)
return
}
var pc ProgramContext
var ofile OFile = nil
switch command {
case "wrap":
arg := cli.Wrap
ofile = NewOFile(arg.OFile)
pc.remove_codesign = true
pc.strip_init_pointers = true
pc.dylib_to_add = []string{"@rpath/bcell.framework/bcell"}
pc.rpath_to_add = []string{"@executable_path/Frameworks"}
pc.outfile = arg.Out
pc.bcellfile = arg.Bcell
pc.signed = arg.Signed
pc.ReadUserConfig(arg.Config)
case "vltk":
arg := cli.Vltk
ofile = NewOFile(arg.OFile)
pc.remove_codesign = true
pc.dylib_to_add = []string{"@rpath/GTJetModule.framework/GTJetModule"}
pc.outfile = arg.Out
case "remove-codesign", "remove-signature":
arg := cli.RemoveCodesign
ofile = NewOFile(arg.OFile)
pc.remove_codesign = true
pc.outfile = arg.Out
default:
return
}
pc.Process(ofile)
ofile.Cleanup()
}
func printOFile(ofile string) {
f, _ := os.OpenFile(ofile, os.O_RDWR, 0644)
printer := InfoPrinterFromFile(f)
printer.Print()
}
func makeSignedBcell(outfile string, infile string) {
raw_data, err := ioutil.ReadFile(infile)
if err != nil {
log.Panic("Cannot read bcell.dat")
}
data := &protomodel.BcellFile{}
err = proto.Unmarshal(raw_data, data)
if err != nil {
log.Panic("Invalid bcell.dat")
}
blocks := []*protomodel.Block{
{
Key: 0,
Value: raw_data,
},
}
signed_data := protomodel.SignedData{
Blocks: blocks,
}
signed_data_b, err := proto.Marshal(&signed_data)
err = ioutil.WriteFile(outfile, signed_data_b, 0644)
if err != nil {
log.Panic("Cannot write SignedData to file")
}
}
func displayBcell(bfile string) {
raw_data, err := ioutil.ReadFile(bfile)
if err != nil {
log.Panic("Invalid Protobuf<BcellFile> bcell.dat (1)")
}
data := &protomodel.BcellFile{}
err = proto.Unmarshal(raw_data, data)
if err != nil {
log.Panic("Invalid Protobuf<BcellFile> bcell.dat (2)")
}
fmt.Printf("[+] User Config: %+v\n", data.BcellConfig)
for arch, info := range data.MachoInfos {
fmt.Printf("[+] Arch %s:\n", arch)
fmt.Printf(" | PointerSize : %+v\n", info.PointerSize)
fmt.Printf(" | Image Base : 0x%x\n", info.ImageBase)
fmt.Printf(" | Init Pointers:\n")
for _, init_ptr := range info.InitPointers {
fmt.Printf(
" | offset 0x%x => addr 0x%x\n",
init_ptr.Offset,
init_ptr.Value,
)
}
}
}
func resolveAddresses(dwarf string, load string, addresses []string) {
mf, valid_macho := NewOFile(dwarf).(*MachoFile)
if !valid_macho {
log.Error("Input DWARF file is not a valid Macho binary")
return
}
loadaddr, err := addr2line.ParseAddressString(load)
if err != nil {
log.Error("Load address is not a valid hex number (%s)", load)
return
}
resolver := addr2line.NewResolver(mf.Context(), loadaddr, addresses)
for ; ; resolver.HasNext() {
resolved, err := resolver.Next()
if err != nil {
fmt.Printf("[?] Error: %s\n", err)
} else if resolved.Valid() {
fmt.Printf("[+] Found 0x%x => 0x%x %s %s:%d\n",
resolved.Raw, resolved.Base, resolved.Symbol, resolved.File, resolved.Line)
} else {
fmt.Printf("[-] Not found 0x%x\n", resolved.Raw)
}
}
}

View File

@ -0,0 +1,66 @@
package wrapper
type WrapArgument struct {
Config string `short:"c" required help:"User config.json" type:"existingfile"`
Out string `short:"o" required name:"outfile" help:"Modified Mach-O/Fat binary output file" type:"path"`
Bcell string `short:"b" required help:"bcell.dat output file" type:"path"`
Signed bool `short:"s" required help:"Output Protobuf<SignedData> for bcell.dat"`
OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"`
}
type VltkArgument struct {
Out string `short:"o" required name:"outfile" help:"Modified Mach-O/Fat binary output file" type:"path"`
OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"`
}
type InfoArgument struct {
OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"`
All bool `short:"a" help:"If print all information"`
Arch string `help:"Only print information in this arch"`
}
type RemoveCodesignArgument struct {
Out string `short:"o" required name:"outfile" help:"Modified Mach-O/Fat binary output file" type:"path"`
OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"`
}
type SignedBcellArgument struct {
Out string `short:"o" required name:"outfile" help:"bcell.dat.signed output file" type:"existingfile"`
Bcell string `arg short:"b" required help:"bcell.dat input file" type:"existingfile"`
}
type DisplayBcellArgument struct {
Bcell string `arg short:"b" required help:"Protobuf<BcellFile> bcell.dat input file" type:"existingfile"`
}
type Addr2LineArgument struct {
Load string `short:"l" required help:"Load address of binary"`
Dwarf string `short:"d" required help:"Path to Mach-O DWARF binary file" type:"existingfile"`
Addresses []string `arg help:"Addresses to resolve"`
}
type LipoArgument struct {
Join struct {
Out string `short:"o" required help:"Output Fat binary file" type:"path"`
Macho []string `arg help:"Macho binaries to join" type:"existingfile"`
} `cmd help:"Join Macho binaries into Fat binary"`
Split struct {
Fat string `arg help:"Fat binary to split" type:"existingfile"`
} `cmd help:"Split fat binary into files, named <name>_<arch>"`
}
type PepeArgument struct {
OFile string `arg help:"Path to Mach-O/Fat binary file" type:"existingfile"`
}
type Argument struct {
Wrap WrapArgument `cmd help:"Modifies Mach-O/Fat binary"`
Vltk VltkArgument `cmd help:"Modifies Mach-O/Fat binary"`
Info InfoArgument `cmd help:"Show Mach-O/Fat binary information"`
RemoveCodesign RemoveCodesignArgument `cmd aliases:"remove-signature" name:"remove-codesign" help:"Remove LC_CODE_SIGNATURE from Mach-O/Fat binary"`
SignedBcell SignedBcellArgument `cmd name:"signed-bcell" help:"Change Protobuf<BcellFile> into Protobuf<SignedData>"`
DisplayBcell DisplayBcellArgument `cmd name:"display-bcell" help:"Display Protobuf<BcellFile> content"`
Addr2Line Addr2LineArgument `cmd name:"addr2line" help:"Resolve crash address from DWARF file"`
Lipo LipoArgument `cmd help:"split Fat binary or join Mach-O binares"`
Pepe PepeArgument `cmd help:"split Fat binary or join Mach-O binares"`
}

View File

@ -0,0 +1,110 @@
package wrapper
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"os"
. "ios-wrapper/pkg/ios"
. "ios-wrapper/pkg/ios/fat"
. "ios-wrapper/pkg/ios/macho"
)
type InfoPrinter struct {
contexts []*MachoContext
}
func (printer *InfoPrinter) Print() {
for _, mc := range printer.contexts {
fmt.Printf("Mach-O arch: %s\n", mc.ArchName())
for i, cmd := range mc.Commands() {
fmt.Printf("%d: %s\n", i, cmd.Cmdname())
}
fmt.Printf("Image Base: 0x%x\n", mc.ImageBase())
init_funcs := mc.InitFunctions()
if len(init_funcs) == 0 {
fmt.Println("No Init functions")
}
for _, fun := range init_funcs {
fmt.Printf("Init functions at offset %s\n", &fun)
}
symbols := mc.CollectLazyBindSymbols()
if len(symbols) > 0 {
fmt.Println("Lazy Symbols")
for _, sym := range symbols {
fmt.Printf(
"%s\n\tStub=0x%x Address=0x%x\n\tDylib=%s\n",
sym.Name(),
sym.Stub(),
sym.Address(),
sym.Dylib(),
)
}
} else {
fmt.Println("No lazy symbols")
}
fmt.Println("======")
}
}
func machoPrinter(file *os.File) *InfoPrinter {
var mc MachoContext
err := mc.ParseFile(file, 0)
if err != nil {
return &InfoPrinter{
contexts: []*MachoContext{},
}
} else {
return &InfoPrinter{
contexts: []*MachoContext{&mc},
}
}
}
func fatPrinter(file *os.File) *InfoPrinter {
var fat FatContext
fat.ParseFile(file)
var contexts []*MachoContext
for _, m := range fat.Machos() {
buf := make([]byte, m.Size)
file.Seek(int64(m.Offset), io.SeekStart)
file.Read(buf)
var mc MachoContext
mc.ParseBuffer(buf)
contexts = append(contexts, &mc)
}
return &InfoPrinter{
contexts,
}
}
func InfoPrinterFromFile(file *os.File) *InfoPrinter {
var magic uint32
magic_buf := []byte{0, 0, 0, 0}
file.Read(magic_buf)
magic_r := bytes.NewReader(magic_buf)
binary.Read(magic_r, binary.LittleEndian, &magic)
file.Seek(0, io.SeekStart)
if magic == MagicFat || magic == CigamFat {
return fatPrinter(file)
}
if magic == Magic32 || magic == Magic64 || magic == Cigam32 ||
magic == Cigam64 {
return machoPrinter(file)
}
return &InfoPrinter{}
}

View File

@ -0,0 +1,58 @@
package ofile
import (
"io/ioutil"
"os"
log "github.com/sirupsen/logrus"
"ios-wrapper/pkg/ios/fat"
)
type FatFile struct {
machos []*MachoFile
files []string
tmp_file string
}
func (ff *FatFile) Machos() []*MachoFile {
return ff.machos
}
func (ff *FatFile) Cleanup() {
for _, macho := range ff.machos {
macho.Cleanup()
}
os.Remove(ff.tmp_file)
}
func NewFatFile(f string) *FatFile {
// create a tmp working file
tmp, _ := ioutil.TempFile("", "bcell_*")
data, _ := ioutil.ReadFile(f)
ioutil.WriteFile(tmp.Name(), data, 0644)
var machos []*MachoFile
var files []string
splitted_files, err := fat.FatSplit(tmp.Name())
if err != nil {
log.WithFields(log.Fields{
"err": err,
"splitted_ok": splitted_files,
}).Panic("Cannot split Fat binary")
}
for _, splitted_file := range splitted_files {
macho := NewMachoFile(splitted_file)
machos = append(machos, macho)
files = append(files, macho.tmp_file)
// NewMachoFile creates another tmp file, remove this temp splitted file
os.Remove(splitted_file)
}
return &FatFile{
machos: machos,
tmp_file: tmp.Name(),
files: files,
}
}

View File

@ -0,0 +1,56 @@
package ofile
import (
"io/ioutil"
"os"
// log "github.com/sirupsen/logrus"
"ios-wrapper/pkg/ios/macho"
"ios-wrapper/pkg/protomodel"
)
type MachoFile struct {
mc *macho.MachoContext
tmp_file string
info *protomodel.MachoInfo
}
func (mf *MachoFile) Context() *macho.MachoContext {
return mf.mc
}
func (mf *MachoFile) Info() *protomodel.MachoInfo {
return mf.info
}
func (mf *MachoFile) TmpFile() string {
return mf.tmp_file
}
func (mf *MachoFile) Cleanup() {
os.Remove(mf.tmp_file)
}
func NewMachoFile(f string) *MachoFile {
// create a tmp working file
tmp, _ := ioutil.TempFile("", "bcell_*")
data, _ := ioutil.ReadFile(f)
ioutil.WriteFile(tmp.Name(), data, 0644)
var mc macho.MachoContext
tmp, _ = os.OpenFile(tmp.Name(), os.O_RDWR, 0644)
mc.ParseFile(tmp, 0)
return &MachoFile{
mc: &mc,
tmp_file: tmp.Name(),
info: &protomodel.MachoInfo{
PointerSize: map[bool]protomodel.MachoInfo_PointerSize{
false: protomodel.MachoInfo_p32,
true: protomodel.MachoInfo_p64,
}[mc.Is64bit()],
ImageBase: mc.ImageBase(),
},
}
}

View File

@ -0,0 +1,47 @@
package ofile
import (
"bufio"
"bytes"
"encoding/binary"
"os"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
)
type OFile interface {
Cleanup()
}
func NewOFile(f string) OFile {
file, err := os.Open(f)
if err != nil {
return nil
}
r := bufio.NewReader(file)
var magic uint32
magic_buf, _ := r.Peek(4)
magic_r := bytes.NewReader(magic_buf)
binary.Read(magic_r, binary.LittleEndian, &magic)
if magic != Magic32 && magic != Magic64 && magic != Cigam32 && magic != Cigam64 && magic != MagicFat &&
magic != CigamFat {
log.Panic("Magic does not match %x", magic)
return nil
}
if magic == Magic32 || magic == Magic64 || magic == Cigam32 ||
magic == Cigam64 {
return NewMachoFile(f)
}
if magic == MagicFat || magic == CigamFat {
return NewFatFile(f)
}
// not likely
return nil
}

View File

@ -0,0 +1,110 @@
package wrapper
import (
"encoding/json"
"io/ioutil"
log "github.com/sirupsen/logrus"
. "ios-wrapper/internal/wrapper/action"
. "ios-wrapper/internal/wrapper/ofile"
"ios-wrapper/pkg/protomodel"
)
type UserConfig struct {
Repack bool `json:"repack"`
Hook bool `json:"hook"`
Emulator bool `json:"emulator"`
Debug bool `json:"debug"`
Root bool `json:"root"`
ServerURL string `json:"server_url"`
ReportType int32 `json:"report_type"`
RemoteConfigURL string `json:"remote_config_url"`
DomainID string `json:"domain_id"`
}
func (uc *UserConfig) Protomodel() *protomodel.Config {
return &protomodel.Config{
Hook: uc.Hook,
Repack: uc.Repack,
Emulator: uc.Emulator,
Debug: uc.Debug,
Root: uc.Root,
ServerUrl: uc.ServerURL,
ReportType: uc.ReportType,
RemoteConfigUrl: uc.RemoteConfigURL,
DomainId: uc.DomainID,
}
}
type ProgramContext struct {
strip_init_pointers bool
remove_codesign bool
dylib_to_add []string
rpath_to_add []string
outfile string
actions []Action
err error
user_config UserConfig
bcellfile string
signed bool // true: Protobuf<SignedData>; false: Protobuf<BcellFile>
}
func (pc *ProgramContext) ExplainError(err error) {
log.WithFields(log.Fields{
"pc error": pc.err,
"ofile error": err,
}).Error("OFile Process gone wrong")
}
func (pc *ProgramContext) ReadUserConfig(f string) {
json_data, err := ioutil.ReadFile(f)
if err != nil {
log.Panic("User Config file is invalid")
}
json.Unmarshal(json_data, &pc.user_config)
}
func (pc *ProgramContext) AddAction(action Action) {
pc.actions = append(pc.actions, action)
}
func (pc *ProgramContext) dispatchActions(ofile OFile) {
for _, action := range pc.actions {
err := RunAction(action, ofile)
if err != nil {
pc.err = err
break
}
}
}
func (pc *ProgramContext) Process(ofile OFile) {
if pc.remove_codesign {
pc.AddAction(NewRemoveCodeSignatureAction())
}
if pc.strip_init_pointers {
pc.AddAction(NewSaveInitPointerAction())
pc.AddAction(NewRemoveInitPointerAction())
}
ExperimentalFeature("Remove Unnecessary Info", func() {
pc.AddAction(NewRemoveUnnecessaryInfoAction())
})
ExperimentalFeature("Remove Classic Symbol", func() {
pc.AddAction(NewRemoveClassicSymbolAction())
})
pc.AddAction(NewAddRpathAction(pc.rpath_to_add))
pc.AddAction(NewAddDylibAction(pc.dylib_to_add))
if pc.bcellfile != "" {
pc.AddAction(NewSaveBcellFileAction(pc.user_config.Protomodel(), pc.bcellfile, pc.signed))
} else {
log.Warn("bcellfile is not set, no output bcellfile")
}
pc.AddAction(NewWriteFileAction(pc.outfile))
pc.dispatchActions(ofile)
}

View File

@ -0,0 +1,30 @@
package wrapper
import (
"fmt"
"os"
log "github.com/sirupsen/logrus"
)
func FileExist(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func CheckFileExistence(filename string) {
if !FileExist(filename) {
fmt.Printf("File %s does not exists\n", filename)
os.Exit(1)
}
}
func ExperimentalFeature(feature string, foo func()) {
if os.Getenv("EXPERIMENTAL") == "1" {
log.Info(fmt.Sprintf("Running Experimental Feature %s", feature))
foo()
}
}

305
macho-go/pkg/dwarf/dwarf.go Normal file
View File

@ -0,0 +1,305 @@
package dwarf
import (
"bytes"
"encoding/binary"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/leb128"
)
type header struct {
unit_length uint32
version uint16
header_length uint32
minimum_instruction_length uint8
maximum_operations_per_instruction uint8
default_is_stmt uint8
line_base int8
line_range uint8
opcode_base uint8
}
type register struct {
address uint64
op_index uint32
file uint32
line uint32
column uint32
is_stmt bool
basic_block bool
end_sequence bool
prologue_end bool
epilogue_begin bool
isa uint32
discriminator uint32
}
func initRegister(is_stmt bool) register {
var reg register
reg.is_stmt = is_stmt
reg.file = 1
reg.line = 1
return reg
}
func (reg *register) reset() {
reg.basic_block = false
reg.prologue_end = false
reg.epilogue_begin = false
reg.discriminator = 0
}
// Opcodes
const (
Copy uint8 = 1
AdvancePc uint8 = 2
AdvanceLine uint8 = 3
SetFile uint8 = 4
SetColumn uint8 = 5
NegateStmt uint8 = 6
SetBasicBlock uint8 = 7
ConstAddPc uint8 = 8
FixedAdvancePc uint8 = 9
SetPrologueEnd uint8 = 10
SetEpiloqueBegin uint8 = 11
SetIsa uint8 = 12
// Extended opcodes
EndSequence uint8 = 1
SetAddress uint8 = 2
DefineFile uint8 = 3
SetDiscriminator uint8 = 4
)
type row struct {
address uint64
op_index uint32
directory string
file string
line int32
column int32
end_sequence bool
}
type filename struct {
name string
dir uint
}
type DebuglineParser struct {
buf *bytes.Buffer
byteorder binary.ByteOrder
header header
standard_opcode_lengths []uint8
include_directories []string
file_names []filename
row []row
}
func (parser *DebuglineParser) Find(address uint64) (bool, uint32, string, string) {
for _, row := range parser.row {
if address == row.address {
return true, uint32(row.line), row.directory, row.file
}
}
return false, 0, "", ""
}
func (parser *DebuglineParser) Parse(buf *bytes.Buffer, byteorder binary.ByteOrder) {
parser.buf = buf
parser.byteorder = byteorder
parser.parseHeader()
parser.parseOpcode()
parser.parseDirectoryTable()
parser.parseFilenameTable()
parser.parseStatementProgram()
}
func (parser *DebuglineParser) parseHeader() {
binary.Read(parser.buf, parser.byteorder, &parser.header.unit_length)
binary.Read(parser.buf, parser.byteorder, &parser.header.version)
binary.Read(parser.buf, parser.byteorder, &parser.header.header_length)
binary.Read(parser.buf, parser.byteorder, &parser.header.minimum_instruction_length)
if parser.header.version >= 4 {
binary.Read(parser.buf, parser.byteorder, &parser.header.maximum_operations_per_instruction)
}
binary.Read(parser.buf, parser.byteorder, &parser.header.default_is_stmt)
binary.Read(parser.buf, parser.byteorder, &parser.header.line_base)
binary.Read(parser.buf, parser.byteorder, &parser.header.line_range)
binary.Read(parser.buf, parser.byteorder, &parser.header.opcode_base)
}
func (parser *DebuglineParser) parseOpcode() {
var standard_opcode_lengths []uint8
for i := uint8(1); i < parser.header.opcode_base; i++ {
var num uint8
binary.Read(parser.buf, parser.byteorder, &num)
standard_opcode_lengths = append(standard_opcode_lengths, num)
}
parser.standard_opcode_lengths = standard_opcode_lengths
}
func (parser *DebuglineParser) parseDirectoryTable() {
var include_directories []string
for {
dir, _ := parser.buf.ReadString(0)
if dir == "\x00" {
break
}
include_directories = append(include_directories, dir)
}
parser.include_directories = include_directories
}
func (parser *DebuglineParser) parseFilenameTable() {
var file_names []filename
for {
file, _ := parser.buf.ReadString(0)
if file == "\x00" {
break
}
dir, _ := ReadULEB(parser.buf) // directory index
ReadULEB(parser.buf) // time
ReadULEB(parser.buf) // length
file_names = append(file_names, filename{
name: file,
dir: dir,
})
}
parser.file_names = file_names
}
func (parser *DebuglineParser) parseStatementProgram() {
reg := initRegister(parser.header.default_is_stmt == 1)
runloop:
for {
var opcode uint8
binary.Read(parser.buf, parser.byteorder, &opcode)
if opcode >= parser.header.opcode_base {
adjusted_opcode := opcode - parser.header.opcode_base
operation_advance := adjusted_opcode / parser.header.line_range
increment_address_and_op_index(&reg, &parser.header, uint(operation_advance))
reg.line += uint32(int32(parser.header.line_base) + int32(adjusted_opcode%parser.header.line_range))
parser.register_to_matrix(&reg)
reg.reset()
} else if opcode == 0 {
size_, _ := ReadULEB(parser.buf)
size := size_ - 1
var extended_opcode uint8
binary.Read(parser.buf, parser.byteorder, &extended_opcode)
switch extended_opcode {
case EndSequence:
reg.end_sequence = true
parser.register_to_matrix(&reg)
reg = initRegister(parser.header.default_is_stmt == 1)
break runloop
case SetAddress:
if size == 8 {
binary.Read(parser.buf, parser.byteorder, &reg.address)
} else if size == 4 {
var v uint32
binary.Read(parser.buf, parser.byteorder, &v)
reg.address = uint64(v)
} else {
parser.buf.Next(int(size))
}
reg.op_index = 0
case SetDiscriminator:
v, _ := ReadULEB(parser.buf)
reg.discriminator = uint32(v)
default:
parser.buf.Next(int(size))
}
} else {
switch opcode {
case Copy:
parser.register_to_matrix(&reg)
reg.reset()
case AdvancePc:
v, _ := ReadULEB(parser.buf)
increment_address_and_op_index(&reg, &parser.header, v)
case AdvanceLine:
v, _ := ReadSLEB(parser.buf)
if v < 0 {
reg.line -= uint32(-v)
} else {
reg.line += uint32(v)
}
case SetFile:
v, _ := ReadULEB(parser.buf)
reg.file = uint32(v)
case SetColumn:
v, _ := ReadULEB(parser.buf)
reg.column = uint32(v)
case NegateStmt:
reg.is_stmt = !reg.is_stmt
case SetBasicBlock:
reg.basic_block = true
case ConstAddPc:
adjusted_opcode := 255 - parser.header.opcode_base
operation_advance := adjusted_opcode / parser.header.line_range
increment_address_and_op_index(&reg, &parser.header, uint(operation_advance))
case FixedAdvancePc:
var v uint16
binary.Read(parser.buf, parser.byteorder, &v)
reg.address += uint64(v)
reg.op_index = 0
case SetPrologueEnd:
reg.prologue_end = true
case SetEpiloqueBegin:
reg.epilogue_begin = true
case SetIsa:
v, _ := ReadULEB(parser.buf)
reg.isa = uint32(v)
default:
break
}
}
}
}
func increment_address_and_op_index(reg *register, h *header, operation_advance uint) {
min_length := uint64(h.minimum_instruction_length)
max_op := uint64(h.maximum_operations_per_instruction)
advance := uint64(operation_advance)
op_index := uint64(reg.op_index)
if h.maximum_operations_per_instruction == 1 {
reg.address += advance * min_length
} else {
reg.address += min_length * ((op_index + advance) / max_op)
reg.op_index = uint32((op_index + advance) % max_op)
}
}
func (parser *DebuglineParser) register_to_matrix(reg *register) {
if !(reg.is_stmt || (reg.line > 0 && reg.column > 0)) {
return
}
file := parser.file_names[reg.file-1]
path := parser.include_directories[file.dir-1]
row := row{
address: reg.address,
op_index: reg.op_index,
directory: path,
file: file.name,
line: int32(reg.line),
column: int32(reg.column),
end_sequence: reg.end_sequence,
}
parser.row = append(parser.row, row)
log.WithFields(log.Fields{
"address": row.address,
"directory": row.directory,
"file": row.file,
"line": row.line,
}).Trace("Debugline Row")
}

159
macho-go/pkg/ios/const.go Normal file
View File

@ -0,0 +1,159 @@
package ios
// Magic values of Mach-O and Fat binaries
const (
Magic32 uint32 = 0xfeedface
Cigam32 uint32 = 0xcefaedfe
Magic64 uint32 = 0xfeedfacf
Cigam64 uint32 = 0xcffaedfe
MagicFat uint32 = 0xcafebabe
CigamFat uint32 = 0xbebafeca
)
// Flags defined in mach_header.flags
const (
FlagNoUndefs uint32 = 0x1
FlagIncrLink uint32 = 0x2
FlagDyldLink uint32 = 0x4
FlagBindAtLoad uint32 = 0x8
FlagPrebound uint32 = 0x10
FlagSplitSegs uint32 = 0x20
FlagLazyInit uint32 = 0x40
FlagTwoLevel uint32 = 0x80
FlagForceFlat uint32 = 0x100
FlagNoMultiDefs uint32 = 0x200
FlagNoFixPrebinding uint32 = 0x400
FlagPrebindable uint32 = 0x800
FlagAllModsBound uint32 = 0x1000
FlagSubsectionsViaSymbols uint32 = 0x2000
FlagCanonical uint32 = 0x4000
FlagWeakDefines uint32 = 0x8000
FlagBindsToWeak uint32 = 0x10000
FlagAllowStackExecution uint32 = 0x20000
FlagRootSafe uint32 = 0x40000
FlagSetuidSafe uint32 = 0x80000
FlagNoReexportedDylibs uint32 = 0x100000
FlagPIE uint32 = 0x200000
FlagDeadStrippableDylib uint32 = 0x400000
FlagHasTLVDescriptors uint32 = 0x800000
FlagNoHeapExecution uint32 = 0x1000000
FlagAppExtensionSafe uint32 = 0x2000000
)
// Types of Load command
const (
LC_DUMMY uint32 = 0x0
LC_SEGMENT uint32 = 0x1
LC_SYMTAB uint32 = 0x2
LC_SYMSEG uint32 = 0x3
LC_THREAD uint32 = 0x4
LC_UNIXTHREAD uint32 = 0x5
LC_LOADFVMLIB uint32 = 0x6
LC_IDFVMLIB uint32 = 0x7
LC_IDENT uint32 = 0x8
LC_FVMFILE uint32 = 0x9
LC_PREPAGE uint32 = 0xa
LC_DYSYMTAB uint32 = 0xb
LC_LOAD_DYLIB uint32 = 0xc
LC_ID_DYLIB uint32 = 0xd
LC_LOAD_DYLINKER uint32 = 0xe
LC_ID_DYLINKER uint32 = 0xf
LC_PREBOUND_DYLIB uint32 = 0x10
LC_ROUTINES uint32 = 0x11
LC_SUB_FRAMEWORK uint32 = 0x12
LC_SUB_UMBRELLA uint32 = 0x13
LC_SUB_CLIENT uint32 = 0x14
LC_SUB_LIBRARY uint32 = 0x15
LC_TWOLEVEL_HINTS uint32 = 0x16
LC_PREBIND_CKSUM uint32 = 0x17
LC_LOAD_WEAK_DYLIB uint32 = 0x18 | 0x80000000
LC_SEGMENT_64 uint32 = 0x19
LC_ROUTINES_64 uint32 = 0x1a
LC_UUID uint32 = 0x1b
LC_RPATH uint32 = 0x1c | 0x80000000
LC_CODE_SIGNATURE uint32 = 0x1d
LC_SEGMENT_SPLIT_INFO uint32 = 0x1e
LC_REEXPORT_DYLIB uint32 = 0x1f | 0x80000000
LC_LAZY_LOAD_DYLIB uint32 = 0x20
LC_ENCRYPTION_INFO uint32 = 0x21
LC_DYLD_INFO uint32 = 0x22
LC_DYLD_INFO_ONLY uint32 = 0x22 | 0x80000000
LC_VERSION_MIN_MACOSX uint32 = 0x24
LC_VERSION_MIN_IPHONEOS uint32 = 0x25
LC_FUNCTION_STARTS uint32 = 0x26
LC_DYLD_ENVIRONMENT uint32 = 0x27
LC_MAIN uint32 = 0x28 | 0x80000000
LC_DATA_IN_CODE uint32 = 0x29
LC_SOURCE_VERSION uint32 = 0x2A
LC_DYLIB_CODE_SIGN_DRS uint32 = 0x2B
LC_ENCRYPTION_INFO_64 uint32 = 0x2C
LC_LINKER_OPTION uint32 = 0x2D
LC_LINKER_OPTIMIZATION_HINT uint32 = 0x2E
LC_VERSION_MIN_TVOS uint32 = 0x2F
LC_VERSION_MIN_WATCHOS uint32 = 0x30
LC_BUILD_VERSION uint32 = 0x32
LC_DYLD_EXPORTS_TRIE uint32 = 0x33 | 0x80000000
LC_DYLD_CHAINED_FIXUPS uint32 = 0x34 | 0x80000000
)
const (
Header_size uint64 = 28
Header_size_64 uint64 = 28 + 4
)
const (
CPU_ARCH_ABI64 uint32 = 0x0100_0000
CPU_TYPE_ARM uint32 = 12
CPU_TYPE_ARM64 uint32 = CPU_TYPE_ARM | CPU_ARCH_ABI64
CPU_TYPE_POWERPC uint32 = 18
CPU_TYPE_POWERPC64 uint32 = CPU_TYPE_POWERPC | CPU_ARCH_ABI64
CPU_TYPE_I386 uint32 = 7
CPU_TYPE_X86_64 uint32 = CPU_TYPE_I386 | CPU_ARCH_ABI64
)
const (
// Section Type
S_REGULAR uint8 = 0x0
S_ZEROFILL uint8 = 0x1
S_CSTRING_LITERALS uint8 = 0x2
S_4BYTE_LITERALS uint8 = 0x3
S_8BYTE_LITERALS uint8 = 0x4
S_LITERAL_POINTERS uint8 = 0x5
S_NON_LAZY_SYMBOL_POINTERS uint8 = 0x6
S_LAZY_SYMBOL_POINTERS uint8 = 0x7
S_SYMBOL_STUBS uint8 = 0x8
S_MOD_INIT_FUNC_POINTERS uint8 = 0x9
S_MOD_TERM_FUNC_POINTERS uint8 = 0xa
S_COALESCED uint8 = 0xb
S_GB_ZEROFILL uint8 = 0xc
S_INTERPOSING uint8 = 0xd
S_16BYTE_LITERALS uint8 = 0xe
S_DTRACE_DOF uint8 = 0xf
S_LAZY_DYLIB_SYMBOL_POINTERS uint8 = 0x10
S_THREAD_LOCAL_REGULAR uint8 = 0x11
S_THREAD_LOCAL_ZEROFILL uint8 = 0x12
S_THREAD_LOCAL_VARIABLES uint8 = 0x13
S_THREAD_LOCAL_VARIABLE_POINTERS uint8 = 0x14
S_THREAD_LOCAL_INIT_FUNCTION_POINTERS uint8 = 0x15
)
const (
// Section Attribute
)
const (
BIND_OPCODE_DONE uint8 = 0x00
BIND_OPCODE_SET_DYLIB_ORDINAL_IMM uint8 = 0x10
BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB uint8 = 0x20
BIND_OPCODE_SET_DYLIB_SPECIAL_IMM uint8 = 0x30
BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM uint8 = 0x40
BIND_OPCODE_SET_TYPE_IMM uint8 = 0x50
BIND_OPCODE_SET_ADDEND_SLEB uint8 = 0x60
BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB uint8 = 0x70
BIND_OPCODE_ADD_ADDR_ULEB uint8 = 0x80
BIND_OPCODE_DO_BIND uint8 = 0x90
BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB uint8 = 0xA0
BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED uint8 = 0xB0
BIND_OPCODE_DO_BIND_ADD_ULEB_TIMES_SKIPPING_ULEB uint8 = 0xC0
)

View File

@ -0,0 +1,184 @@
/// Contains the declaration of FatHeader and FatArch
/// These structs are always written using Big-Endian,
/// as documented in the mach-o/fat.h
/// This file also has a CreateFat function to generate
/// Fat file from a list of MachoContext
package fat
import (
"io"
"os"
"sort"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
macho "ios-wrapper/pkg/ios/macho"
)
/// Get the alignment for the Mach-O in Fat binary
/// The returned value is the multiplier of 2
func GetAlignment(h *macho.Header) uint32 {
switch h.Cputype() {
case CPU_TYPE_ARM, CPU_TYPE_ARM64:
return 0xe // log2(0x4000)
case CPU_TYPE_POWERPC,
CPU_TYPE_POWERPC64,
CPU_TYPE_I386,
CPU_TYPE_X86_64:
return 0xc // log2(0x1000)
default:
return 0xd // log2(0x2000)
}
}
func MachosToFatArchs(machos []*macho.MachoContext) []*FatArch {
var fa []*FatArch
for _, m := range machos {
fa = append(fa, &FatArch{
Cputype: m.Header().Cputype(),
Cpusubtype: m.Header().Cpusubtype(),
Size: m.FileSize(),
Offset: 0,
Align: GetAlignment(m.Header()),
})
}
return fa
}
/// Create a Fat binary from MachoContext
/// Convert MachoContext to FatArch
/// Calculate the alignment, now using basic calculation
/// because iOS always has valid archs ARM
///
/// Sort the Fat arch as per the cctools/lipo
/// Calculate the offset with each Mach-O
///
/// Write the FatHeader
/// Write the FatArchs
/// Write the Mach-Os
func CreateFat(machos []*macho.MachoContext, outfilename string) error {
archs := MachosToFatArchs(machos)
sort.SliceStable(archs, func(i, j int) bool {
if archs[i].Cputype == archs[j].Cputype {
return archs[i].Cpusubtype < archs[i].Cpusubtype
}
if archs[i].Cputype == CPU_TYPE_ARM64 {
return false
}
if archs[j].Cputype == CPU_TYPE_ARM64 {
return true
}
return archs[i].Cpusubtype < archs[j].Cpusubtype
})
// calculate the offset for each FatArch
offset := uint32(8) // size of fat header, bytes
// offset to the first macho
offset += uint32(len(archs)) * 5 // size of fat_arch, bytes
for _, arch := range archs {
offset = uint32(OffsetRounder(uint64(offset), 1<<arch.Align))
arch.Offset = offset
offset += arch.Size
log.WithFields(log.Fields{
"cputype": arch.Cputype,
"cpusubtype": arch.Cpusubtype,
"size": arch.Size,
"offset": arch.Offset,
"algin": arch.Align,
}).Debug("Arch to add")
}
file, err := os.OpenFile(outfilename, os.O_CREATE|os.O_WRONLY, 0777)
if err != nil {
return err
// log.Fatal("Cannot open output file")
}
var w fatWriter
w.WriteFatHeader(file, &FatHeader{
Magic: MagicFat,
Nfat_arch: uint32(len(archs)),
})
w.WriteFatArchs(file, archs)
w.WriteMachos(file, archs, machos)
return w.err
}
type fatWriter struct {
err error
}
func (fw *fatWriter) WriteFatHeader(w io.Writer, h *FatHeader) {
if fw.err != nil {
return
}
b := h.Serialize()
_, err := w.Write(b)
fw.err = err
}
func (fw *fatWriter) WriteFatArchs(w io.Writer, archs []*FatArch) {
for _, arch := range archs {
if fw.err != nil {
return
}
b := arch.Serialize()
_, err := w.Write(b)
fw.err = err
log.WithFields(log.Fields{
"cputype": arch.Cputype,
"cpusubtype": arch.Cpusubtype,
"size": arch.Size,
"offset": arch.Offset,
"algin": arch.Align,
}).Info("Attempt to write Fat Arch")
}
}
/// for each fat arch sorted, we locate the MachoContext and
/// use it to Write to the buffer
/// locating the macho by its cputype and cpusubtype
func (fw *fatWriter) WriteMachos(
w io.WriteSeeker,
archs []*FatArch,
machos []*macho.MachoContext,
) {
for _, arch := range archs {
for _, macho := range machos {
if fw.err != nil {
return
}
cputype := macho.Header().Cputype()
cpusubtype := macho.Header().Cpusubtype()
if cputype == arch.Cputype &&
cpusubtype == arch.Cpusubtype {
log.WithFields(log.Fields{
"cputype": cputype,
"cpusubtype": cpusubtype,
"offset": arch.Offset,
"size": arch.Size,
}).Info("Attempt to write Mach-O to file")
_, err1 := w.Seek(int64(arch.Offset), io.SeekStart)
_, err2 := macho.WriteBufferTo(w)
if err1 != nil {
fw.err = err1
} else if err2 != nil {
fw.err = err2
}
}
}
}
}
func OffsetRounder(v, r uint64) uint64 {
r--
v += r
v &= uint64(^int64(r))
return v
}

110
macho-go/pkg/ios/fat/fat.go Normal file
View File

@ -0,0 +1,110 @@
package fat
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"os"
. "ios-wrapper/pkg/ios"
"ios-wrapper/pkg/ios/macho"
)
type FatHeader struct {
Magic uint32
Nfat_arch uint32
}
func (h *FatHeader) Serialize() []byte {
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, h.Magic)
binary.Write(buf, binary.BigEndian, h.Nfat_arch)
return buf.Bytes()
}
type FatArch struct {
Cputype uint32
Cpusubtype uint32
Offset uint32
Size uint32
Align uint32
}
func (arch *FatArch) Serialize() []byte {
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, arch.Cputype)
binary.Write(buf, binary.BigEndian, arch.Cpusubtype)
binary.Write(buf, binary.BigEndian, arch.Offset)
binary.Write(buf, binary.BigEndian, arch.Size)
binary.Write(buf, binary.BigEndian, arch.Align)
return buf.Bytes()
}
// don't know when to use
// it seems that only use if the size of any file is > 4GB
// else just use FatArch
type FatArch64 struct {
Cputype uint32
Cpusubtype uint32
Offset uint64
Size uint64
Align uint32
}
type FatContext struct {
fatArch []*FatArch
}
func (fc *FatContext) ParseFile(file *os.File) {
r := bufio.NewReader(file)
{ // read magic to define byteorder and pointersize
var magic uint32
magic_buf := make([]byte, 4)
r.Read(magic_buf)
magic_r := bytes.NewReader(magic_buf)
binary.Read(magic_r, binary.BigEndian, &magic)
if magic != MagicFat {
return
}
}
var nfat_arch uint32
binary.Read(r, binary.BigEndian, &nfat_arch)
for i := uint32(0); i < nfat_arch; i++ {
var fat_arch FatArch
binary.Read(r, binary.BigEndian, &fat_arch.Cputype)
binary.Read(r, binary.BigEndian, &fat_arch.Cpusubtype)
binary.Read(r, binary.BigEndian, &fat_arch.Offset)
binary.Read(r, binary.BigEndian, &fat_arch.Size)
binary.Read(r, binary.BigEndian, &fat_arch.Align)
fc.fatArch = append(fc.fatArch, &fat_arch)
}
}
func (fc *FatContext) Machos() []*FatArch {
return fc.fatArch
}
func FatSplit(path string) ([]string, error) {
return CreateMachosFromFat(path)
}
// Parse files into Mach-O, calculate fat_arch and join
// into a Fat binary
// @paths: paths to Mach-O binaries
// @out: output Fat file path
// returns success?
func FatJoin(paths []string, out string) error {
machos, err := macho.MachosFromFiles(paths)
if err != nil {
return err
}
if macho.CheckDuplicateArch(machos) {
return errors.New("Duplicate Arch")
}
return CreateFat(machos, out)
}

View File

@ -0,0 +1,62 @@
package fat
import (
"fmt"
"io"
"os"
"strings"
)
// Split a Fat binary into multiple Mach-O binaries
// @ifile: path to Fat binary
// returns a list of file path of splited Mach-O binaries
func CreateMachosFromFat(ifile string) ([]string, error) {
f, err := os.OpenFile(ifile, os.O_RDONLY, 0644)
if err != nil {
fmt.Println("Cannot open file")
return []string{}, err
}
var fc FatContext
fc.ParseFile(f)
var r []string
for _, arch := range fc.Machos() {
offset := arch.Offset
size := arch.Size
buf := make([]byte, size)
filename := fmt.Sprintf(
"%s_%x",
strings.ReplaceAll(ifile, " ", "_"),
offset,
)
var err error
_, err = f.Seek(int64(offset), io.SeekStart)
if err != nil {
return r, err
}
_, err = f.Read(buf)
if err != nil {
return r, err
}
outfile, err := os.OpenFile(
filename,
os.O_CREATE|os.O_WRONLY,
0777,
)
if err != nil {
return r, err
}
_, err = outfile.Write(buf)
if err != nil {
return r, err
}
r = append(r, filename) // everything is find, append new file path
}
return r, nil
}

View File

@ -0,0 +1,55 @@
package macho
import (
"bytes"
"ios-wrapper/pkg/dwarf"
)
type DebuglineInfo struct {
debuglines []*dwarf.DebuglineParser
}
type LineInfo struct {
Line uint32
Directory string
File string
}
func (dinfo *DebuglineInfo) Find(address uint64) *LineInfo {
for _, debugline := range dinfo.debuglines {
found, line, directory, file := debugline.Find(address)
if found {
return &LineInfo{
line, directory, file,
}
}
}
return nil
}
// Parse the __debug_line into a list of DebugLine
//
// reference from Crystal dwarf parser
// https://github.com/crystal-lang/crystal/blob/421fa11bf05b11441b74bbeb2762b673f96fec3f/src/crystal/dwarf/line_numbers.cr
func (mc *MachoContext) DebugLineInfo() *DebuglineInfo {
debug_line_section := mc.FindSection("__debug_line")
if debug_line_section == nil {
return nil
}
start := uint64(debug_line_section.Offset())
end := start + debug_line_section.Size()
buf := bytes.NewBuffer(mc.buf[start:end])
debugLine := []*dwarf.DebuglineParser{}
for buf.Len() != 0 {
parser := &dwarf.DebuglineParser{}
parser.Parse(buf, mc.byteorder)
debugLine = append(debugLine, parser)
}
return &DebuglineInfo{
debugLine,
}
}

View File

@ -0,0 +1,237 @@
package macho
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
. "ios-wrapper/pkg/leb128"
)
type ImportSymbol struct {
name string
dylib string
segment uint32
segment_offset uint32
address uint64
file_address uint64
// push number
pnum uint32
stub uint64
}
func (sym *ImportSymbol) Name() string {
return sym.name
}
func (sym *ImportSymbol) Dylib() string {
return sym.dylib
}
func (sym *ImportSymbol) Address() uint64 {
return sym.address
}
func (sym *ImportSymbol) Pnum() uint32 {
return sym.pnum
}
func (sym *ImportSymbol) Stub() uint64 {
return sym.stub
}
func (mc *MachoContext) CollectLazyBindSymbols() []*ImportSymbol {
if mc.dyldinfo == nil {
return mc.CollectLazyBindSymbolsModern()
} else {
return mc.CollectLazyBindSymbolsLegacy()
}
}
// New convention using LC_DYLD_CHAINED_FIXUPS
func (mc *MachoContext) CollectLazyBindSymbolsModern() []*ImportSymbol {
var buf []byte
for _, cmd := range mc.Linkedits() {
if (cmd.Cmd() != LC_DYLD_CHAINED_FIXUPS) {
continue
}
count_offset := int64(cmd.Dataoff()) + 16
mc.file.WriteAt([]byte{0, 0, 0, 0}, count_offset)
symbol_offset := int64(0x4000)
mc.file.WriteAt([]byte{0, 0, 0, 0, 0, 0, 0, 0}, symbol_offset)
buf = mc.buf[cmd.Dataoff():cmd.Dataoff() + cmd.Datasize()]
break
}
r := bytes.NewReader(buf)
rr := bufio.NewReader(r)
var fixups_version int32
var starts_offset int32
var imports_offset int32
var symbols_offset int32
var imports_count int32
var imports_format int32
var symbols_format int32
binary.Read(rr, mc.byteorder, &fixups_version)
binary.Read(rr, mc.byteorder, &starts_offset)
binary.Read(rr, mc.byteorder, &imports_offset)
binary.Read(rr, mc.byteorder, &symbols_offset)
binary.Read(rr, mc.byteorder, &imports_count)
binary.Read(rr, mc.byteorder, &imports_format)
binary.Read(rr, mc.byteorder, &symbols_format)
fmt.Printf(
"HMMGE %x %x\n",
imports_offset,
imports_count,
)
return []*ImportSymbol{}
}
// Old convention using LC_DYLD_INFO_ONLY section and bytecode runner
func (mc *MachoContext) CollectLazyBindSymbolsLegacy() []*ImportSymbol {
start := mc.dyldinfo.lazy_bind_off
size := mc.dyldinfo.lazy_bind_size
end := start + size
if size == 0 {
return []*ImportSymbol{}
}
// clear this whole section to 0x00 BIND_OPCODE_DONE
dummy := []byte{
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
}
// make LINK EDIT section writable
// mc.file.WriteAt([]byte{0x03}, int64(0x3f8))
// set number of symbols to 0
mc.file.WriteAt([]byte{0, 0, 0, 0}, int64(0x444))
mc.file.WriteAt([]byte{0, 0, 0, 0}, int64(0x44c))
mc.file.WriteAt([]byte{0, 0, 0, 0}, int64(0x48c))
mc.file.WriteAt(dummy, int64(start))
buf := bytes.NewBuffer(mc.buf[start:end])
offset := uint(0)
var syms []*ImportSymbol
var sym ImportSymbol
// symoffset := offset
lastop_done := false
for offset < uint(size) {
d, _ := buf.ReadByte()
op := d & 0xf0
imm := d & 0x0f
log.WithFields(log.Fields{
"op": fmt.Sprintf("0x%x", op),
"imm": fmt.Sprintf("0x%x", imm),
}).Trace("Bytecode")
if op != BIND_OPCODE_DONE && lastop_done {
// symoffset = offset
lastop_done = false
}
switch op {
case BIND_OPCODE_DONE:
lastop_done = true
offset += 1
break
case BIND_OPCODE_DO_BIND:
if sym.name != "" {
new_sym := sym
syms = append(syms, &new_sym)
// fmt.Printf("Offset 0x%x: Symbol %+v\n", symoffset, sym)
log.WithFields(log.Fields{
// "offset": fmt.Sprintf("0x%x", symoffset),
"symbol": sym.name,
}).Trace("Bind")
sym.name = ""
}
offset += 1
break
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
sym.dylib = string(mc.dylibs[int32(imm)-1].name[:])
offset += 1
break
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
uleb, br := ReadULEB(buf)
sym.dylib = string(mc.dylibs[int32(uleb)-1].name[:])
offset += br
break
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
if imm == 0 {
sym.dylib = "dylib_0"
} else if imm == 0x0f {
sym.dylib = "dylib_-1"
} else if imm == 0x0e {
sym.dylib = "dylib_-2"
}
offset += 1
break
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
sym.name, _ = buf.ReadString(0)
offset += uint(len(sym.name))
break
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
uleb, br := ReadULEB(buf)
sym.segment = uint32(imm)
sym.segment_offset = uint32(uleb)
seg := mc.segments[sym.segment]
sym.address = seg.Vmaddr() + uint64(sym.segment_offset)
sym.file_address = seg.Fileoff() + uint64(sym.segment_offset)
offset += br
break
case BIND_OPCODE_ADD_ADDR_ULEB:
uleb, br := ReadULEB(buf)
sym.segment_offset += uint32(uleb)
sym.address += uint64(uleb)
sym.file_address += uint64(uleb)
offset += br
break
default:
break
}
}
for _, sym := range syms {
switch mc.ArchName() {
case "armv7", "armv7s":
var addr uint32
b := mc.buf[sym.file_address : sym.file_address+4]
r := bytes.NewBuffer(b)
binary.Read(r, mc.byteorder, &addr)
sym.stub = uint64(addr)
break
case "arm64", "arm64e":
b := mc.buf[sym.file_address : sym.file_address+8]
r := bytes.NewBuffer(b)
binary.Read(r, mc.byteorder, &sym.stub)
break
default:
sym.pnum = 0
sym.stub = 0
}
}
return syms
}

View File

@ -0,0 +1,268 @@
package macho
import (
"fmt"
"io"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
)
func rewriteLoadcommandsWithoutCodesignature(mc *MachoContext) {
if mc.Is64bit() {
mc.file.Seek(int64(Header_size_64), io.SeekStart)
} else {
mc.file.Seek(int64(Header_size), io.SeekStart)
}
for _, cmd := range mc.commands {
if cmd.Cmd() == LC_CODE_SIGNATURE {
continue
}
log.WithFields(log.Fields{
"cmd": cmd.Cmd(),
"cmdsize": cmd.Cmdsize(),
"name": cmd.Cmdname(),
}).Trace("Rewrite Load command")
mc.file.Write(cmd.Serialize(mc))
}
}
// Remove Codesign data from Mach-O binary
// There are many caveats to this problem.
// Mostly related to how codesign-allocate works.
// Removing codesign data at the end of __LINKEDIT sement
// is probably easier than removing them at an abitrary location.
//
// Assuming the codesign data is at the end of __LINKEDIT segment.
// The binary is probably signed at the last step, which is common.
//
// CODE_SIGNATURE load commands points to the codesign data offset and size.
// __LINKEDIT section points to data offset and size.
// We have:
// linkedit = (section*) LC_SEGMENT.section[0] // name="__LINKEDIT"
// codesign = (linkedit_data_command*) LC_CODE_SIGNATURE
// BinarySize = { f.seek(0, SEEKEND); return f.tell() }
//
// linkedit->fileoff + linkedit->filesize == codesign->dataOff + codesign->dataSize
// linkedit->fileoff + linkedit->filesize == BinarySize
//
// To remove the codesign data, we truncate the file to remove the codesign data.
// Then we update the linkedit->filesize to linkedit->filesize - codesign->dataSize
// We also fix the header to not read the LC_CODE_SIGNATURE,
// because codesign is the last step, the last load command is LC_CODE_SIGNATURE.
// Fix header->ncmds -= 1, header->sizeofcmds -= sizeof(linkedit_data_command)
//
// There's one more caveat, as mentioned by `insert_dylib`. Codesign data is aligned
// by 0x10. So it would have some padding before codesign data. Now that we have
// removed the data, but the padding still exists and is not prefered by codesign-allocate.
// To fix this issue, **again** we assume that the previous section is the String table
// (referenced by symtab) and increase the size of the string table to the end of data.
//
// A better approach could be an extended search for section pointing to the previous data.
// But general cases are String table.
//
// Implementation warnings:
// We have two implementation designs.
// - Re-parse the load commands and edit the value at offset
// - Use parsed load commands data, edit the values, and rewrite header + load commands
func (mc *MachoContext) RemoveCodesign() bool {
if !mc.WriteEnabled() {
return false
}
linkedit := mc.FindSegment("__LINKEDIT")
if linkedit == nil {
log.Warn("The binary has no __LINKEDIT Segment")
return false
}
var codesign *LinkEdit
for _, cmd := range mc.Linkedits() {
if cmd.Cmd() == LC_CODE_SIGNATURE {
codesign = cmd
break
}
}
if codesign == nil {
log.Warn("The binary is not signed, no LC_CODE_SIGNATURE found")
return false
}
filesize := uint64(len(mc.buf))
linkedit_end := uint64(linkedit.Fileoff()) + linkedit.Filesize()
codesign_end := uint64(codesign.Dataoff() + codesign.Datasize())
// linkedit is the last item in Mach-O binary
if linkedit_end != filesize {
log.Panic("Link edit is not the last item")
return false
}
// codesign is the last in linkedit
if linkedit_end != codesign_end {
log.Panic("Code sign data is not the last item in link edit segment")
return false
}
linkedit_newsize := linkedit.Filesize() - uint64(codesign.Datasize())
if (mc.symtab.stroff+mc.symtab.strsize)%0x10 == 0 {
// codesign requires padding of 0x10
// if strtab+strsize & 0xff < 0x8, it will pad til 0x10
// when removed codesign, we truncate the file to offset at 0x8 rather at 0x10
} else {
linkedit_newsize -= 8
}
mc.file.Truncate(int64(uint64(linkedit.Fileoff()) + linkedit_newsize))
mc.symtab.strsize = uint32(linkedit.Fileoff()+linkedit_newsize) - mc.symtab.stroff
// fix linkedit section
if mc.Is64bit() {
linkedit.(*Segment64).filesize = linkedit_newsize
} else {
linkedit.(*Segment32).filesize = uint32(linkedit_newsize)
}
// rewrite load commands
mc.UpdateHeaderRemoveLcmd(codesign.Cmdsize())
rewriteLoadcommandsWithoutCodesignature(mc)
// erase old LC_CODE_SIGNATURE data
old_codesign_lcmd_offset := func() uint64 {
loadcmd_size := int64(mc.Header().sizeofcmds)
if mc.Is64bit() {
return uint64(loadcmd_size) + Header_size_64
} else {
return uint64(loadcmd_size) + Header_size
}
}()
// size of codesign = sizeof linkedit_data_command = 4 * 4
mc.file.WriteAt(make([]byte, 4*4), int64(old_codesign_lcmd_offset))
// mc.file.Seek(old_codesign_lcmd_offset, io.SeekStart)
// mc.file.Write(make([]byte, 4*4))
return true
}
func (mc *MachoContext) RemoveInitFunctions() bool {
if mc.WriteEnabled() {
for _, ptr := range mc.InitFunctions() {
if mc.Is64bit() {
mc.file.WriteAt([]byte{0, 0, 0, 0, 0, 0, 0, 0}, int64(ptr.offset))
} else {
mc.file.WriteAt([]byte{0, 0, 0, 0}, int64(ptr.offset))
}
log.WithFields(log.Fields{
"offset": fmt.Sprintf("0x%x", ptr.Offset()),
"value": fmt.Sprintf("0x%x", ptr.Value()),
}).Debug("Remove init pointer")
}
return true
}
return false
}
func (mc *MachoContext) RemoveUnnecessaryInfo() bool {
for _, command := range mc.commands {
switch command.(type) {
case *LinkEdit:
var le = command.(*LinkEdit)
if le.Cmdname() != "LC_FUNCTION_STARTS" && le.Cmdname() != "LC_DATA_IN_CODE" {
continue
}
var start int64 = int64(le.dataoff)
var end int64 = start + int64(le.datasize)
for i := start; i < end; i++ {
mc.file.WriteAt([]byte{0}, i)
}
continue
default:
continue
}
}
return false
}
func (mc *MachoContext) RemoveClassicSymbol() bool {
return false
}
func (mc *MachoContext) AddLoadCmd(lcmd LoadCommand) {
var offset uint64
payload := lcmd.Serialize(mc)
if uint64(len(payload)) > mc.PaddingSize() {
log.WithFields(log.Fields{
"cmd": lcmd,
"len(cmd)": len(payload),
"available": mc.PaddingSize(),
}).Panic("Not enough space to add load command")
}
if mc.Is64bit() {
offset = Header_size_64 + uint64(mc.header.sizeofcmds)
} else {
offset = Header_size + uint64(mc.header.sizeofcmds)
}
log.WithFields(log.Fields{
"cmd": lcmd.Cmdname(),
"size": len(payload),
"offset": offset,
}).Debug("Adding Load Command")
mc.file.WriteAt(payload, int64(offset))
mc.UpdateHeaderAddLcmd(uint32(len(payload)))
}
func (mc *MachoContext) AddDylib(dylib string) {
if mc.DylibExisted(dylib) {
log.WithFields(log.Fields{
"dylib": dylib,
}).Debug("Adding dylib but existed")
return
}
name := []byte(dylib)
name = append(name, 0)
dylib_lcmd := Dylib{
c: LoadCmd{cmd: LC_LOAD_DYLIB, cmdsize: 0},
name: name,
nameoff: 24,
timestamp: 0,
current_version: 0,
compatibility_version: 0,
}
mc.AddLoadCmd(&dylib_lcmd)
}
func (mc *MachoContext) AddRPath(rpath string) {
if mc.RPathExisted(rpath) {
log.WithFields(log.Fields{
"rpath": rpath,
}).Debug("Adding rpath but existed")
return
}
path := []byte(rpath)
path = append(path, 0)
rpath_lcmd := RPath{
c: LoadCmd{cmd: LC_RPATH, cmdsize: 0},
offset: 12,
path: path,
}
mc.AddLoadCmd(&rpath_lcmd)
}
func (mc *MachoContext) UpdateHeaderAddLcmd(size uint32) {
if mc.WriteEnabled() {
mc.header.ncmds += 1
mc.header.sizeofcmds += size
mc.file.WriteAt(mc.header.Serialize(mc), 0)
}
}
func (mc *MachoContext) UpdateHeaderRemoveLcmd(size uint32) {
if mc.WriteEnabled() {
mc.header.ncmds -= 1
mc.header.sizeofcmds -= size
mc.file.WriteAt(mc.header.Serialize(mc), 0)
}
}

View File

@ -0,0 +1,13 @@
package macho
import (
"fmt"
)
type ParseError struct {
Expect string
}
func (e *ParseError) Error() string {
return fmt.Sprintf("Parse Error: %s", e.Expect)
}

View File

@ -0,0 +1,384 @@
package macho
import (
"bytes"
"encoding/binary"
"fmt"
)
type Section interface {
// SegName() []byte
SectName() []byte
Addr() uint64
Size() uint64
Offset() uint32
Type() uint8
Attribute() uint32
}
type Segment interface {
SegName() []byte
Vmaddr() uint64
Vmsize() uint64
Fileoff() uint64
Filesize() uint64
Flags() uint32
Sections() []Section
}
type Segment32 struct {
c LoadCmd
segname []byte
vmaddr uint32
vmsize uint32
fileoff uint32
filesize uint32
maxprot uint32
initprot uint32
nsects uint32
flags uint32
sections []*Section32
}
func (lcmd *Segment32) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *Segment32) Cmdsize() uint32 {
segment_size := uint32(56)
section_size := uint32(4*9 + 16*2)
return segment_size + section_size*lcmd.nsects
}
func (lcmd *Segment32) Cmdname() string {
var s string
s += fmt.Sprintf(
"%s %s vm[vmaddr:0x%x vmsize:0x%x] [fileoff:0x%x filesize:0x%x]",
lcmd.c.Cmdname(),
string(lcmd.segname),
lcmd.vmaddr,
lcmd.vmsize,
lcmd.fileoff,
lcmd.filesize,
)
for i, section := range lcmd.sections {
s += fmt.Sprintf("\n %d. %s [addr:0x%x size:0x%x offset:0x%x]",
i, string(section.sectname),
section.addr, section.size, section.offset,
)
}
return s
}
func (lcmd *Segment32) SegName() []byte {
return lcmd.segname
}
func (lcmd *Segment32) Vmaddr() uint64 {
return uint64(lcmd.vmaddr)
}
func (lcmd *Segment32) Vmsize() uint64 {
return uint64(lcmd.vmaddr)
}
func (lcmd *Segment32) Fileoff() uint64 {
return uint64(lcmd.fileoff)
}
func (lcmd *Segment32) Filesize() uint64 {
return uint64(lcmd.filesize)
}
func (lcmd *Segment32) Flags() uint32 {
return lcmd.flags
}
func (lcmd *Segment32) Sections() []Section {
var sections []Section
for _, section := range lcmd.sections {
sections = append(sections, section)
}
return sections
}
func (lcmd *Segment32) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
buf.Write(lcmd.segname)
binary.Write(buf, mc.byteorder, lcmd.vmaddr)
binary.Write(buf, mc.byteorder, lcmd.vmsize)
binary.Write(buf, mc.byteorder, lcmd.fileoff)
binary.Write(buf, mc.byteorder, lcmd.filesize)
binary.Write(buf, mc.byteorder, lcmd.maxprot)
binary.Write(buf, mc.byteorder, lcmd.initprot)
binary.Write(buf, mc.byteorder, lcmd.nsects)
binary.Write(buf, mc.byteorder, lcmd.flags)
for _, section := range lcmd.sections {
binary.Write(buf, mc.byteorder, section.Serialize(mc))
}
return buf.Bytes()
}
func (lcmd *Segment32) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
lcmd.segname = r.Next(16)
binary.Read(r, mc.byteorder, &lcmd.vmaddr)
binary.Read(r, mc.byteorder, &lcmd.vmsize)
binary.Read(r, mc.byteorder, &lcmd.fileoff)
binary.Read(r, mc.byteorder, &lcmd.filesize)
binary.Read(r, mc.byteorder, &lcmd.maxprot)
binary.Read(r, mc.byteorder, &lcmd.initprot)
binary.Read(r, mc.byteorder, &lcmd.nsects)
binary.Read(r, mc.byteorder, &lcmd.flags)
for i := uint32(0); i < lcmd.nsects; i++ {
section_buf := make([]byte, 68)
r.Read(section_buf)
var section Section32
section.Deserialize(mc, section_buf)
lcmd.sections = append(lcmd.sections, &section)
}
}
type Segment64 struct {
c LoadCmd
segname []byte
vmaddr uint64
vmsize uint64
fileoff uint64
filesize uint64
maxprot uint32
initprot uint32
nsects uint32
flags uint32
sections []*Section64
}
func (lcmd *Segment64) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *Segment64) Cmdsize() uint32 {
segment_size := uint32(72)
section_size := uint32(8*2 + 4*8 + 16*2)
return segment_size + section_size*lcmd.nsects
}
func (lcmd *Segment64) Cmdname() string {
var s string
s += fmt.Sprintf(
"%s %s vm[vmaddr:0x%x vmsize:0x%x] [fileoff:0x%x filesize:0x%x]",
lcmd.c.Cmdname(),
string(lcmd.segname),
lcmd.vmaddr,
lcmd.vmsize,
lcmd.fileoff,
lcmd.filesize,
)
for i, section := range lcmd.sections {
s += fmt.Sprintf("\n %d. %s [addr:0x%x size:0x%x offset:0x%x]",
i, string(section.sectname),
section.addr, section.size, section.offset,
)
}
return s
}
func (lcmd *Segment64) SegName() []byte {
return lcmd.segname
}
func (lcmd *Segment64) Vmaddr() uint64 {
return lcmd.vmaddr
}
func (lcmd *Segment64) Vmsize() uint64 {
return lcmd.vmaddr
}
func (lcmd *Segment64) Fileoff() uint64 {
return lcmd.fileoff
}
func (lcmd *Segment64) Filesize() uint64 {
return lcmd.filesize
}
func (lcmd *Segment64) Flags() uint32 {
return lcmd.flags
}
func (lcmd *Segment64) Sections() []Section {
var sections []Section
for _, section := range lcmd.sections {
sections = append(sections, section)
}
return sections
}
func (lcmd *Segment64) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
buf.Write(lcmd.segname)
binary.Write(buf, mc.byteorder, lcmd.vmaddr)
binary.Write(buf, mc.byteorder, lcmd.vmsize)
binary.Write(buf, mc.byteorder, lcmd.fileoff)
binary.Write(buf, mc.byteorder, lcmd.filesize)
binary.Write(buf, mc.byteorder, lcmd.maxprot)
binary.Write(buf, mc.byteorder, lcmd.initprot)
binary.Write(buf, mc.byteorder, lcmd.nsects)
binary.Write(buf, mc.byteorder, lcmd.flags)
for _, section := range lcmd.sections {
binary.Write(buf, mc.byteorder, section.Serialize(mc))
}
return buf.Bytes()
}
func (lcmd *Segment64) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
lcmd.segname = r.Next(16)
binary.Read(r, mc.byteorder, &lcmd.vmaddr)
binary.Read(r, mc.byteorder, &lcmd.vmsize)
binary.Read(r, mc.byteorder, &lcmd.fileoff)
binary.Read(r, mc.byteorder, &lcmd.filesize)
binary.Read(r, mc.byteorder, &lcmd.maxprot)
binary.Read(r, mc.byteorder, &lcmd.initprot)
binary.Read(r, mc.byteorder, &lcmd.nsects)
binary.Read(r, mc.byteorder, &lcmd.flags)
for i := uint32(0); i < lcmd.nsects; i++ {
section_buf := make([]byte, 80)
r.Read(section_buf)
var section Section64
section.Deserialize(mc, section_buf)
lcmd.sections = append(lcmd.sections, &section)
}
}
type Section32 struct {
sectname []byte
segname []byte
addr uint32
size uint32
offset uint32
align uint32
reloff uint32
nreloc uint32
flags uint32
reserved1 uint32
reserved2 uint32
}
func (sec *Section32) SectName() []byte {
return sec.sectname
}
func (sec *Section32) Addr() uint64 {
return uint64(sec.addr)
}
func (sec *Section32) Size() uint64 {
return uint64(sec.size)
}
func (sec *Section32) Offset() uint32 {
return sec.offset
}
func (sec *Section32) Type() uint8 {
return uint8(sec.flags & 0xff)
}
func (sec *Section32) Attribute() uint32 {
return sec.flags & 0xffffff00
}
func (section *Section32) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
buf.Write(section.sectname)
buf.Write(section.segname)
binary.Write(buf, mc.byteorder, section.addr)
binary.Write(buf, mc.byteorder, section.size)
binary.Write(buf, mc.byteorder, section.offset)
binary.Write(buf, mc.byteorder, section.align)
binary.Write(buf, mc.byteorder, section.reloff)
binary.Write(buf, mc.byteorder, section.nreloc)
binary.Write(buf, mc.byteorder, section.flags)
binary.Write(buf, mc.byteorder, section.reserved1)
binary.Write(buf, mc.byteorder, section.reserved2)
return buf.Bytes()
}
func (section *Section32) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
section.sectname = r.Next(16)
section.segname = r.Next(16)
binary.Read(r, mc.byteorder, &section.addr)
binary.Read(r, mc.byteorder, &section.size)
binary.Read(r, mc.byteorder, &section.offset)
binary.Read(r, mc.byteorder, &section.align)
binary.Read(r, mc.byteorder, &section.reloff)
binary.Read(r, mc.byteorder, &section.nreloc)
binary.Read(r, mc.byteorder, &section.flags)
binary.Read(r, mc.byteorder, &section.reserved1)
binary.Read(r, mc.byteorder, &section.reserved2)
}
type Section64 struct {
sectname []byte
segname []byte
addr uint64
size uint64
offset uint32
align uint32
reloff uint32
nreloc uint32
flags uint32
reserved1 uint32
reserved2 uint32
reserved3 uint32
}
func (sec *Section64) SectName() []byte {
return sec.sectname
}
func (sec *Section64) Addr() uint64 {
return sec.addr
}
func (sec *Section64) Size() uint64 {
return sec.size
}
func (sec *Section64) Offset() uint32 {
return sec.offset
}
func (sec *Section64) Type() uint8 {
return uint8(sec.flags & 0xff)
}
func (sec *Section64) Attribute() uint32 {
return sec.flags & 0xffffff00
}
func (section *Section64) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
buf.Write(section.sectname)
buf.Write(section.segname)
binary.Write(buf, mc.byteorder, section.addr)
binary.Write(buf, mc.byteorder, section.size)
binary.Write(buf, mc.byteorder, section.offset)
binary.Write(buf, mc.byteorder, section.align)
binary.Write(buf, mc.byteorder, section.reloff)
binary.Write(buf, mc.byteorder, section.nreloc)
binary.Write(buf, mc.byteorder, section.flags)
binary.Write(buf, mc.byteorder, section.reserved1)
binary.Write(buf, mc.byteorder, section.reserved2)
binary.Write(buf, mc.byteorder, section.reserved3)
return buf.Bytes()
}
func (section *Section64) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
section.sectname = r.Next(16)
section.segname = r.Next(16)
binary.Read(r, mc.byteorder, &section.addr)
binary.Read(r, mc.byteorder, &section.size)
binary.Read(r, mc.byteorder, &section.offset)
binary.Read(r, mc.byteorder, &section.align)
binary.Read(r, mc.byteorder, &section.reloff)
binary.Read(r, mc.byteorder, &section.nreloc)
binary.Read(r, mc.byteorder, &section.flags)
binary.Read(r, mc.byteorder, &section.reserved1)
binary.Read(r, mc.byteorder, &section.reserved2)
binary.Read(r, mc.byteorder, &section.reserved3)
}

View File

@ -0,0 +1,483 @@
package macho
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
. "ios-wrapper/pkg/ios"
)
// A Serializable interface
// Serialize to bytes and Deserialize to struct
// with MachoContext to know the byteorder
type Serializable interface {
Serialize(mc *MachoContext) []byte
Deserialize(mc *MachoContext, buf []byte)
}
// macho_header and macho_header64
type Header struct {
magic uint32
cputype uint32
cpusubtype uint32
filetype uint32
ncmds uint32
sizeofcmds uint32
flags uint32
reserved uint32
}
func (h *Header) Cputype() uint32 {
return h.cputype
}
func (h *Header) Cpusubtype() uint32 {
return h.cpusubtype
}
func (h *Header) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, h.magic)
binary.Write(buf, mc.byteorder, h.cputype)
binary.Write(buf, mc.byteorder, h.cpusubtype)
binary.Write(buf, mc.byteorder, h.filetype)
binary.Write(buf, mc.byteorder, h.ncmds)
binary.Write(buf, mc.byteorder, h.sizeofcmds)
binary.Write(buf, mc.byteorder, h.flags)
if mc.Is64bit() {
binary.Write(buf, mc.byteorder, h.reserved)
}
return buf.Bytes()
}
func (h *Header) Deserialize(mc *MachoContext, r *bufio.Reader) {
binary.Read(r, mc.byteorder, &h.magic)
binary.Read(r, mc.byteorder, &h.cputype)
binary.Read(r, mc.byteorder, &h.cpusubtype)
binary.Read(r, mc.byteorder, &h.filetype)
binary.Read(r, mc.byteorder, &h.ncmds)
binary.Read(r, mc.byteorder, &h.sizeofcmds)
binary.Read(r, mc.byteorder, &h.flags)
if mc.Is64bit() {
binary.Read(r, mc.byteorder, &h.reserved)
}
}
type LoadCommand interface {
Serializable
Cmd() uint32
Cmdsize() uint32
Cmdname() string
}
// Stores the common value of load command
// Raw stores the rest of the command
// where no struct defined for cmd
type LoadCmd struct {
cmd uint32
cmdsize uint32
Raw []byte
}
func (lcmd *LoadCmd) Cmd() uint32 {
return lcmd.cmd
}
func (lcmd *LoadCmd) Cmdsize() uint32 {
return lcmd.cmdsize
}
func (lcmd *LoadCmd) Cmdname() string {
switch lcmd.cmd {
case LC_RPATH:
return "LC_RPATH"
case LC_DYLD_INFO:
return "LC_DYLD_INFO"
case LC_DYLD_INFO_ONLY:
return "LC_DYLD_INFO_ONLY"
case LC_CODE_SIGNATURE:
return "LC_CODE_SIGNATURE"
case LC_LAZY_LOAD_DYLIB:
return "LC_LAZY_LOAD_DYLIB"
case LC_LOAD_DYLIB:
return "LC_LOAD_DYLIB"
case LC_ID_DYLIB:
return "LC_ID_DYLIB"
case LC_REEXPORT_DYLIB:
return "LC_REEXPORT_DYLIB"
case LC_ENCRYPTION_INFO:
return "LC_ENCRYPTION_INFO"
case LC_ENCRYPTION_INFO_64:
return "LC_ENCRYPTION_INFO_64"
case LC_DYSYMTAB:
return "LC_DYSYMTAB"
case LC_LOAD_WEAK_DYLIB:
return "LC_LOAD_WEAK_DYLIB"
case LC_SEGMENT:
return "LC_SEGMENT"
case LC_SEGMENT_64:
return "LC_SEGMENT_64"
case LC_MAIN:
return "LC_MAIN"
case LC_FUNCTION_STARTS:
return "LC_FUNCTION_STARTS"
case LC_DATA_IN_CODE:
return "LC_DATA_IN_CODE"
case LC_SYMTAB:
return "LC_SYMTAB"
default:
// TODO: Update
return fmt.Sprintf("LC_DONT_KNOW_0x%x", lcmd.Cmd())
}
}
func (lcmd *LoadCmd) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.Raw)
return buf.Bytes()
}
func (lcmd *LoadCmd) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.cmd)
binary.Read(r, mc.byteorder, &lcmd.cmdsize)
lcmd.Raw = make([]byte, lcmd.cmdsize-8)
r.Read(lcmd.Raw)
}
type RPath struct {
c LoadCmd
offset uint32
path []byte
}
func (lcmd *RPath) Cmd() uint32 {
return LC_RPATH
}
func (lcmd *RPath) Cmdsize() uint32 {
raw_cmd_size := 8 + 4 + len(lcmd.path) + 1
if raw_cmd_size%8 == 0 {
return uint32(raw_cmd_size)
}
padded_cmd_size := raw_cmd_size + (8 - (raw_cmd_size % 8))
return uint32(padded_cmd_size)
}
func (lcmd *RPath) Cmdname() string {
return fmt.Sprintf("%s %s", lcmd.c.Cmdname(), string(lcmd.path))
}
func (lcmd *RPath) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.offset)
binary.Write(buf, mc.byteorder, []byte(lcmd.path))
buf.WriteByte(0)
if buf.Len()%8 != 0 {
bytes_padded := 8 - (buf.Len() % 8)
binary.Write(buf, mc.byteorder, make([]byte, bytes_padded))
}
return buf.Bytes()
}
func (lcmd *RPath) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.offset)
lcmd.path, _ = r.ReadBytes(0)
lcmd.path = bytes.Trim(lcmd.path, "\x00")
}
type Dylib struct {
c LoadCmd
nameoff uint32
name []byte
timestamp uint32
current_version uint32
compatibility_version uint32
}
func (lcmd *Dylib) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *Dylib) Cmdsize() uint32 {
raw_cmd_size := 8 + 16 + len(lcmd.name) + 1
if raw_cmd_size%8 == 0 {
return uint32(raw_cmd_size)
}
padded_cmd_size := raw_cmd_size + (8 - (raw_cmd_size % 8))
return uint32(padded_cmd_size)
}
func (lcmd *Dylib) Cmdname() string {
return fmt.Sprintf("%s %s", lcmd.c.Cmdname(), string(lcmd.name))
}
func (lcmd *Dylib) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.nameoff)
binary.Write(buf, mc.byteorder, lcmd.timestamp)
binary.Write(buf, mc.byteorder, lcmd.current_version)
binary.Write(buf, mc.byteorder, lcmd.compatibility_version)
binary.Write(buf, mc.byteorder, lcmd.name)
buf.WriteByte(0)
// size must align with 8
if buf.Len()%8 != 0 {
bytes_padded := 8 - (buf.Len() % 8)
binary.Write(buf, mc.byteorder, make([]byte, bytes_padded))
}
return buf.Bytes()
}
func (lcmd *Dylib) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.nameoff)
binary.Read(r, mc.byteorder, &lcmd.timestamp)
binary.Read(r, mc.byteorder, &lcmd.current_version)
binary.Read(r, mc.byteorder, &lcmd.compatibility_version)
lcmd.name, _ = r.ReadBytes(0)
lcmd.name = bytes.Trim(lcmd.name, "\x00")
}
type EncryptionInfo struct {
c LoadCmd
cryptoff uint32
cryptsize uint32
cryptid uint32
pad uint32
}
func (lcmd *EncryptionInfo) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *EncryptionInfo) Cmdsize() uint32 {
if lcmd.Cmd() == LC_ENCRYPTION_INFO {
return uint32(8 + 4*3)
} else {
return uint32(8 + 4*4)
}
}
func (lcmd *EncryptionInfo) Cmdname() string {
return fmt.Sprintf("%s start:0x%x size:0x%x encrypted:%d",
lcmd.c.Cmdname(), lcmd.cryptoff, lcmd.cryptsize, lcmd.cryptid)
}
func (lcmd *EncryptionInfo) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.cryptoff)
binary.Write(buf, mc.byteorder, lcmd.cryptsize)
binary.Write(buf, mc.byteorder, lcmd.cryptid)
if mc.Is64bit() {
binary.Write(buf, mc.byteorder, lcmd.pad)
}
return buf.Bytes()
}
func (lcmd *EncryptionInfo) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.cryptoff)
binary.Read(r, mc.byteorder, &lcmd.cryptsize)
binary.Read(r, mc.byteorder, &lcmd.cryptid)
if mc.Is64bit() {
binary.Read(r, mc.byteorder, &lcmd.pad)
}
}
type EntryPoint struct {
c LoadCmd
entryoff uint64
stacksize uint64
}
func (lcmd *EntryPoint) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *EntryPoint) Cmdsize() uint32 {
return uint32(8 + 8*2)
}
func (lcmd *EntryPoint) Cmdname() string {
return lcmd.c.Cmdname()
}
func (lcmd *EntryPoint) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.entryoff)
binary.Write(buf, mc.byteorder, lcmd.stacksize)
return buf.Bytes()
}
func (lcmd *EntryPoint) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.entryoff)
binary.Read(r, mc.byteorder, &lcmd.stacksize)
}
type DyldInfo struct {
c LoadCmd
rebase_off uint32
rebase_size uint32
bind_off uint32
bind_size uint32
weak_bind_off uint32
weak_bind_size uint32
lazy_bind_off uint32
lazy_bind_size uint32
export_off uint32
export_size uint32
}
func (lcmd *DyldInfo) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *DyldInfo) Cmdsize() uint32 {
return uint32(8 + 4*10)
}
func (lcmd *DyldInfo) Cmdname() string {
return lcmd.c.Cmdname()
}
func (lcmd *DyldInfo) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.rebase_off)
binary.Write(buf, mc.byteorder, lcmd.rebase_size)
binary.Write(buf, mc.byteorder, lcmd.bind_off)
binary.Write(buf, mc.byteorder, lcmd.bind_size)
binary.Write(buf, mc.byteorder, lcmd.weak_bind_off)
binary.Write(buf, mc.byteorder, lcmd.weak_bind_size)
binary.Write(buf, mc.byteorder, lcmd.lazy_bind_off)
binary.Write(buf, mc.byteorder, lcmd.lazy_bind_size)
binary.Write(buf, mc.byteorder, lcmd.export_off)
binary.Write(buf, mc.byteorder, lcmd.export_size)
return buf.Bytes()
}
func (lcmd *DyldInfo) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.rebase_off)
binary.Read(r, mc.byteorder, &lcmd.rebase_size)
binary.Read(r, mc.byteorder, &lcmd.bind_off)
binary.Read(r, mc.byteorder, &lcmd.bind_size)
binary.Read(r, mc.byteorder, &lcmd.weak_bind_off)
binary.Read(r, mc.byteorder, &lcmd.weak_bind_size)
binary.Read(r, mc.byteorder, &lcmd.lazy_bind_off)
binary.Read(r, mc.byteorder, &lcmd.lazy_bind_size)
binary.Read(r, mc.byteorder, &lcmd.export_off)
binary.Read(r, mc.byteorder, &lcmd.export_size)
}
type LinkEdit struct {
c LoadCmd
dataoff uint32
datasize uint32
}
func (lcmd *LinkEdit) Dataoff() uint32 {
return lcmd.dataoff
}
func (lcmd *LinkEdit) Datasize() uint32 {
return lcmd.datasize
}
func (lcmd *LinkEdit) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *LinkEdit) Cmdsize() uint32 {
return uint32(8 + 4*2)
}
func (lcmd *LinkEdit) Cmdname() string {
return lcmd.c.Cmdname()
}
func (lcmd *LinkEdit) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.dataoff)
binary.Write(buf, mc.byteorder, lcmd.datasize)
return buf.Bytes()
}
func (lcmd *LinkEdit) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.dataoff)
binary.Read(r, mc.byteorder, &lcmd.datasize)
}
type Symtab struct {
c LoadCmd
symoff uint32
nsyms uint32
stroff uint32
strsize uint32
}
func (lcmd *Symtab) Cmd() uint32 {
return lcmd.c.Cmd()
}
func (lcmd *Symtab) Cmdsize() uint32 {
return uint32(8 + 4*4)
}
func (lcmd *Symtab) Cmdname() string {
return fmt.Sprintf("%s symoff:0x%x nsyms:%d stroff:0x%x strsize:0x%x",
lcmd.c.Cmdname(), lcmd.symoff, lcmd.nsyms, lcmd.stroff, lcmd.strsize)
}
func (lcmd *Symtab) Serialize(mc *MachoContext) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, mc.byteorder, lcmd.Cmd())
binary.Write(buf, mc.byteorder, lcmd.Cmdsize())
binary.Write(buf, mc.byteorder, lcmd.symoff)
binary.Write(buf, mc.byteorder, lcmd.nsyms)
binary.Write(buf, mc.byteorder, lcmd.stroff)
binary.Write(buf, mc.byteorder, lcmd.strsize)
return buf.Bytes()
}
func (lcmd *Symtab) Deserialize(mc *MachoContext, buf []byte) {
r := bytes.NewBuffer(buf)
binary.Read(r, mc.byteorder, &lcmd.c.cmd)
binary.Read(r, mc.byteorder, &lcmd.c.cmdsize)
binary.Read(r, mc.byteorder, &lcmd.symoff)
binary.Read(r, mc.byteorder, &lcmd.nsyms)
binary.Read(r, mc.byteorder, &lcmd.stroff)
binary.Read(r, mc.byteorder, &lcmd.strsize)
}

View File

@ -0,0 +1,265 @@
package macho
import (
"bufio"
"bytes"
"encoding/binary"
"io"
"os"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
)
// A Mach-O binary context
// holds byteorder, pointersize, header
// And load commands
type MachoContext struct {
file *os.File // backed file enabled write
buf []byte // backed up data
byteorder binary.ByteOrder
pointersize uint32
entryoff uint64
header Header
commands []LoadCommand
rpaths []*RPath
dylibs []*Dylib
linkedits []*LinkEdit
segments []Segment
symtab *Symtab
dyldinfo *DyldInfo
}
func (mc *MachoContext) FileSize() uint32 {
return uint32(len(mc.buf))
}
func (mc *MachoContext) Header() *Header {
return &mc.header
}
func (mc *MachoContext) Commands() []LoadCommand {
return mc.commands
}
func (mc *MachoContext) Rpaths() []*RPath {
return mc.rpaths
}
func (mc *MachoContext) Dylibs() []*Dylib {
return mc.dylibs
}
func (mc *MachoContext) Linkedits() []*LinkEdit {
return mc.linkedits
}
func (mc *MachoContext) Segments() []Segment {
return mc.segments
}
func (mc *MachoContext) WriteEnabled() bool {
return mc.file != nil
}
func (mc *MachoContext) WriteBufferTo(w io.Writer) (int, error) {
return w.Write(mc.buf)
}
// Parse the provided Mach-O binary from a file
// The first 4 bytes of the file must be the MachO magic
// That is:
// file.Seek(0, io.SeekStart)
// magic := make([]byte, 4)
// file.Read(magic)
// assert magic == []byte{macho magic bytes}
// or else, parsing error is panic
func (mc *MachoContext) ParseFile(file *os.File, length int) error {
file.Seek(0, io.SeekStart)
mc.file = file
// save the file content to buf
if length == 0 {
last, _ := mc.file.Seek(0, io.SeekEnd)
mc.buf = make([]byte, last)
mc.file.Seek(0, io.SeekStart)
} else {
mc.buf = make([]byte, length)
}
if _, err := io.ReadFull(mc.file, mc.buf); err != nil {
return err
}
mc.file.Seek(0, io.SeekStart) // reset peek, safety reason
r := bufio.NewReader(file)
return mc.Parse(r)
}
// A macho context can be made from a buffer
// In this case write is disable
// Every information accessed will be through
// this byte buffer
func (mc *MachoContext) ParseBuffer(buf []byte) error {
mc.file = nil
mc.buf = buf
r := bytes.NewReader(buf)
rr := bufio.NewReader(r)
return mc.Parse(rr)
}
// Parse the provided Mach-O binary from a buffer
func (mc *MachoContext) Parse(r *bufio.Reader) error {
{ // read magic to define byteorder and pointersize
var magic uint32
magic_buf, _ := r.Peek(4)
magic_r := bytes.NewReader(magic_buf)
binary.Read(magic_r, binary.LittleEndian, &magic)
if magic != Magic32 && magic != Magic64 && magic != Cigam32 &&
magic != Cigam64 {
log.WithFields(log.Fields{
"magic": magic,
}).Error("Magic for Mach-O does not match")
return &ParseError{
Expect: "Magic for Mach-O does not match",
}
}
if magic == Magic32 || magic == Magic64 {
mc.byteorder = binary.LittleEndian
} else {
mc.byteorder = binary.BigEndian
}
if magic == Magic32 {
mc.pointersize = 4
} else {
mc.pointersize = 8
}
}
mc.header.Deserialize(mc, r)
for i := uint32(0); i < mc.header.ncmds; i++ {
var cmd uint32
var cmdsize uint32
load_buf, _ := r.Peek(8)
load_r := bytes.NewReader(load_buf)
binary.Read(load_r, mc.byteorder, &cmd)
binary.Read(load_r, mc.byteorder, &cmdsize)
log.WithFields(log.Fields{
"nth": i,
"cmd": cmd,
"cmdsize": cmdsize,
}).Trace("Load command")
command_buf := make([]byte, cmdsize)
if bytesread, err := io.ReadFull(r, command_buf); err != nil ||
uint32(bytesread) != cmdsize {
log.WithFields(log.Fields{
"nth": i,
"cmdsize": cmdsize,
"bytesread": bytesread,
}).Error("Cannot read load command")
return &ParseError{
Expect: "Cannot read load command",
}
}
switch cmd {
case LC_RPATH:
lcmd := new(RPath)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.rpaths = append(mc.rpaths, lcmd)
break
case LC_ID_DYLIB,
LC_LOAD_DYLIB,
LC_LAZY_LOAD_DYLIB,
LC_REEXPORT_DYLIB,
LC_LOAD_WEAK_DYLIB:
lcmd := new(Dylib)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
if cmd != LC_ID_DYLIB {
mc.dylibs = append(mc.dylibs, lcmd)
}
break
case LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64:
lcmd := new(EncryptionInfo)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
break
case LC_SEGMENT:
lcmd := new(Segment32)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.segments = append(mc.segments, lcmd)
break
case LC_SEGMENT_64:
lcmd := new(Segment64)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.segments = append(mc.segments, lcmd)
break
case LC_MAIN:
lcmd := new(EntryPoint)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.entryoff = lcmd.entryoff
break
case LC_DYLD_INFO, LC_DYLD_INFO_ONLY:
lcmd := new(DyldInfo)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.dyldinfo = lcmd
break
case LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_CODE_SIGNATURE, LC_DYLD_CHAINED_FIXUPS, LC_DYLD_EXPORTS_TRIE:
lcmd := new(LinkEdit)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.linkedits = append(mc.linkedits, lcmd)
break
case LC_SYMTAB:
lcmd := new(Symtab)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
mc.symtab = lcmd
default:
lcmd := new(LoadCmd)
lcmd.Deserialize(mc, command_buf)
mc.commands = append(mc.commands, lcmd)
break
}
}
return nil
}
func (mc *MachoContext) Is64bit() bool {
return mc.pointersize == 8
}
func (mc *MachoContext) PointerSize() uint32 {
return mc.pointersize
}
func (mc *MachoContext) ImageBase() uint64 {
for _, segment := range mc.segments {
if segment.Fileoff() == 0 && segment.Filesize() != 0 {
return segment.Vmaddr()
}
}
return 0
}

View File

@ -0,0 +1,111 @@
package macho
import (
"bytes"
"encoding/binary"
"io"
)
const (
N_STAB uint8 = 0xe0 /* if any of these bits set, a symbolic debugging entry */
N_PEXT uint8 = 0x10 /* private external symbol bit */
N_TYPE uint8 = 0x0e /* mask for the type bits */
N_EXT uint8 = 0x01 /* external symbol bit, set for external symbols */
// Values for N_TYPE bits of the n_type field.
N_UNDF uint8 = 0x0 /* undefined, n_sect == NO_SECT */
N_ABS uint8 = 0x2 /* absolute, n_sect == NO_SECT */
N_SECT uint8 = 0xe /* defined in section number n_sect */
N_PBUD uint8 = 0xc /* prebound undefined (defined in a dylib) */
N_INDR uint8 = 0xa /* indirect */
NO_SECT uint8 = 0 /* symbol is not in any section */
)
type Symbol struct {
name string
address uint64
}
func (sym *Symbol) Name() string {
return sym.name
}
func (sym *Symbol) Address() uint64 {
return sym.address
}
func (mc *MachoContext) CollectSymbols() []*Symbol {
// for referencing section
// sections := []Section{}
// for _, segment := range mc.segments {
// for _, section := range segment.Sections() {
// sections = append(sections, section)
// }
// }
symbols := []*Symbol{}
if mc.symtab.nsyms == 0 {
return []*Symbol{}
}
symtab_buffer := func() *bytes.Buffer {
start := mc.symtab.symoff
if mc.Is64bit() {
end := start + mc.symtab.nsyms*16
return bytes.NewBuffer(mc.buf[start:end])
} else {
end := start + mc.symtab.nsyms*12
return bytes.NewBuffer(mc.buf[start:end])
}
}()
strtable := func() *bytes.Reader {
start := mc.symtab.stroff
end := start + mc.symtab.strsize
return bytes.NewReader(mc.buf[start:end])
}()
for i := uint32(0); i < mc.symtab.nsyms; i++ {
var strx uint32
var flags uint8
var sect uint8
var desc uint16
var value32 uint32
var value64 uint64
binary.Read(symtab_buffer, mc.byteorder, &strx)
binary.Read(symtab_buffer, mc.byteorder, &flags)
binary.Read(symtab_buffer, mc.byteorder, &sect)
binary.Read(symtab_buffer, mc.byteorder, &desc)
if mc.Is64bit() {
binary.Read(symtab_buffer, mc.byteorder, &value64)
} else {
// always use value64
binary.Read(symtab_buffer, mc.byteorder, &value32)
value64 = uint64(value32)
}
mangled_name := func() string {
strtable.Seek(int64(strx), io.SeekStart)
name := bytes.NewBufferString("")
for {
b, _ := strtable.ReadByte()
if b == 0 {
break
}
name.WriteByte(b)
}
return name.String()
}()
// fmt.Printf("0x%x %s\n", value64, mangled_name)
symbols = append(symbols, &Symbol{
name: mangled_name,
address: value64,
})
}
return symbols
}

View File

@ -0,0 +1,197 @@
package macho
import (
"bytes"
"encoding/binary"
"fmt"
"os"
log "github.com/sirupsen/logrus"
. "ios-wrapper/pkg/ios"
)
func (mc *MachoContext) ArchName() string {
if mc.header.cputype == 12 && mc.header.cpusubtype == 9 {
return "armv7"
}
if mc.header.cputype == 12 && mc.header.cpusubtype == 11 {
return "armv7s"
}
if mc.header.cputype == 0x100000C && mc.header.cpusubtype == 0 {
return "arm64"
}
if mc.header.cputype == 0x100000C && mc.header.cpusubtype == 2 {
return "arm64e"
}
if mc.header.cputype == 7 && mc.header.cpusubtype == 3 {
return "i386"
}
if mc.header.cputype == 0x1000007 && mc.header.cpusubtype == 3 {
return "x86_64"
}
if mc.header.cputype == 0x1000007 && mc.header.cpusubtype == 0x80000003 {
return "x86_64-lib64"
}
return fmt.Sprintf("arch-%x-%x", mc.header.cputype, mc.header.cpusubtype)
}
func MachosFromFiles(files []string) ([]*MachoContext, error) {
var r []*MachoContext
for _, filename := range files {
var mc MachoContext
f, _ := os.OpenFile(filename, os.O_RDWR, 0644)
err := mc.ParseFile(f, 0)
if err != nil {
return nil, err
}
r = append(r, &mc)
}
return r, nil
}
func CheckDuplicateArch(machos []*MachoContext) bool {
for i := 0; i < len(machos)-1; i++ {
for j := i + 1; j < len(machos); j++ {
i_cputype := machos[i].Header().Cputype()
j_cputype := machos[j].Header().Cputype()
i_cpusubtype := machos[i].Header().Cpusubtype()
j_cpusubtype := machos[j].Header().Cpusubtype()
if i_cputype == j_cputype &&
i_cpusubtype == j_cpusubtype {
log.WithFields(log.Fields{
"cputype": i_cputype,
"cpusubtype": i_cpusubtype,
}).Warn("Duplicate Mach-O Arch")
return true
}
}
}
return false
}
func (mc *MachoContext) PaddingSize() uint64 {
if mc.Is64bit() {
return mc.entryoff - (Header_size_64 + uint64(mc.header.sizeofcmds))
} else {
return mc.entryoff - (Header_size + uint64(mc.header.sizeofcmds))
}
}
func (mc *MachoContext) DylibExisted(name string) bool {
// simple check
// Advanced check requires expansion of @rpath
for _, dylib := range mc.dylibs {
dylib_ := bytes.Trim(dylib.name, "\x00")
match := bytes.Compare(dylib_, []byte(name)) == 0
log.WithFields(log.Fields{
"left": dylib_,
"right": []byte(name),
"match": match,
}).Trace("Dylib check")
if match {
return true
}
}
return false
}
func (mc *MachoContext) RPathExisted(path string) bool {
// simple check
// Advanced check requires expansion of @rpath
for _, rpath := range mc.rpaths {
rpath_ := bytes.Trim(rpath.path, "\x00")
match := bytes.Compare(rpath_, []byte(path)) == 0
log.WithFields(log.Fields{
"left": rpath_,
"right": []byte(path),
"match": match,
}).Trace("Rpath check")
if match {
return true
}
}
return false
}
func (mc *MachoContext) FindSection(name string) Section {
for _, segment := range mc.segments {
for _, section := range segment.Sections() {
sectname := bytes.Trim(section.SectName(), "\x00")
if bytes.Compare(sectname, []byte(name)) == 0 {
return section
}
}
}
return nil
}
func (mc *MachoContext) FindSegment(name string) Segment {
for _, segment := range mc.segments {
sectname := bytes.Trim(segment.SegName(), "\x00")
if bytes.Compare(sectname, []byte(name)) == 0 {
return segment
}
}
return nil
}
func (mc *MachoContext) Cut(offset uint64, size uint64) []byte {
return mc.buf[offset : offset + size];
}
// INIT POINTER
type InitPointer struct {
offset uint64
low uint32
high uint32
}
func (ptr *InitPointer) Offset() uint64 {
return ptr.offset
}
func (ptr *InitPointer) Value() uint64 {
return (uint64(ptr.high) << 32) | uint64(ptr.low)
}
func (ptr *InitPointer) String() string {
return fmt.Sprintf("0x%x -> 0x%x",
ptr.offset, (uint64(ptr.high)<<32)|uint64(ptr.low))
}
func (mc *MachoContext) InitFunctions() []InitPointer {
var valid_sections []Section
for _, segment := range mc.segments {
for _, section := range segment.Sections() {
if section.Type() == S_MOD_INIT_FUNC_POINTERS {
valid_sections = append(valid_sections, section)
}
}
}
var funcs []InitPointer
for _, section := range valid_sections {
offset := uint64(section.Offset())
size := section.Size()
data := mc.buf[offset : offset+size]
// TODO: assert len(data) // mc.pointersize
count := size / uint64(mc.pointersize)
data_buf := bytes.NewBuffer(data)
for i := uint64(0); i < count; i++ {
var fun InitPointer
fun.offset = offset + i*uint64(mc.pointersize)
binary.Read(data_buf, mc.byteorder, &fun.low)
if mc.Is64bit() {
binary.Read(data_buf, mc.byteorder, &fun.high)
}
funcs = append(funcs, fun)
}
}
return funcs
}

View File

@ -0,0 +1,40 @@
package leb128
import (
"bytes"
)
// Read unsigned leb128, no error
func ReadULEB(buf *bytes.Buffer) (uint, uint) {
result := uint(0)
shift := uint(0)
bytes_read := uint(0)
for {
b, _ := buf.ReadByte()
bytes_read += 1
result |= uint(b&0b0111_1111) << shift
shift += 7
if (b & 0b1000_0000) == 0 {
return result, bytes_read
}
}
}
// Read signed leb128, no error
func ReadSLEB(buf *bytes.Buffer) (int, uint) {
result := int(0)
shift := uint(0)
bytes_read := uint(0)
for {
b, _ := buf.ReadByte()
bytes_read += 1
result |= int(b&0b0111_1111) << shift
shift += 7
if (b & 0b1000_0000) == 0 {
if b&0x40 != 0 {
return result | (-1 << shift), bytes_read
}
return result, bytes_read
}
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020 IBM Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package version
var (
Version = "1.1"
)

View File

@ -0,0 +1,15 @@
syntax = "proto3";
option optimize_for = LITE_RUNTIME;
option go_package = "pkg/protomodel";
import "config.proto";
import "macho_info.proto";
package protomodel;
message BcellFile {
Config bcell_config = 1;
map<string, MachoInfo> macho_infos = 2;
}

View File

@ -0,0 +1,20 @@
syntax = "proto3";
option optimize_for = LITE_RUNTIME;
option go_package = "pkg/protomodel";
package protomodel;
message Config {
bool hook = 1;
bool emulator = 2;
bool repack = 3;
bool debug = 4;
bool root = 5;
bytes cert_rsa = 6;
string server_url = 7;
int32 report_type = 8;
bytes obfuscator = 9;
string remote_config_url = 10;
string domain_id = 11;
}

View File

@ -0,0 +1,34 @@
syntax = "proto3";
option optimize_for = LITE_RUNTIME;
option go_package = "pkg/protomodel";
package protomodel;
message MachoInfo {
enum PointerSize {
invalid = 0;
p32 = 1;
p64 = 2;
}
message InitPointer {
uint64 offset = 1; // offset relative to the macho header
uint64 value = 2; // address of the init function
}
// iOS library rewrite these as opcodes and dyld processes
// message LazySymbol {
// string name = 1;
// int32 dylib_ordinal = 2; // could be -1 -2
// uint32 segment = 3;
// uint32 segment_offset = 4;
// }
PointerSize pointer_size = 1;
uint64 image_base = 2;
repeated InitPointer init_pointers = 3;
// saved or read from header -> dyld_info -> lazyoff
// uint64 lazy_symbol_address = 4;
// repeated LazySymbol lazy_symbols = 5;
}

View File

@ -0,0 +1,16 @@
syntax = "proto3";
option optimize_for = LITE_RUNTIME;
option go_package = "pkg/protomodel";
package protomodel;
message Block {
int32 key = 1;
bytes value = 2;
}
message SignedData {
repeated Block blocks = 1;
}

View File

@ -0,0 +1,112 @@
import os
import subprocess
import plistlib
import sys
import io
FAT_MAGIC = b'\xbe\xba\xfe\xca'
FAT_CIGAM = b'\xca\xfe\xba\xbe'
MH_MAGIC = b'\xce\xfa\xed\xfe'
MH_CIGAM = b'\xfe\xed\xfa\xce'
MH_MAGIC_64 = b'\xcf\xfa\xed\xfe'
MH_CIGAM_64 = b'\xfe\xed\xfa\xcd'
LC_ENCRYPTION_INFO = 0x21
LC_ENCRYPTION_INFO_64 = 0x2C
class Macho:
class LoadCommand():
def __init__(self, cmd, buf):
self.cmd = cmd
self.buf = buf
class EncryptionInfo():
def __init__(self, load_command):
cryptid = load_command.buf[4*4:4*4+4]
self.encrypted = cryptid != b'\x00\x00\x00\x00'
def __init__(self, buf):
self.buf = buf
self.buf.seek(0)
self.magic = self.buf.read(4)
if self.magic in [MH_MAGIC, MH_MAGIC_64]:
self.endian = "little"
else:
self.endian = "big"
if self.magic in [MH_MAGIC, MH_CIGAM]:
self.bittype = "32"
else:
self.bittype = "64"
def get_encryption_info(self):
for load_command in self.__traverse_load_commands():
if load_command.cmd in [LC_ENCRYPTION_INFO, LC_ENCRYPTION_INFO_64]:
einfo = Macho.EncryptionInfo(load_command)
return einfo.encrypted
def __traverse_load_commands(self):
ncmds = self.ncmds()
self.buf.seek(self.load_commands_start())
for i in range(ncmds):
old = self.buf.tell()
b = self.buf.read(8)
self.buf.seek(old)
cmd = int.from_bytes(b[:4], self.endian)
cmdsize = int.from_bytes(b[4:], self.endian)
yield Macho.LoadCommand(cmd, self.buf.read(cmdsize))
def ncmds(self):
self.buf.seek(16)
ncmds = self.buf.read(4)
return int.from_bytes(ncmds, self.endian)
def load_commands_start(self):
if self.bittype == "32":
return 7 * 4
else:
return 8 * 4
# return a list of Macho
def load_binary(path):
f = open(path, 'rb')
magic = f.read(4)
if magic in [FAT_CIGAM, FAT_MAGIC]:
# do fat stuff
print("fat not supported yet")
pass
else:
f.seek(0)
return [Macho(f)]
return []
def encryption_status(path):
machos = load_binary(path)
for macho in machos:
print(f"{path}: {macho.magic} | encryption:{macho.get_encryption_info()}")
def get_app(app_folder):
plist = os.path.join(app_folder, "Info.plist")
plist = plistlib.load(open(plist, "rb"))
binary_name = plist.get("CFBundleExecutable", None)
return os.path.join(app_folder, binary_name)
if __name__ == "__main__":
app_folder = sys.argv[1]
pjoin = os.path.join
framework_folder = pjoin(app_folder, "Frameworks")
plugin_folder = pjoin(app_folder, "Plugins")
app = get_app(app_folder)
encryption_status(app)
for framework in os.listdir(framework_folder):
app = get_app(pjoin(framework_folder, framework))
encryption_status(app)

View File

@ -0,0 +1,34 @@
import os
import subprocess
pjoin = os.path.join
app_folder = sys.args[1]
framework_folder = pjoin(app_folder, "Frameworks")
plugin_folder = pjoin(app_folder, "Plugins")
for framework in os.listdir(framework_folder):
binary, subfix = framework.split('.')
# should read Info.plist to find out the Binary
path = ( pjoin(framework_folder, framework, binary)
, pjoin(framework_folder, framework)
) [subfix != "framework"]
r = subprocess.run([
'codesign', '--remove-signature', path
], capture_output=True)
print(path, r.returncode, r.stderr)
if (os.path.exists(plugin_folder)):
for plugin in os.listdir():
binary, _ = plugin.split('.')
r = subprocess.run([
'codesign', '--remove-signature',
pjoin(app_folder, "Plugins", plugin, binary),
], capture_output=True)
print(r.args, r.returncode, r.stderr)

View File

@ -0,0 +1,35 @@
import os
import subprocess
pjoin = os.path.join
app_folder = sys.args[1]
cert = sys.args[2]
framework_folder = pjoin(app_folder, "Frameworks")
plugin_folder = pjoin(app_folder, "Plugins")
for framework in os.listdir(framework_folder):
binary, subfix = framework.split('.')
# should read Info.plist to find out the Binary
path = ( pjoin(framework_folder, framework, binary)
, pjoin(framework_folder, framework)
) [subfix != "framework"]
r = subprocess.run([
'codesign', '-f', '-s', cert, path
], capture_output=True)
print(path, r.returncode, r.stderr)
if (os.path.exists(plugin_folder)):
for plugin in os.listdir():
binary, _ = plugin.split('.')
r = subprocess.run([
'codesign', '-f', '-s', cert,
pjoin(app_folder, "Plugins", plugin, binary),
], capture_output=True)
print(r.args, r.returncode, r.stderr)