There is nothing technically stopping you from doing this
Great! Given that i don't want to redirect the flow and only modify the headers i think NETransparentProxyProvider might be the better option for me.
however this will be something that you need to research on your own
I didn't find any information on how to do this. Do you know if there is a function from the NETransparentProxyProvider that can give me access to the headers?
Thanks for the help btw :)
Post
Replies
Boosts
Views
Activity
Im having trouble understanding how this transparent proxy would work.
Given that i only want to modify the headers of this requests flowing through my proxy and not re-route them i should return false in handleNewFlow ?
Im trying to follow the link you provided but i can't seem to grasp how this works.
let flow: NEAppProxyTCPFlow
let connection: NWConnection
// Reads from the flow and writes to the remote connection.
func outboundCopier() {
flow.readData { (data, error) in
if error == nil, let readData = data, !readData.isEmpty {
connection.send(content: readData,
completion: .contentProcessed( { connectionError in
// Handle completion success or error.
// Set up another read if there is no error.
if connectionError == nil {
self.outboundCopier()
}
}))
} else {
// Handle error case or the read that contains empty data.
}
}
}
Where should i create that NWConnection? Should i initialize one with the endpoint data contained in the flow i receive? Also I asume the headers i want are contained in the data from readData but how can interpret this data and modify it?
create your outbound copier, and then open the flow with your inbound copier.
So no matter the direction of the flow (inbound/outbound) I need to use both inboundCopier and outboundCopier correct?
Thanks for the help!
Lastly, do you guys have a sample project using NETransparentProxyProvider because I could not find a single repo on github using this api.
Yes, I can get the remoteEndpoint populated
guard let socketFlow = flow as? NEFilterSocketFlow,
let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint,
let localEndpoint = socketFlow.localEndpoint as? NWHostEndpoint else {
return nil
}
if #available(macOS 11.0, *) {
if let hostname = socketFlow.remoteHostname {
// Access hostname
}
}
Ok so here is what I came up with. This works fine, the only thing I have yet to see is if it has any memory leaks. I hope this helps someone in the future :) If you have any suggestions to the func please let me know.
Also, you should first convert the audit_token to pid using the function mentioned above.
func getArgs(from pid: Int32) -> [NSString]? {
var arguments: [NSString] = []
var mib: [Int32] = [0, 0, 0]
var argsMax: Int = 0
mib[0] = CTL_KERN
mib[1] = KERN_ARGMAX
var size = MemoryLayout<Int>.stride(ofValue: argsMax)
if sysctl(&mib, 2, &argsMax, &size, nil, 0) == -1 {
return nil
}
let processArgs = UnsafeMutablePointer<CChar>.allocate(capacity: argsMax)
mib[0] = CTL_KERN
mib[1] = KERN_PROCARGS2
mib[2] = pid
size = argsMax as size_t
// Get process arguments
if sysctl(&mib, 3, processArgs, &size, nil, 0) == -1 {
return nil
}
if size <= MemoryLayout<Int>.size {
return nil
}
var numberOfArgs: Int32 = 0
//Get number of args
memcpy(&numberOfArgs, processArgs, MemoryLayout.size(ofValue: numberOfArgs))
// Initialize the pointer to the start of args
var parser: UnsafeMutablePointer<CChar> = processArgs + MemoryLayout.size(ofValue: numberOfArgs)
// Iterate until NULL terminated path
while parser < &processArgs[size] {
if 0x0 == parser.pointee {
// arrived ar argv[0]
break
}
parser += 1
}
// sanity check
if parser == &processArgs[size] {
return nil
}
while parser < &processArgs[size] {
if 0x0 != parser.pointee {
break
}
parser += 1
}
// sanity check
if parser == &processArgs[size] {
return nil
}
var argStart: UnsafeMutablePointer<CChar>? = parser
// Get all args
while parser < &processArgs[size] {
if parser.pointee == CChar(0) {
if nil != argStart {
let argument = NSString(utf8String: argStart!)
if argument != nil {
arguments.append(argument!)
}
}
argStart = parser + 1
if arguments.count == numberOfArgs {
break
}
}
parser += 1
}
// Is this free necessary?
free(processArgs)
return arguments
}
Would this work? Just accessing the NEAppProxyFlow and returning true without having to handle the flow
// NEDNSProxyProvider
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
NSLog("DNSProxyProvider: handleFlow")
if let tcpFlow = flow as? NEAppProxyTCPFlow {
let remoteHost = (tcpFlow.remoteEndpoint as! NWHostEndpoint).hostname
let remotePort = (tcpFlow.remoteEndpoint as! NWHostEndpoint).port
// Do whatever I want with this data
} else if let udpFlow = flow as? NEAppProxyUDPFlow {
let localHost = (udpFlow.localEndpoint as! NWHostEndpoint).hostname
let localPort = (udpFlow.localEndpoint as! NWHostEndpoint).port
// Do whatever I want with this data
}
return true
}
Would manually deactivating my System Extension before installing the new version help? I was thinking about doing that in the preinstall script of my pkg installer by executing the container app with a uninstall parameter like this:
/Applications/app/Contents/MacOS/app --unload-extension
Another question I had about the willCompleteAfterReboot is why is this called? I want to better understand this problem I am having about extensions needing a reboot to complete an upgrade. What is preventing the system from starting my extension without the need of a reboot?
I am using a Network Extension, if I were to add Endpoint Security to my extension would something in the activation process change? I can't see antivirus software that uses this framework requiring a reboot for every update to their extension.
Turns out I was setting the exportedInterface instead of the remoteObjectInterface on the NSXPCConnection.
Quinn, as you said, for apps as big as XCode this can be very slow. I understand I can cache the result but if I am doing this in the context of an ES_EVENT_TYPE_AUTH_EXEC I would be blocking the first execution of Xcode for a long time before my analysis finishes. What would you do in my case?
12-17-2021 Xcode Version 13.2 (13C90) MBP 13-inch,i7, 2017, macOS 11.6.1
I honestly can't believe how after all this years the bug is still there. It is extremely anoying to have to restart Xcode everytime I want to test something in playgrounds
Ok I found out what is happening but I need help fixing it. Look at this code
// Inside function that handles process exec messages
guard let copiedMessage = copyMessage(msg) else {
es_respond_auth_result(esClient.client!, msg, ES_AUTH_RESULT_ALLOW, false)
return
}
DispatchQueue.global().async {
var process: ProcessDetails? = nil
let deadline = copiedMessage.pointee.deadline
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
// This may take some time
process = ProcessDetails(process: copiedMessage.pointee.event.exec.target.pointee)
semaphore.signal()
}
_ = semaphore.wait(timeout: DispatchTime(uptimeNanoseconds: deadline - (2 * NSEC_PER_SEC)))
es_respond_auth_result(...)
freeMessage(copiedMessage)
}
I am calling description inside my ProcessDetails class, the problem is that if the timeout of the semaphore runs out I will free the copiedMessage but the initialization of ProcessDetails is still going so that results in a crash. How could I kill that seconds thread before freeing the message? Or should I somehow set another semaphore and wait for that thread to finish (even though I already responded to the message) to free the message?
As per Quinns request here is a more detailed version of my code
static func handleProcessExec(_ msg: UnsafePointer<es_message_t>) {
let target = msg.pointee.event.exec.target.pointee
guard msg.pointee.process.pointee.is_es_client == false else {
es_respond_auth_result(esClient.client!, msg, ES_AUTH_RESULT_ALLOW, true)
return
}
// copy message so it can be used on another thread
guard let copiedMessage = copyMessage(msg) else {
es_respond_auth_result(esClient.client!, msg, ES_AUTH_RESULT_DENY, false)
return
}
DispatchQueue.global().async {
var process: ProcessDetails? = nil
let deadline = DispatchTime(uptimeNanoseconds: copiedMessage.pointee.deadline - (2*NSEC_PER_SEC))
let semaphore = DispatchSemaphore(value: 0)
// RuleResult contains what rule matched and if we should allow/deny
var result = RuleResult()
let timestamp = copiedMessage.pointee.time.tv_sec
DispatchQueue.global().async {
process = ProcessDetails(process: copiedMessage.pointee.event.exec.target.pointee)
ESEventRulesRunner.runProcessExecRules(process: process!, &result)
semaphore.signal()
}
_ = semaphore.wait(timeout: deadline)
es_respond_auth_result(esClient.client!, copiedMessage, result.verdict, false)
if result.verdict == ES_AUTH_RESULT_DENY {
// Log event to api
let event = ESEvent(process: process!, timestamp: timestamp, ruleType: result.type)
Api.postEvent(event: event)
}
freeMessage(copiedMessage)
}
}
Hi, I am having the same issue that Uddalak described. But in my case this only happens when I attach Xcode's debugger to my system extension. As soon as I do it the entire system becomes unresponsive for 30 secs until my app is killed (no crash log). It doesn't matter if I use the 0x7fffffff or 0xffffffff flags or if I cache the response or not.
In my event handler block I allow all events, like this:
es_respond_flags_result(client, msg, 0x7fffffff, true)