Skip to content

Commit 81b4b55

Browse files
committed
Initial commit
1 parent 7b7e62b commit 81b4b55

File tree

5 files changed

+368
-0
lines changed

5 files changed

+368
-0
lines changed

LICENSE

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
MIT License
2+
3+
Copyright (c) 2017, John Holdsworth
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
6+
software and associated documentation files (the "Software"), to deal in the Software
7+
without restriction, including without limitation the rights to use, copy, modify, merge,
8+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
9+
to whom the Software is furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
17+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Package.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import PackageDescription
2+
3+
let package = Package(
4+
name: "AndroidInjection",
5+
dependencies: [
6+
.Package(url: "https://github.com/SwiftJava/swift-android-zlib.git", majorVersion: 1),
7+
]
8+
)

Sources/AndroidInjection.swift

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
//
2+
// AndroidInjection.swift
3+
// swift-android-injection
4+
//
5+
// Created by John Holdsworth on 15/09/2017.
6+
// Copyright © 2017 John Holdsworth. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import zlib
11+
12+
private var pointerSize = MemoryLayout<uintptr_t>.size
13+
private let INJECTION_PORT: UInt16 = 31441
14+
15+
func htons(_ port: UInt16) -> UInt16 {
16+
return (port << 8) + (port >> 8)
17+
}
18+
let ntohs = htons
19+
20+
func sockaddr_cast(_ p: UnsafeMutableRawPointer) -> UnsafeMutablePointer<sockaddr> {
21+
return p.assumingMemoryBound(to: sockaddr.self)
22+
}
23+
24+
protocol Kinded {
25+
var offset: Int32 { get }
26+
var kind: Int32 { get }
27+
}
28+
29+
open class AndroidInjection {
30+
31+
open class func connectAndRun(forMainThread: @escaping (@escaping () -> ()) -> ()) {
32+
DispatchQueue.global(qos: .background).async {
33+
let serverSocket = connectTo(ipAddress: androidInjectionHost, INJECTION_APPNAME: "Injection")
34+
if serverSocket < 0 {
35+
return
36+
}
37+
let serverWrite = fdopen(serverSocket, "w")
38+
NSLog("Injection: Connected to \(androidInjectionHost)")
39+
40+
var value: Int32 = Int32(INJECTION_PORT)
41+
let valueLength = MemoryLayout.size(ofValue: value)
42+
if serverWrite == nil || fwrite(&value, 1, valueLength, serverWrite) != valueLength {
43+
NSLog("Injection: Could not write magic to %d %p: %s", serverSocket, serverWrite!, strerror(errno))
44+
return
45+
}
46+
47+
if !#file.withCString( {
48+
filepath in
49+
value = Int32(strlen(filepath))
50+
return fwrite(&value, 1, valueLength, serverWrite) == valueLength &&
51+
fwrite(filepath, 1, Int(value), serverWrite) == value
52+
} ) {
53+
NSLog("Injection: Could not write filepath to %d %p: %s", serverSocket, serverWrite!, strerror(errno))
54+
return
55+
}
56+
fflush(serverWrite)
57+
58+
var injectionNumber = 0
59+
let serverRead = fdopen(serverSocket, "r")
60+
var compressedLength: Int32 = 0, uncompressedLength: Int32 = 0
61+
62+
while fread(&compressedLength, 1, valueLength, serverRead) == valueLength &&
63+
fread(&uncompressedLength, 1, valueLength, serverRead) == valueLength,
64+
var compressedBuffer = malloc(Int(compressedLength)),
65+
var uncompressedBuffer = malloc(Int(uncompressedLength)),
66+
fread(compressedBuffer, 1, Int(compressedLength), serverRead) == compressedLength {
67+
defer {
68+
free(uncompressedBuffer)
69+
free(compressedBuffer)
70+
}
71+
if compressedLength == valueLength && uncompressedLength == valueLength {
72+
continue
73+
}
74+
75+
NSLog("Injection: received %d/%d bytes", compressedLength, uncompressedLength)
76+
77+
var destLen = uLongf(uncompressedLength)
78+
if uncompress(uncompressedBuffer.assumingMemoryBound(to: Bytef.self), &destLen,
79+
compressedBuffer.assumingMemoryBound(to: Bytef.self),
80+
uLong(compressedLength)) != Z_OK || destLen != uLongf(uncompressedLength) {
81+
NSLog("Injection: uncompression failure")
82+
break
83+
}
84+
85+
injectionNumber += 1
86+
let libraryPath = NSTemporaryDirectory()+"injection\(injectionNumber).so"
87+
let libraryFILE = fopen(libraryPath, "w")
88+
if libraryFILE == nil || fwrite(uncompressedBuffer, 1, Int(uncompressedLength), libraryFILE) != uncompressedLength {
89+
NSLog("Injection: Could not write library file")
90+
break
91+
}
92+
fclose(libraryFILE)
93+
94+
forMainThread( {
95+
NSLog("Injection: Wrote to \(libraryPath), injecting...")
96+
let error = loadAndInject(library: libraryPath)
97+
var status = Int32(error == nil ? 0 : strlen(error))
98+
if fwrite(&status, 1, valueLength, serverWrite) != valueLength {
99+
NSLog("Injection: Could not write status")
100+
}
101+
if error != nil && fwrite(error, 1, Int(status), serverWrite) != status {
102+
NSLog("Injection: Could not write error string")
103+
}
104+
fflush(serverWrite)
105+
NSLog("Injection complete.")
106+
} )
107+
}
108+
109+
NSLog("Injection loop exits")
110+
fclose(serverWrite)
111+
fclose(serverRead)
112+
}
113+
}
114+
115+
open class func connectTo(ipAddress: UnsafePointer<Int8>, INJECTION_APPNAME: UnsafePointer<Int8>) -> Int32 {
116+
var loaderAddr = sockaddr_in()
117+
118+
loaderAddr.sin_family = sa_family_t(AF_INET)
119+
inet_aton(ipAddress, &loaderAddr.sin_addr)
120+
loaderAddr.sin_port = htons(INJECTION_PORT)
121+
122+
NSLog("%s attempting connection to: %s:%d", INJECTION_APPNAME, ipAddress, INJECTION_PORT)
123+
124+
var loaderSocket: Int32 = 0, optval: Int32 = 1
125+
loaderSocket = socket(Int32(loaderAddr.sin_family), SOCK_STREAM, 0)
126+
if loaderSocket < 0 {
127+
NSLog("%s: Could not open socket for injection: %s", INJECTION_APPNAME, strerror(errno))
128+
}
129+
else if setsockopt(loaderSocket, Int32(IPPROTO_TCP), TCP_NODELAY, &optval, socklen_t(MemoryLayout.size(ofValue: optval))) < 0 {
130+
NSLog("%s: Could not set TCP_NODELAY: %s", INJECTION_APPNAME, strerror(errno))
131+
}
132+
else if connect(loaderSocket, sockaddr_cast(&loaderAddr), socklen_t(MemoryLayout.size(ofValue: loaderAddr))) < 0 {
133+
NSLog("%s could not connect: %s", INJECTION_APPNAME, strerror(errno))
134+
}
135+
else {
136+
return loaderSocket
137+
}
138+
close(loaderSocket)
139+
return -1
140+
}
141+
142+
open class func loadAndInject(library: String) -> UnsafePointer<Int8>? {
143+
guard let libHandle = dlopen(library, Int32(RTLD_LAZY)) else {
144+
let error = dlerror()
145+
NSLog("Load of \(library) failed - \(String(cString: error!))")
146+
return error
147+
}
148+
149+
var info = Dl_info()
150+
guard dladdr(&pointerSize, &info) != 0 else {
151+
NSLog("Locating app library failed")
152+
return nil
153+
}
154+
155+
guard let mainHandle = dlopen(info.dli_fname, Int32(RTLD_LAZY | RTLD_NOLOAD)) else {
156+
NSLog("Load of \(String(cString: info.dli_fname)) failed")
157+
return nil
158+
}
159+
160+
var processed = [UnsafeMutablePointer<UInt8>: Bool]()
161+
162+
struct TypeEntry: Kinded {
163+
let offset: Int32
164+
let kind: Int32
165+
}
166+
167+
process(libHandle: libHandle, mainHandle: mainHandle,
168+
entrySymbol: ".swift2_type_metadata_start",
169+
entryType: TypeEntry.self, processed: &processed)
170+
171+
struct Conformance: Kinded {
172+
let skip1: Int32
173+
let offset: Int32
174+
let skip2: Int32
175+
let kind: Int32
176+
}
177+
178+
process(libHandle: libHandle, mainHandle: mainHandle,
179+
entrySymbol: ".swift2_protocol_conformances_start",
180+
entryType: Conformance.self, pointerOffset: pointerSize, processed: &processed)
181+
182+
return nil
183+
}
184+
185+
class func process<T: Kinded>(libHandle: UnsafeMutableRawPointer, mainHandle: UnsafeMutableRawPointer,
186+
entrySymbol: String, entryType: T.Type, pointerOffset: Int = 0,
187+
processed: inout [UnsafeMutablePointer<UInt8>: Bool] ) {
188+
guard let conformance = dlsym(libHandle, entrySymbol) else {
189+
NSLog("Could not locate \(entrySymbol) entries")
190+
return
191+
}
192+
193+
let ptr = conformance.assumingMemoryBound(to: UInt64.self)
194+
let len = Int(ptr.pointee)
195+
196+
let entrySize = MemoryLayout<T>.size
197+
let entries = len / entrySize
198+
NSLog("\(entrySymbol) entries: \(entries)")
199+
200+
let table = (ptr+1).withMemoryRebound(to: UInt8.self, capacity: len) { $0 }
201+
let start = (ptr+1).withMemoryRebound(to: T.self, capacity: entries) { $0 }
202+
203+
for i in 0 ..< entries {
204+
if start[i].kind == 15 {
205+
let metadata = UnsafeMutablePointer(table + Int(start[i].offset) + i * entrySize + pointerOffset)
206+
if processed[metadata] == true {
207+
continue
208+
}
209+
metadata.withMemoryRebound(to: ClassMetadataSwift.self, capacity: 1) {
210+
swizzleClass(classMetadata: $0, mainHandle: mainHandle)
211+
}
212+
processed[metadata] = true
213+
}
214+
}
215+
}
216+
217+
static var originals = [String: UnsafeMutablePointer<ClassMetadataSwift>]()
218+
219+
open class func swizzleClass(classMetadata: UnsafeMutablePointer<ClassMetadataSwift>, mainHandle: UnsafeMutableRawPointer) {
220+
var info = Dl_info()
221+
guard dladdr(classMetadata, &info) != 0, let classSymbol = info.dli_sname else {
222+
NSLog("Could not locate class")
223+
return
224+
}
225+
226+
let classKey = String(cString: classSymbol)
227+
guard let existingClass = originals[classKey] ?? dlsym(mainHandle, classSymbol)?
228+
.assumingMemoryBound(to: ClassMetadataSwift.self) else {
229+
NSLog("Could not locate original class for \(String(cString: classSymbol))")
230+
return
231+
}
232+
originals[classKey] = existingClass
233+
234+
func byteAddr<T>(_ location: UnsafeMutablePointer<T>) -> UnsafeMutablePointer<UInt8> {
235+
return location.withMemoryRebound(to: UInt8.self, capacity: 1) { $0 }
236+
}
237+
238+
let vtableOffset = byteAddr(&existingClass.pointee.IVarDestroyer) - byteAddr(existingClass)
239+
let vtableLength = Int(existingClass.pointee.ClassSize) - pointerSize * 2 - vtableOffset
240+
NSLog("\(unsafeBitCast(classMetadata, to: AnyClass.self)), vtable length: \(vtableLength)")
241+
memcpy(byteAddr(existingClass) + vtableOffset, byteAddr(classMetadata) + vtableOffset, vtableLength)
242+
}
243+
}

