├── Image ├── 01.png ├── 02.png ├── 03.jpg └── 04.jpg ├── LICENSE ├── README.md ├── main.m └── makefile /Image/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwo/patch_webinspect/4cb1e4ec0ee49ac7e6ee8dd847180801615b9348/Image/01.png -------------------------------------------------------------------------------- /Image/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwo/patch_webinspect/4cb1e4ec0ee49ac7e6ee8dd847180801615b9348/Image/02.png -------------------------------------------------------------------------------- /Image/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwo/patch_webinspect/4cb1e4ec0ee49ac7e6ee8dd847180801615b9348/Image/03.jpg -------------------------------------------------------------------------------- /Image/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zwo/patch_webinspect/4cb1e4ec0ee49ac7e6ee8dd847180801615b9348/Image/04.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zwo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enable debugging any App's WebView on MacOS 2 | 3 | This is a PoC to enable debugging WebView in any App, by patching `WebInspect` the dylib regulating which App can be debugged by Safari. 4 | 5 | The idea is to tweak method `-[RWIRelayDelegateMac _allowApplication:bundleIdentifier:]` to always return TRUE. 6 | 7 | As notice from the disassembled flow diagram, function `isProxyApplication` is one of the judgement conditions. Moreover, from the opcodes, we see `bl` is set before test, so we can change `test al, al` to `test bl, bl`. 8 | 9 | ![Flow diagram](Image/01.png) 10 | 11 | Just get it done by patching opcode `84 CO` to `84 DB`. However, the location of this opcode may vary in different OS versions. In this example is 0x6daaf. 12 | 13 | ![opcode](Image/02.png) 14 | 15 | ### How to use 16 | 17 | First of all, disable SIP (System Integrity Protection), otherwise, `task_for_pid` won't work. 18 | 19 | **Warning!** disabling SIP is at your own risk, you should make sure your environment is safe by yourself. 20 | 1. Drag `WebInspector` into your favorite disassembler (IDA, Hopper, etc.) and analyze, the path is as below. 21 | 22 | ```bash 23 | /System/Library/PrivateFrameworks/WebInspector.framework/Versions/A/WebInspector 24 | ``` 25 | By searching `_allowApplication`, you can locate the method `-[RWIRelayDelegateMac _allowApplication:bundleIdentifier:]`, where you can find the opcodes to test for `isProxyApplication`'s result, as the second image shown above. 26 | 27 | **Or** you can get the address with lldb: 28 | ```bash 29 | # start safari 30 | lldb -p $(pgrep webinspectord) 31 | ``` 32 | 33 | In lldb run the following: 34 | ``` 35 | image lookup -v -r -s "allowApplication" 36 | # get the adress from range range = [0x00007ff927e6692f-0x00007ff927e669d5) 37 | disass -s 0x00007ff927e6692f -c 50 38 | # find the correct address and subtract current aslr_offset 39 | ``` 40 | 41 | Open `main.m`, scroll to the last line of code, change the visual memory address `0x6c660` to that you see on your disassembler/debugger. 42 | 43 | ```c 44 | patch_mem(remoteTask, aslr_offset+0x6c660, sizeof(unsigned short), 0xc084, 0xdb84); 45 | ``` 46 | 47 | 2. In your terminal, compile it with make command 48 | ```bash 49 | $ make 50 | ``` 51 | 52 | 3. Open Safari.app, run with sudo privilege 53 | ```bash 54 | $ sudo ./patchwebinspect 55 | ``` 56 | 57 | If the program gives a successful prompt, then you can debug any WebView. This program only patchs memory, so each time Safari relaunches, the effect is gone, and need to re-patch. The following images show WeChat's WebView can be debugged after patched. 58 | 59 | ![WeChat 1](Image/03.jpg) 60 | 61 | ![WeChat 2](Image/04.jpg) 62 | 63 | ### MIT License 64 | 65 | Copyright (c) 2019 zwo 66 | 67 | Permission is hereby granted, free of charge, to any person obtaining a copy 68 | of this software and associated documentation files (the "Software"), to deal 69 | in the Software without restriction, including without limitation the rights 70 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 71 | copies of the Software, and to permit persons to whom the Software is 72 | furnished to do so, subject to the following conditions: 73 | 74 | The above copyright notice and this permission notice shall be included in all 75 | copies or substantial portions of the Software. 76 | 77 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 78 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 79 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 80 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 81 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 82 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 83 | SOFTWARE. -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | 4 | #import 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | NSArray* matchStringToRegexString(NSString *string, NSString *regexStr) 22 | { 23 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionCaseInsensitive error:nil]; 24 | 25 | NSArray * matches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])]; 26 | 27 | 28 | NSMutableArray *array = [NSMutableArray array]; 29 | 30 | for (NSTextCheckingResult *match in matches) { 31 | 32 | for (int i = 0; i < [match numberOfRanges]; i++) { 33 | NSString *component = [string substringWithRange:[match rangeAtIndex:i]]; 34 | 35 | [array addObject:component]; 36 | 37 | } 38 | 39 | } 40 | 41 | return array; 42 | } 43 | 44 | pid_t getProcessByName(const char *name) 45 | { 46 | int procCnt = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); 47 | pid_t pids[1024]; 48 | memset(pids, 0, sizeof pids); 49 | proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids)); 50 | 51 | for (int i = 0; i < procCnt; i++) 52 | { 53 | if (!pids[i]) continue; 54 | char curPath[PROC_PIDPATHINFO_MAXSIZE]; 55 | char curName[PROC_PIDPATHINFO_MAXSIZE]; 56 | memset(curPath, 0, sizeof curPath); 57 | proc_pidpath(pids[i], curPath, sizeof curPath); 58 | unsigned long len = strlen(curPath); 59 | if (len) 60 | { 61 | unsigned long pos = len; 62 | while (pos && curPath[pos] != '/') --pos; 63 | strcpy(curName, curPath + pos + 1); 64 | if (!strcmp(curName, name)) 65 | { 66 | return pids[i]; 67 | } 68 | } 69 | } 70 | return 0; 71 | } 72 | 73 | void patch_mem(task_t task, uint64_t address, mach_vm_size_t size, unsigned short original_mem,unsigned short patched_mem) 74 | { 75 | fprintf(stdout, "patch memory at address 0x%llx\n", address); 76 | vm_offset_t data; 77 | mach_msg_type_number_t dataCnt; 78 | kern_return_t ret; 79 | ret = mach_vm_read(task, address, size, &data, &dataCnt); 80 | if (ret != KERN_SUCCESS) 81 | { 82 | fprintf(stderr, "can't read memory\n"); 83 | return; 84 | } 85 | unsigned int *ptr=(unsigned int *)data; 86 | if (*ptr == original_mem) 87 | { 88 | fprintf(stdout, "Correct process version! Prepare to patch\n"); 89 | ret = mach_vm_protect(task,address,size,FALSE ,VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE); 90 | if (ret != KERN_SUCCESS) 91 | { 92 | fprintf (stderr, "Unable to change protect mode: %s. Cannot continue!\n", mach_error_string(ret)); 93 | return; 94 | } 95 | ret = mach_vm_write(task, address, (vm_offset_t)&patched_mem, (mach_msg_type_number_t)size); 96 | if (ret != KERN_SUCCESS) 97 | { 98 | fprintf (stderr, "Unable to change protect mode: %s. Cannot continue!\n", mach_error_string(ret)); 99 | return; 100 | } 101 | mach_vm_deallocate(mach_task_self(), data, dataCnt); 102 | mach_vm_read(task, address, size, &data, &dataCnt); 103 | ptr=(unsigned int *)data; 104 | if (*ptr == patched_mem) 105 | { 106 | fprintf(stdout, "Patch Successfull!\n"); 107 | }else{ 108 | fprintf(stderr, "Not patched :(\n"); 109 | } 110 | mach_vm_deallocate(mach_task_self(), data, dataCnt); 111 | }else if (*ptr == patched_mem) { 112 | fprintf(stdout, "Already patched\n"); 113 | mach_vm_deallocate(mach_task_self(), data, dataCnt); 114 | }else{ 115 | fprintf(stderr, "Incorrect version of process or ASLR Offset\n"); 116 | mach_vm_deallocate(mach_task_self(), data, dataCnt); 117 | } 118 | } 119 | 120 | int main(int argc, const char * argv[]) { 121 | @autoreleasepool { 122 | 123 | uid_t uid = getuid(); 124 | if (uid != 0) 125 | { 126 | fprintf(stderr, "Should be run as root to patch webinspectord!\n"); 127 | return -1; 128 | } 129 | pid_t webinspectord_pid = getProcessByName("webinspectord"); 130 | if (!webinspectord_pid) 131 | { 132 | fprintf(stderr, "Error: webinspectord not running, start some app to reload webinspectord\n"); 133 | return -1; 134 | } 135 | fprintf(stdout, "webinspectord's pid is %d\n", webinspectord_pid); 136 | task_t remoteTask; 137 | mach_error_t kr = 0; 138 | kr = task_for_pid(mach_task_self(), webinspectord_pid, &remoteTask); 139 | if (kr != KERN_SUCCESS) { 140 | fprintf (stderr, "Unable to call task_for_pid on pid %d: %s. Cannot continue!\n",webinspectord_pid, mach_error_string(kr)); 141 | return (-1); 142 | } 143 | NSTask *task=[NSTask new]; 144 | task.launchPath = @"/bin/bash"; 145 | task.arguments=@[@"-c", [NSString stringWithFormat:@"vmmap %d", webinspectord_pid]]; 146 | NSPipe *pipe; 147 | pipe = [NSPipe pipe]; 148 | [task setStandardOutput: pipe]; 149 | NSFileHandle *file = [pipe fileHandleForReading]; 150 | [task launch]; 151 | 152 | NSData *data = [file readDataToEndOfFile]; 153 | 154 | NSString *outputString = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; 155 | NSArray *lines=[outputString componentsSeparatedByString:@"\n"]; 156 | uint64_t aslr_offset=0; 157 | for (NSString *line in lines) { 158 | if ([line rangeOfString:@"WebInspector."].location != NSNotFound) { 159 | NSString *regStr=@"\\b0*7ff\\w+\\b"; 160 | NSArray *arr=matchStringToRegexString(line, regStr); 161 | NSString *offsetStr=arr[0]; 162 | NSScanner *scanner = [NSScanner scannerWithString:offsetStr]; 163 | [scanner scanHexLongLong:&aslr_offset]; 164 | break; 165 | } 166 | } 167 | if (aslr_offset == 0) { 168 | fprintf(stderr, "WebInspector's memory offset can't be figured out!\n"); 169 | } 170 | 171 | fprintf(stdout, "aslr_offset 0x%llx\n", aslr_offset); 172 | patch_mem(remoteTask, aslr_offset+0x81974, sizeof(unsigned short), 0xc084, 0xdb84); 173 | } 174 | return 0; 175 | } 176 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: 2 | clang ./main.m -fobjc-link-runtime -o patchwebinspect 3 | 4 | --------------------------------------------------------------------------------