aozhimin / iOS-Monitor-Platform

:books: iOS 性能监控 SDK —— Wedjat(华狄特)开发过程的调研和整理
https://aozhimin.github.io/iOS-Monitor-Platform/
MIT License
2.6k stars 494 forks source link

How to hook 'BSD socket' with fishhook tool? #1

Open 0x4d4746h opened 7 years ago

0x4d4746h commented 7 years ago

Anybody succeed for connect() and send() methods?

0x4d4746h commented 7 years ago

Test on the real phone. Not simulator

aozhimin commented 7 years ago

@0x4d4746h I'm not sure, according to some person's tests, the library fishhook of Facebook,Can't hook function of BSD socket like connect(), so probably Apple modified the underlying implementation, without a standard way of BSD socket

aozhimin commented 7 years ago

And I will confirm in the future whether it is true, thanks @0x4d4746h

aozhimin commented 7 years ago

@0x4d4746h I have tested in the machine and simulator, we can hook the connect()and end() methods with fishhook. My test as follows:

image

image

vzrao commented 7 years ago

@aozhimin I am able to hook send() but not connect(). Can pls you confirm?

aozhimin commented 7 years ago

@vzrao I'm sure it's ok. I don't know where your problem is.

vzrao commented 7 years ago

@aozhimin Is it possible to share the related .h and .m files? If not, where are these hook_xxx functions defined, in AppDelegate.m or some other individual file? Thanks!

aozhimin commented 7 years ago

@vzrao I'm so sorry see your reply now. In fact, you can declare these hook_xxx methods in any files, Here is the sample code:

static int  (*orig_connect)(int, const struct sockaddr *, socklen_t);

static ssize_t  (*orig_send)(int, const void *, size_t, int);

int hook_connect(int socket, const struct sockaddr *addr, socklen_t address_len) {
    char ipString[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &(((struct sockaddr_in*)addr)->sin_addr), ipString, INET_ADDRSTRLEN);
    NSLog(@"ip:%s", ipString);
    return orig_connect(socket, addr, address_len);
}

ssize_t hook_send(int socket, const void * buffer, size_t length, int flags) {
    NSLog(@"send:%s", buffer);
    return orig_send(socket, buffer, length, flags);
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        struct rebinding socket_rebinding = {"connect", hook_connect, (void *)&orig_connect };
        struct rebinding send_rebinding = {"send", hook_send, (void *)&orig_send };
        rebind_symbols((struct rebinding[2]){socket_rebinding, send_rebinding}, 2);
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
vzrao commented 7 years ago

@aozhimin thank you for replying. I am still not able to see connect() got hooked.

my code is to download some pictures like below:

NSURLSessionDataTask imageTask = [session dataTaskWithRequest:request completionHandler: ^(NSData data, NSURLResponse response, NSError error) { if( nil != response ){ if (data) { NSLog(@"Fetching image succeed"); // process the fetched image
}else{ NSLog(@"Fetching image failed"); } }else{ NSLog(@"Fetching image failed"); }

                              }];
[imageTask resume];

send() and recvmsg() were hooked/observed. Since these functions use the same header <sys/socket.h> as connect(), they should be in the same library.

will connect() happen every time there is network/http request? I doubt connect() is not called at all in my case. But NSURLSessionDataTask eventually will use BSD functions, right?

Thanks!

aozhimin commented 7 years ago

@vzrao According to my test, when use the NSURLSession API to make HTTP requests, the BSD socket functions will eventually be called, such as connect() function. Here is my test code:

static NSString * const kWANIPQueryUrl       = @"http://ifconfig.co/ip";
NSURLRequest *request = [NSURLRequest requestWithURL:url
                                         cachePolicy:NSURLRequestUseProtocolCachePolicy
                                     timeoutInterval:3];
[[session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    NSLog(@"response:%@", response);
}] resume];

and here is the code for hook method of BSD socket:

static int  (*orig_connect)(int, const struct sockaddr *, socklen_t);

static ssize_t  (*orig_send)(int, const void *, size_t, int);

int hook_connect(int socket, const struct sockaddr *addr, socklen_t address_len) {
    char ipString[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &(((struct sockaddr_in*)addr)->sin_addr), ipString, INET_ADDRSTRLEN);
    NSLog(@"ip:%s", ipString);
    return orig_connect(socket, addr, address_len);
}

