KISS

Keep It Simple Stupid

Removing ClearcutUploader errors when using GMaps iOS SDK

| comments

I worked on an iOS application that used the Google Maps SDK and noticed a bunch of logging when I backgrounded the application complaining that it couldn’t connect to some server:

1
2
3
4
5
6
7
8
9
2021-04-17 00:42:48.543716+0300 MyApp[73170:12130630] [connection] nw_socket_connect [C7.1:3] connect failed (fd 23) [64: Host is down]
2021-04-17 00:42:48.543851+0300 MyApp[73170:12130630] [] nw_socket_connect connect failed [64: Host is down]
2021-04-17 00:42:48.545236+0300 MyApp[73170:12130630] Connection 7: received failure notification
2021-04-17 00:42:48.545564+0300 MyApp[73170:12130630] Connection 7: failed to connect 1:64, reason -1
2021-04-17 00:42:48.545708+0300 MyApp[73170:12130630] Connection 7: encountered error(1:64)
2021-04-17 00:42:48.548091+0300 MyApp[73170:12130630] Task <170493E8-F704-4578-AB47-7B9CE76064B1>.<1> HTTP load failed, 0/0 bytes (error code: -1004 [1:64])
2021-04-17 00:42:48.951902+0300 MyApp[73170:12128623] Task <170493E8-F704-4578-AB47-7B9CE76064B1>.<1> finished with error [-1004] Error Domain=NSURLErrorDomain Code=-1004 "Could not connect to the server." UserInfo={_kCFStreamErrorCodeKey=64, NSUnderlyingError=0x600003ccb360 {Error Domain=kCFErrorDomainCFNetwork Code=-1004 "(null)" UserInfo={_kCFStreamErrorCodeKey=64, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <170493E8-F704-4578-AB47-7B9CE76064B1>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <170493E8-F704-4578-AB47-7B9CE76064B1>.<1>"
), NSLocalizedDescription=Could not connect to the server., NSErrorFailingURLStringKey=https://play.googleapis.com/log/batch, NSErrorFailingURLKey=https://play.googleapis.com/log/batch, _kCFStreamErrorDomainKey=1}

And this is just one instance of it; typically it would be repeated a dozen times.

I don’t know if it’s due to Pi-Hole DNS blocking, blacklisting in /etc/hosts, and/or blocking in Little Snitch, but that doesn’t matter.

In AppCode, I tried using the Grep Console plugin to filter out these logs and it’s possible to do for some of these lines, but not all of them. These unnecessary logs are very annoying when I’m debugging something which generates some very specific logs and extra logging (especially a lot of it) adds distraction. I didn’t ask for this logging, I didn’t enable it and there is no official way to disable it, so we’ll have to dig deeper (I’m using Google Maps SDK 4.2.0 here).

Patching CCTClearcutUploader initializer (failed)

We can start by looking for the URL:

1
2
$ rg --binary -F 'play.googleapis.com' Pods/GoogleMaps
Binary file Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase matches (found "\u{0}" byte around offset 4)

Good, we’ve found the framework that contains the string. Let’s open it in Hopper, its demo version is enough for this small task. Select the “x86 (64 bits)” architecture because that’s what is used by the iOS Simulator; I had to disable “Parse exceptions information if present” because otherwise the disassembled would get stuck at “Parsing Exception Tables”. Type the URL in the strings list on the left and you can jump to this location:

1
2
                 LC50:
00000000000fee05         db         "https://play.googleapis.com/log/batch", 0  ; DATA XREF=cfstring_https___play_googleapis_com_log_batch

Put the cursor on the address (first field) and press x to see references. There is only one:

1
2
3
                 cfstring_https___play_googleapis_com_log_batch:

00000000001c3a98         dq         0x00000000002beb20, 0x00000000000007c8, 0x00000000000fee05, 0x0000000000000025 ; "https://play.googleapis.com/log/batch", DATA XREF=-[GMSx_CCTClearcutUploader initWithAuthorizer:listeners:logDirectory:isSharedDirectory:]+117

Repeat the same trick and you’ll get to the usage inside this function (at 0x17df):

1
2
3
4
5
6
7
8
9
10
                 -[GMSx_CCTClearcutUploader initWithAuthorizer:listeners:logDirectory:isSharedDirectory:]:
000000000000176a         push       rbp                                         ; Objective C Implementation defined at 0x1cf0a8 (instance method), DATA XREF=0x1cf0a8
000000000000176b         mov        rbp, rsp
00000000000017d8         mov        rdi, qword [objc_cls_ref_NSURL]             ; argument "instance" for method _objc_msgSend, objc_cls_ref_NSURL
00000000000017df         lea        rdx, qword [cfstring_https___play_googleapis_com_log_batch] ; @"https://play.googleapis.com/log/batch"
00000000000017e6         mov        rsi, r14                                    ; argument "selector" for method _objc_msgSend
0000000000001803         mov        rsi, qword [0x243950]                       ; argument "selector" for method _objc_msgSend, @selector(initWithAuthorizer:listeners:logDirectory:isSharedDirectory:serverURL:batchServerURL:requestFilter:)
000000000000180a         sub        rsp, 0x8

A few instructions below (at 0x1803) you can notice the selector for a more specific initializer: initWithAuthorizer:listeners:logDirectory:isSharedDirectory:serverURL:batchServerURL:requestFilter:. Typically less specific initializers (with fewer parameters) call more specific one (with more parameters), so it makes sense to find the most specific one. Paste the name to the procedure list on the left to jump to it:

1
2
                 -[GMSx_CCTClearcutUploader initWithAuthorizer:listeners:logDirectory:isSharedDirectory:serverURL:batchServerURL:requestFilter:]:
000000000000188d         push       rbp                                         ; Objective C Implementation defined at 0x1cf0c0 (instance method), DATA XREF=0x1cf0c0

It doesn’t call any other initializer of GMSx_CCTClearcutUploader, so my idea was to patch this one to immediately return. The status bar shows that this location has file offset 0x234d; note that this is an offset in the x86_64 slice, not in the original fat binary. So I would replace the byte 55 at this address with byte C3 (for ret instruction). To edit this byte, we need to extract this slice first:

1
$ lipo -thin x86_64 -output gmapsbase.x86_64 Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase

Using vim to edit binary files:

1
2
3
4
5
# before:
00002340: c418 5b41 5c41 5d41 5e41 5f5d c355 4889  ..[A\A]A^A_].UH.

# after:
00002340: c418 5b41 5c41 5d41 5e41 5f5d c3c3 4889  ..[A\A]A^A_]..H.

