This is a technical write up of the author’s investigation on how users of Samsung phones in Hong Kong (and Macau), using firmware released in September 2020, would be forced to use a public DNS service in Mainland China, which caused unease and privacy concerns among some of its users.
While this was investigated on a variant of a Galaxy Note 10+ phone targetting the Hong Kong market, it was reported that the issue exists for a wide range of recent Samsung phones, including those sold in other places when used in Hong Kong.
(Update: The firmware update released in Mid-October 2020 has fixed the DNS issue discussed in this Part, but the issue of DNS queries for qq.com
discussed in Part 2 remained unchanged.)
Shameless plug (for Hong Kong users): The author is the developer of Headuck Call blocker (useful only in Hong Kong). Earlier it got mistakenly flagged as malware by Google and lost many of its users, but the appeal was successful and the App is on Google Play again. Please re-consider the App if you got scared by the earlier Google Play malware warning.
Background
In early October 2020, a Samsung phone user in Hong Kong, local forum (HKEPC) user dingwinslow209, reported that (link to forum post in Chinese) an extra DNS server entry, 114.114.114.114
was added to the DNS setting of his Samsung mobile phone, which was updated with the latest firmware, whenever he was using a WiFi connection. This happened both when the DNS setting is static, or dynamic using DHCP (but not when VPN / mobile network was used). Even when both DNS1 and DNS2 were set to valid DNS servers (e.g. Google public DNS service using DNS1 = 8.8.8.8
and DNS2 = 8.8.4.4
), a new DNS3 entry pointing to 114.114.114.114
would appear in some utility app.
This immediately raised privacy concerns among some Samsung users in Hong Kong, as the public DNS server 114.114.114.114
is owned by Cogent Communications, under Nanjing XinFeng Information Technologies Inc, in Mainland China.
Subsequently, in another local forum (lihkg), users captured DNS requests to 114.114.114.114
, and observed queries for “qq.com
” (domain owned by Chinese tech giant Tencent), even when no software from Tencent is installed in the devices. There were reports that these DNS queries were sent once per minute, so long as the phone screen remained on.
There are further reports that when DNS queries to qq.com were blocked, the phone would report no internet connectivity via the WiFi connection.
The observation of the extra DNS entry was later independently confirmed by other forum users and the local media, using recent Samsung phones which have been updated in recent months. (See the links in the HKPEC post above, in Chinese). The issue persisted even when users reset their Samsung phone to factory settings. This showed that the issue originates from Samsung firmware instead of some third-party software malware.
The following documents the technical investigation and verification of the issue directly from analysing the code from firmware. The information should be sufficient for the issue and its extent to be independently verified.
Getting and extracting the firmware
To confirm the issue, a recent Samsung Galaxy Note 10+ firmware (SM-N9750 TGY, Hong Kong version), at Security Patch level 2020-09-01 was downloaded from one of the Samsung firmware download sites. Galaxy Note 10+ is one of the Samsung models reportedly affected.
After unzipping the downloaded firmware (in zip format), expanding the tar file beginning with “AP_N9750ZSU3CTH1
“, decompressing the file system.img.ext4.lz4
using lz4
, converting it to ext4 format using simg2img
, and mounting it as ext4 volume under Linux, one has access to the system image which would be installed when Note 10+ users update their phone to that patch level. (The same is accessible if a Note 10+ phone is rooted).
After some research, the culprit was found – a vendor specific service level component, added by Samsung to the Android framework, located at /system/framework/wifi-service.jar
. This seems to work at the android system service level, supplementing the usual services.jar
.
The decompiled source of the jar file (using this site, with Jadx decompiler) has been uploaded to https://github.com/headuck/SM-N9750-TGY. The following is a walkthrough of the relevant code when a user connects to a WiFi network, showing how the DNS entries were modified.
Relevant flow of DNS entry addition
The main culprit is the class com.android.server.wifi.WifiConnectivityMonitor
, located in the file WifiConnectivityMonitor.java. The decompiled code contains the following:
line 129: the hardcoded address 114.114.114.114
private static final String CHN_PUBLIC_DNS_IP = "114.114.114.114";
line 878: addresses used for DNS probe (to be covered in next part).
public final String DEFAULT_URL = "http://www.google.com"; public final String DEFAULT_URL_CHINA = "http://www.qq.com"; public String DEFAULT_URL_STRING = "www.google.com"; public final String DEFAULT_URL_STRING_CHINA = "www.qq.com";
This large class is mainly a state machine of the various WiFi states. The state hierarchy are defined, and initial state set, at lines 1185-1197.
addState(this.mDefaultState); addState(this.mNotConnectedState, this.mDefaultState); addState(this.mConnectedState, this.mDefaultState); addState(this.mCaptivePortalState, this.mConnectedState); addState(this.mEvaluatedState, this.mConnectedState); .... setInitialState(this.mNotConnectedState);
The base class for the StateMachine can be found under AOSP source (StateMachine.java).
When the device is connected to WiFi, it would enter ConnectedState
.
line 1976 (under processMessage()
of the initial NotConnectedState
) would be invoked when a new WiFi connection is detected.
wifiConnectivityMonitor.transitionTo(wifiConnectivityMonitor.mConnectedState);
The mConnectedState
variable is of class ConnectedState
, defined from line 1988. The enter()
method of ConnectedState
contains the following code (from line 2090), which uses CHN_PUBLIC_DNS_IP
(i.e. the Mainland Chinese controlled DNS server):
if (WifiConnectivityMonitor.this.mWifiManager != null && WifiConnectivityMonitor.this.inChinaNetwork()) { Message msg = new Message(); msg.what = 330; Bundle args = new Bundle(); args.putString("publicDnsServer", WifiConnectivityMonitor.CHN_PUBLIC_DNS_IP); msg.obj = args; WifiConnectivityMonitor.this.mWifiManager.callSECApi(msg); }
From the code it seems to add the Mainland Chinese DNS service to the user’s list of DNS server automatically, when the device is connected to Chinese mobile network. There seems no option to disable the behaviour.
WifiConnectivityMonitor.inChinaNetwork()
is at line 11199. As suggested by its name, it should obtain the ISO code and return true only if the device is connected to a mobile network in China:
public boolean inChinaNetwork() { String str = this.mCountryIso; if (str == null || str.length() != 2) { updateCountryIsoCode(); } if (!isChineseIso(this.mCountryIso)) { return false; } if (!DBG) { return true; } Log.d(TAG, "Need to skip captive portal check. CISO: " + this.mCountryIso); return true; }
Digging deeper, this is how the ISO code (mCountryIso
) is obtained, under updateCountryIsoCode()
at line 11219 (fallback skipped).
public void updateCountryIsoCode() { if (this.mTelephonyManager == null) { try { this.mTelephonyManager = (TelephonyManager) this.mContext.getSystemService("phone"); } catch (Exception e) { Log.e(TAG, "Exception occured at updateCountryIsoCode(), while retrieving Context.TELEPHONY_SERVICE"); } } TelephonyManager telephonyManager = this.mTelephonyManager; if (telephonyManager != null) { this.mCountryIso = telephonyManager.getNetworkCountryIso(); Log.i(TAG, "updateCountryIsoCode() via TelephonyManager : mCountryIso: " + this.mCountryIso); } /* fallback when there is no mobile network skipped. The fallback is to read the CountryISO setting from a Samsung config file (cscfeature.xml) */ .... }
(While not shown here, this code is also invoked when initializing and when change in ISO code of telephone network is detected, so it need not be called during each check.) The country code is get from TelephonyManager.getNetworkCountryIso()
which is a standard Android API, documented here. It returns the ISO-3166-1 alpha-2 country code equivalent of the MCC (Mobile Country Code) of the mobile operator. In Hong Kong, this is “HK”, and in Mainland China this is “CN”.
As one might suspect at this point, the problem lies in isChineseIso()
, at line 11214:
private boolean isChineseIso(String countryIso) { return "cn".equalsIgnoreCase(countryIso) || "hk".equalsIgnoreCase(countryIso) || "mo".equalsIgnoreCase(countryIso); }
This means that you are treated as being connected to a Chinese mobile network if you are connected to a Hong Kong mobile network for the purpose of adding the 114 DNS service. (BTW, “MO” is the ISO-3166-1 code for Macau.) Perhaps Samsung might want to address cases when people travel to Mainland China while forgetting to reset their hardcoded DNS settings, and kindly “add” a DNS service which works within the Great Firewall of China. But they seemed to forget that both Hong Kong and Macau are outside the Great Firewall, at least so far.
Back to the DNS setting code above (line 2090). It makes a binder call to WifiManager.callSECApi()
, with message code = 330, with a Bundle
setting publicDnsServer
to our friend 114.114.114.114
. While WifiManager
is a standard Android class the method callSECApi()
, as suggested by its name, is Samsung specific.
The remote call to WifiManager
would end up in the service class implementation at com.android.server.wifi.WifiServiceImpl
, implementing WifiService, at WifiServiceImpl.java. (The class is Samsung’s extension to the AOSP service class of the same name). Unfortunately the Samsung specific target method callSECApi()
at line 4957 of WifiServiceImpl.java
failed to be decompiled to Java and only the smali code is available.
At line 5069, the case for message code 330 sent above would branch to address label L_0x052d
, at line 5706
case 330: goto L_0x052d;
At L_0x052d
, it just forward the call to ClientModeImpl.callSECApi()
representing the relevant wifi client connection (r12 being the original android.os.Message
parameter)
L_0x052d: r11.enforceChangePermission() com.android.server.wifi.ClientModeImpl r0 = r11.mClientModeImpl int r0 = r0.callSECApi(r12) r1 = r0 goto L_0x06b0
The class com.android.server.wifi.ClientModeImpl
is at ClientModeImpl.java. The method called, callSECApi()
, is at line 10278 (and again cannot be decompiled to Java). Message code 330 leads to the label L_0x0124
L_0x0124: r1 = 131286(0x200d6, float:1.83971E-40) java.lang.Object r2 = r14.obj // Catch:{ all -> 0x02ef } android.os.Message r1 = r13.obtainMessage(r1, r4, r4, r2) // Catch:{ all -> 0x02ef } r13.sendMessage(r1) // Catch:{ all -> 0x02ef } monitor-exit(r13) return r4
The above code send the message with code number 131286 to the (separate) state machine under the class ClientModeImpl
.
line 241 shows that this message is CMD_REPLACE_PUBLIC_DNS
private static final int CMD_REPLACE_PUBLIC_DNS = 131286;
This message is processed in the L2ConnectedState
of the state machine (which covers the child states of obtaining IP, connected, and roaming in the state hierarchy).
case ClientModeImpl.CMD_REPLACE_PUBLIC_DNS /*131286*/: if (ClientModeImpl.this.mLinkProperties != null) { String publicDnsIp = ((Bundle) message2.obj).getString("publicDnsServer"); ArrayList dnsList = new ArrayList<>(ClientModeImpl.this.mLinkProperties.getDnsServers()); dnsList.add(NetworkUtils.numericToInetAddress(publicDnsIp)); ClientModeImpl.this.mLinkProperties.setDnsServers(dnsList); ClientModeImpl clientModeImpl13 = ClientModeImpl.this; clientModeImpl13.updateLinkProperties(clientModeImpl13.mLinkProperties); break; } break;
Here, the code add the value of publicDnsServer
(i.e. 114.114.114.114
when the device is on Mainland China, Hong Kong or Macau mobile network) to the current list of DNS servers. LinkProperties
is a standard AOSP class (source link here). The getDnsServers()
call simply returns the list of existing DNS servers in the network link of the Wifi client, while setDnsServers()
just set the list, but also removes the duplicates.
From LinkProperties.java
(AOSP source):
/** * Replaces the DNS servers in this {@code LinkProperties} with * the given {@link Collection} of {@link InetAddress} objects. * * @param dnsServers The {@link Collection} of DNS servers to set in this object. */ public void setDnsServers(@NonNull Collection dnsServers) { mDnses.clear(); for (InetAddress dnsServer: dnsServers) { addDnsServer(dnsServer); } }
/** * Adds the given {@link InetAddress} to the list of DNS servers, if not present. * * @param dnsServer The {@link InetAddress} to add to the list of DNS servers. * @return true if the DNS server was added, false if it was already present. * @hide */ public boolean addDnsServer(@NonNull InetAddress dnsServer) { if (dnsServer != null && !mDnses.contains(dnsServer)) { mDnses.add(dnsServer); return true; } return false; }
Note that unlike some interfaces, there is no limit to the number of DNS servers under LinkProperties
. In other words, this would force the addition of the server 114.114.114.114
each time when called, if it does not already exist on the list. It is frutile for users to manually remove or change the setting, since the next call upon network state change will ensure that the server is added back.
In the next part I will look into the other issue which concerns some users – the background resolving of qq.com
.
在〈How Samsung phones force Mainland China DNS service upon Hong Kong WiFi users (Part 1)〉中有 5 則留言
The maninland in title is not necessary btw
Cheers mate, great work.
But at least Samsung engineers got this mixed up, so just for clarity to foreign readers.
Many thanks for the effort. We are so surprised that Samsung got this problem as one of the most established non-PRC company in smartphone s……
Thx for the info, that means we can get away with this by installing vpn or using mobile data.
VPN: cannot fix
Mobile Data: can fix