ssize_t hook_send(int socket, const void * buffer, size_t length, int flags) {
    NSLog(@"send:%s", buffer);
    return orig_send(socket, buffer, length, flags);
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        struct rebinding socket_rebinding = {"connect", hook_connect, (void *)&orig_connect };
        struct rebinding send_rebinding = {"send", hook_send, (void *)&orig_send };
        rebind_symbols((struct rebinding[2]){socket_rebinding, send_rebinding}, 2);
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

The following is the output of the Xcode console:

2017-10-16 20:48:25.457 Debug[1964:43193] ip:188.113.88.193

The following screenshot is from Charles:

image

The IP address 188.113.88.193 is the same as Charles' remote address, so it is proved that the NSURLSession API will eventually call the BSD Socket function.

The following is a backtrace info:

* thread #11, queue = 'com.apple.networking.connection.0x7fa47bc834c0', stop reason = breakpoint 20.1
  * frame #0: 0x0000000105a54aa9 Debug`hook_connect(socket=18, addr=0x00007fa47be21988, address_len=16) at main.m:32
    frame #1: 0x00000001084bbd32 libsystem_network.dylib`tcp_connection_destination_perform_socket_connect + 787
    frame #2: 0x000000010848d25b libsystem_network.dylib`tcp_connection_handle_destination_prepare_complete + 62
    frame #3: 0x00000001084bb5b0 libsystem_network.dylib`tcp_connection_destination_start + 421
    frame #4: 0x000000010848c712 libsystem_network.dylib`tcp_connection_start_destination + 84
    frame #5: 0x000000010848c523 libsystem_network.dylib`tcp_connection_handle_start_next_destination_helper + 198
    frame #6: 0x000000010848c649 libsystem_network.dylib`tcp_connection_handle_start_next_destination + 97
    frame #7: 0x000000010848dc1c libsystem_network.dylib`__tcp_connection_start_host_block_invoke_3 + 104
    frame #8: 0x00000001084b183b libsystem_network.dylib`tcp_connection_host_resolve_result + 1849
    frame #9: 0x00000001083e524e libsystem_dnssd.dylib`handle_addrinfo_response + 509
    frame #10: 0x00000001083e33f5 libsystem_dnssd.dylib`DNSServiceProcessResult + 665
    frame #11: 0x000000010827d49b libdispatch.dylib`_dispatch_client_callout + 8
    frame #12: 0x00000001082708a5 libdispatch.dylib`_dispatch_source_latch_and_call + 1750
    frame #13: 0x000000010826b830 libdispatch.dylib`_dispatch_source_invoke + 1057
    frame #14: 0x00000001082637fb libdispatch.dylib`_dispatch_queue_drain + 1818
    frame #15: 0x0000000108262ea9 libdispatch.dylib`_dispatch_queue_invoke + 601
    frame #16: 0x0000000108265af2 libdispatch.dylib`_dispatch_root_queue_drain + 1420
    frame #17: 0x0000000108265561 libdispatch.dylib`_dispatch_worker_thread3 + 111
    frame #18: 0x00000001085ae5a2 libsystem_pthread.dylib`_pthread_wqthread + 1299
    frame #19: 0x00000001085ae07d libsystem_pthread.dylib`start_wqthread + 13
vzrao commented 7 years ago

@aozhimin Thank you Zhimin. Are you testing connect() on real device? I was able to see connect() get hooked on emulator but not on real iPhone.

what kind of device you test on? Mine is an iPhone 6s with iOS 10.3.3(14G60).

I just copied and pasted your code into main.m and ViewController.m (viewDidLoad) with minimum modification:

ViewController.m

**main.m

import <UIKit/UIKit.h>

import "AppDelegate.h"

include <arpa/inet.h>

include "fishhook.h"

//int main(int argc, char * argv[]) { // @autoreleasepool { // return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); // } //}

static int (orig_connect)(int, const struct sockaddr , socklen_t);

static ssize_t (orig_send)(int, const void , size_t, int);

int hook_connect(int socket, const struct sockaddr addr, socklen_t address_len) { char ipString[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(((struct sockaddr_in)addr)->sin_addr), ipString, INET_ADDRSTRLEN); NSLog(@"ip:%s", ipString); return orig_connect(socket, addr, address_len); }

ssize_t hook_send(int socket, const void * buffer, size_t length, int flags) { NSLog(@"send:%s", buffer); return orig_send(socket, buffer, length, flags); }

int main(int argc, char argv[]) { @autoreleasepool { struct rebinding socket_rebinding = {"connect", hook_connect, (void )&orig_connect }; struct rebinding send_rebinding = {"send", hook_send, (void *)&orig_send }; rebind_symbols((struct rebinding[2]){socket_rebinding, send_rebinding}, 2); return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }

aozhimin commented 7 years ago

@vzrao Yep, the above test was tested on the simulator, it is only part of the test. I also tested on the real machine. iPhone 5s with iOS 8.1.3. The following figure shows the output of the console: image We still see the output like ip:, but did not see 188.113.88.193. so we hooked BSD connect() function successfully, but the NSURLSession API may not call BSD Socket functions. I will confirm this conclusion later, and then reply to you.

aozhimin commented 7 years ago

@vzrao

People have had issues with socket and/or connect in the past. One problem is that fishhook can only hook external calls, which means function calls within the same library generally cannot be hooked. In this case, calls to socket and connect from within the same library (libSystem) cannot be hooked. With the simulator, the system libraries are broken out into many sub-libraries, including libsystem_network.dylib. With many sub-libraries, this means function calls from one sub-library to another can be hooked on the simulator, but on device where there's just a single libSystem, those same calls are within the same library and cannot be hooked.

aozhimin commented 7 years ago

@vzrao There are some additional explanations. The following is the assembly code of connect() function in iOS simulator:

    0x106cc1558 <+255>: movl   %eax, -0xac(%rbp)
    0x106cc155e <+261>: movl   $0x0, -0xa8(%rbp)
    0x106cc1568 <+271>: leaq   -0xb0(%rbp), %rsi
    0x106cc156f <+278>: movl   $0x20, %edx
    0x106cc1574 <+283>: movl   %ebx, %edi
    0x106cc1576 <+285>: callq  0x106ceed7a               ; symbol stub for: connect

However, The following is in SE(10.3.2):

    0x18aa165c8 <+312>:  str    w8, [sp, #0x4c]
    0x18aa165cc <+316>:  stp    xzr, xzr, [sp, #0x58]
    0x18aa165d0 <+320>:  str    xzr, [sp, #0x50]
    0x18aa165d4 <+324>:  orr    w2, wzr, #0x20
    0x18aa165d8 <+328>:  add    x1, sp, #0x48             ; =0x48 
    0x18aa165dc <+332>:  mov    x0, x19
    0x18aa165e0 <+336>:  bl     0x18a9bc03c               ; __connect
    0x18aa165e4 <+340>:  cbnz   w0, 0x18aa16504           ; <+116>

As you can see, unlike the iOS simulator, there isn't symbol stub for: connect, it call connect() use bl and direct address, this is why can't hook connect() in the iDevice.

aozhimin commented 7 years ago

As for send() function:

        int ret ;
        Dl_info dylib_info;
        ssize_t (*fun_send)(int, const void *, size_t, int) = send;
        if ((ret = dladdr(fun_send, &dylib_info))) {
            NSLog(@"sendlib :%s", dylib_info.dli_fname);
        }

and the output of NSLog statement is as follows:

Printing description of dylib_info.dli_fname:
(const char *) dli_fname = 0x000000018a8d06f0 "/usr/lib/system/libsystem_c.dylib"
vzrao commented 7 years ago

@aozhimin Thank you Zhimin! The high level purpose is to let all the network traffic of specific apps to go through a proxy server (where bytes can be counted). We've looked at Network Extension but it requires managed devices (MDM).

Any advice?

Thanks! Rao

aozhimin commented 7 years ago

@vzrao NEAppProxyTCPFlow ? APPL Sample Code Maybe you can try Network Extension. I am trying to solve it by CFNetwork. If I have any progress will inform you immediately.

vzrao commented 6 years ago

@aozhimin

Thank you Zhimin. We tried Personal VPN and all the traffic of our app did went through our proxy. But the problem is that if there are other apps running in background at the same time, those traffic will go through the proxy as well, which is not what we want.

aozhimin commented 6 years ago

@vzrao I'm not sure if the Network Extension can filter other app traffic. I tried to hook CFStreamCreatePairWithSocket(), this works when invoke this method directly like CocoaAsyncSocket,but not work in NSURLSession API. Finally I confirmed that socket is related to the stream(CFStream or NSStream), but I have not found a breakthrough.