Then you need to replace the slice:

1
2
$ lipo Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase -replace x86_64 gmapsbase.x86_64 -output gmapsbase.patched
$ mv gmapsbase.patched Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase

Build and launch your app… and you’ll get a crash at startup:

1
Exception: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)

So this was a bad idea indeed. Let’s try another way.

Ignoring background events

didEnterBackgroundNotification is the notification name that is sent when the app is backgrounded and it’s likely that the SDK is subscribing to it. We can search for didEnterBackground:

1
2
                 aApplicationdid_135862:        // aApplicationdid
0000000000135862         db         "applicationDidEnterBackground:", 0         ; DATA XREF=0x1cf240, 0x1d3268, 0x1da120, 0x243a28

Press x to find references, one of them is:

1
2
3
4
5
00000000001cf240         struct __objc_method {                                 ; "applicationDidEnterBackground:","v24@0:8@16"
                             aApplicationdid_135862,              // name
                             aV240816,                            // signature
                             -[GMSx_CCTClearcutUploader applicationDidEnterBackground:] // implementation
                         }

Press x on aApplicationdid_135862, jumping to:

1
0000000000243a28         dq         aApplicationdid_135862                      ; @selector(applicationDidEnterBackground:), "applicationDidEnterBackground:", DATA XREF=___55-[GMSx_CCTClearcutUploader startAutoUploadOnBackground]_block_invoke+11, -[GMSx_PHTPhenotypeFlags initWithPackageName:userID:phenotype:autoUpdate:startLoading:]+393, -[GMSx_CCTLogWriter init]+319

Press x on the address, jumping to:

1
2
3
4
5
6
7
8
9
                 ___55-[GMSx_CCTClearcutUploader startAutoUploadOnBackground]_block_invoke:
00000000000024c3         push       rbp                                         ; DATA XREF=-[GMSx_CCTClearcutUploader startAutoUploadOnBackground]+141
00000000000024c4         mov        rbp, rsp
00000000000024c7         mov        rdx, rsi
00000000000024ca         mov        rdi, qword [rdi+0x20]                       ; argument "instance" for method _objc_msgSend
00000000000024ce         mov        rsi, qword [0x243a28]                       ; argument "selector" for method _objc_msgSend, @selector(applicationDidEnterBackground:)
00000000000024d5         pop        rbp
00000000000024d6         jmp        qword [_objc_msgSend@GOT]                   ; _objc_msgSend, _objc_msgSend,_objc_msgSend@GOT
                    ; endp

We can try patching this block, why not?! When your cursor stays on the address 0x24c3 (beginning of the function), the status bar shows file offset 0x2f83 and the right panel shows Instruction Encoding (aka opcode) 55, press Opt+a to assemble another instruction, enter ret (to return from the procedure), press Esc. Go back to that line, see that the opcode is now C3. We know what to do now (see the previous section for binary patching):

1
2
3
4
5
# before:
00002f80: 0000 0055 4889 e548 89f2 488b 7f20 488b  ...UH..H..H.. H.

# after:
00002f80: 0000 00c3 4889 e548 89f2 488b 7f20 488b  ....H..H..H.. H.

Rebuild and run your program. Voila, no more these annoying logs on backgrounding! The application doesn’t crash either. This may not be the most efficient to block those calls and it may not cover all cases, but it’s good enough for now.

Important to know

  1. If you’ve done something wrong, you’ll very likely get a crash, or, less likely, some incorrect behavior.

  2. Unfortunately I don’t know of an automatic way to find the correct address and binary patch it, so you’ll need to manually patch every new released version.

  3. Use this patched version only for local work, don’t distribute it.

  4. You need to make this edit for every architecture you care about, e.g. x86_64 for iOS Simulator and arm64 for iOS devices:

1
2
3
4
5
6
$ file Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase
Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase: Mach-O universal binary with 4 architectures: [arm_v7:Mach-O object arm_v7] [i386] [x86_64] [arm64]
Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase (for architecture armv7):       Mach-O object arm_v7
Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase (for architecture i386):        Mach-O object i386
Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase (for architecture x86_64):      Mach-O 64-bit object x86_64
Pods/GoogleMaps/Base/Frameworks/GoogleMapsBase.framework/GoogleMapsBase (for architecture arm64):       Mach-O 64-bit object arm64

Comments