Sources/AndroidInjectionHost.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
let androidInjectionHost = "192.168.1.14"

Sources/SwiftInternals.swift

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//
2+
// Internals.swift
3+
// XprobeSwift
4+
//
5+
// Created by John Holdsworth on 22/12/2016.
6+
// Copyright © 2016 John Holdsworth. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/** pointer to a function implementing a Swift method */
12+
public typealias SIMP = @convention(c) (_: AnyObject) -> Void
13+
14+
/**
15+
Layout of a class instance. Needs to be kept in sync with ~swift/include/swift/Runtime/Metadata.h
16+
*/
17+
public struct ClassMetadataSwift {
18+
19+
public let MetaClass: uintptr_t = 0, SuperClass: uintptr_t = 0
20+
public let CacheData1: uintptr_t = 0, CacheData2: uintptr_t = 0
21+
22+
public let Data: uintptr_t = 0
23+
24+
/// Swift-specific class flags.
25+
public let Flags: UInt32 = 0
26+
27+
/// The address point of instances of this type.
28+
public let InstanceAddressPoint: UInt32 = 0
29+
30+
/// The required size of instances of this type.
31+
/// 'InstanceAddressPoint' bytes go before the address point;
32+
/// 'InstanceSize - InstanceAddressPoint' bytes go after it.
33+
public let InstanceSize: UInt32 = 0
34+
35+
/// The alignment mask of the address point of instances of this type.
36+
public let InstanceAlignMask: UInt16 = 0
37+
38+
/// Reserved for runtime use.
39+
public let Reserved: UInt16 = 0
40+
41+
/// The total size of the class object, including prefix and suffix
42+
/// extents.
43+
public let ClassSize: UInt32 = 0
44+
45+
/// The offset of the address point within the class object.
46+
public let ClassAddressPoint: UInt32 = 0
47+
48+
/// An out-of-line Swift-specific description of the type, or null
49+
/// if this is an artificial subclass. We currently provide no
50+
/// supported mechanism for making a non-artificial subclass
51+
/// dynamically.
52+
public let Description: uintptr_t = 0
53+
54+
/// A function for destroying instance variables, used to clean up
55+
/// after an early return from a constructor.
56+
public var IVarDestroyer: SIMP? = nil
57+
58+
// After this come the class members, laid out as follows:
59+
// - class members for the superclass (recursively)
60+
// - metadata reference for the parent, if applicable
61+
// - generic parameters for this class
62+
// - class variables (if we choose to support these)
63+
// - "tabulated" virtual methods
64+
65+
}
66+
67+
//#if swift(>=3.0)
68+
// // not public in Swift3
69+
// @_silgen_name("swift_demangle")
70+
// private
71+
// func _stdlib_demangleImpl(
72+
// mangledName: UnsafePointer<CChar>?,
73+
// mangledNameLength: UInt,
74+
// outputBuffer: UnsafeMutablePointer<UInt8>?,
75+
// outputBufferSize: UnsafeMutablePointer<UInt>?,
76+
// flags: UInt32
77+
// ) -> UnsafeMutablePointer<CChar>?
78+
//
79+
// public func _stdlib_demangleName(_ mangledName: String) -> String {
80+
// return mangledName.utf8CString.withUnsafeBufferPointer {
81+
// (mangledNameUTF8) in
82+
//
83+
// let demangledNamePtr = _stdlib_demangleImpl(
84+
// mangledName: mangledNameUTF8.baseAddress,
85+
// mangledNameLength: UInt(mangledNameUTF8.count - 1),
86+
// outputBuffer: nil,
87+
// outputBufferSize: nil,
88+
// flags: 0)
89+
//
90+
// if let demangledNamePtr = demangledNamePtr {
91+
// let demangledName = String(cString: demangledNamePtr)
92+
// free(demangledNamePtr)
93+
// return demangledName
94+
// }
95+
// return mangledName
96+
// }
97+
// }
98+
//#endif
99+

0 commit comments

Comments
 (0)