Workaround: Juniper Junos Pulse Split Tunneling Restriction

It’s been almost 6 years since I wrote the article to bypass split tunneling restriction on Juniper Network Connect. It’s now time to bypass the split tunneling restriction on VPNs running on Juniper Junos Pulse (or Pulse Secure).

The workaround for Junos Pulse (or Pulse Secure) is a lot more complicated compared to Network Connect. The DLL / EXE files are also now digitally signed and are checked for valid signatures whenever you logon to the VPN. Also, my method mentioned below may not be most optimal way to bypass the split tunneling restriction on Junos Pulse, but it has been working well for me so far, for the last few days at least (I only managed to get it working 3 days back!). Okay, let’s not waste any more time and get down to business.

Tools Required

You will need the following tools for this hack.

  1. openssl utility – This is used to generate the root CA and subordinate certificates. I have it installed under Cygwin.
  2. IDA Pro – IDA Pro disassembler and decompiler
  3. SignTool – part of Windows SDK to digitally sign DLL / EXE files
  4. Hex Editor – To patch the binary files, I am using PsPad
  5. x86 opcode reference – I use the site x86asm.net for opcode reference.

All my work was done on a PC running on a 64-bit version of Windows 7.

Files to Patch

There are two files to be patched. One is dsAccessService.exe, the other is dsTMService.dll. The dsAccessService.exe is the executable used for Windows Service. The dsTMService.dll file is used to monitor the routes. The path for the files are:

C:\Program Files (x86)\Common Files\Juniper Networks\JUNS\dsAccessService.exe
C:\Program Files (x86)\Common Files\Juniper Networks\Tunnel Manager\dsTMService.dll

You can verify the location of the dsAccessService.exe file by querying service JuniperAccessService. You will get one of the two outputs below.

C:\> sc qc JuniperAccessService
[SC] QueryServiceConfig SUCCESS

SERVICE_NAME: JuniperAccessService
        TYPE               : 10  WIN32_OWN_PROCESS
        START_TYPE         : 2   AUTO_START
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : "C:\Program Files (x86)\Common Files\Juniper Networks\JUNS\dsAccessService.exe"
        LOAD_ORDER_GROUP   : TDI
        TAG                : 0
        DISPLAY_NAME       : Juniper Unified Network Service
        DEPENDENCIES       :
        SERVICE_START_NAME : LocalSystem
C:\> sc qc JuniperAccessService
[SC] QueryServiceConfig SUCCESS

SERVICE_NAME: JuniperAccessService
        TYPE               : 10  WIN32_OWN_PROCESS
        START_TYPE         : 2   AUTO_START
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : "C:\Program Files (x86)\Common Files\Juniper Networks\JUNS\dsAccessService.exe"
        LOAD_ORDER_GROUP   : TDI
        TAG                : 0
        DISPLAY_NAME       : Pulse Secure Network Service
        DEPENDENCIES       :
        SERVICE_START_NAME : LocalSystem

The dsAccessService.exe file has code to check the digital signature of dsTMService.dll. So once the file dsTMService.dll is patched to ignore route changes, the signature verification will fail, and the VPN connection will fail.

Bypassing the signature check seem too troublesome, so I decided to resign the modified dsTMService.dll file, but it again failed at signature verification; but this check is easier to bypass.

Generate Root CA and Subordinate Certificate

This process is only done once. Once the certificates are generated, you can reuse them again till they expire. To generate a root CA certificate, run openssl as follows:

$ openssl genrsa -out ca.key 4096
$ openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [SG]:SG
State or Province Name (full name) [Some-State]:Singapore
Locality Name (eg, city) []:Singapore
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Digital Internals
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:Digital Internals
Email Address []:

Enter any value when prompted.

Now generate the subordinate certificate. This time make sure you specify Juniper Networks, Inc. when prompted for the names.

$ openssl genrsa -out juniper-ia.key 4096
$ openssl req -new -key juniper-ia.key -out juniper-ia.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [SG]:SG
State or Province Name (full name) [Some-State]:Singapore
Locality Name (eg, city) []:Singapore
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Juniper Networks, Inc.
Organizational Unit Name (eg, section) []:Juniper Networks, Inc.
Common Name (e.g. server FQDN or YOUR name) []:Juniper Networks, Inc.
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Next, sign the subordinate CA certificate with the root CA certificate.

$ openssl x509 -req -days 3650 -in juniper-ia.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out juniper-ia.crt
Signature ok
subject=/C=SG/ST=Singapore/L=Singapore/O=Juniper Networks, Inc./OU=Juniper Networks, Inc./CN=Juniper Networks, Inc.
Getting CA Private Key

Package the keys in PKCS12 format. This format is used by the Microsoft SignTool. It is not required to enter any password when prompted.

$ openssl pkcs12 -export -out juniper-ia.p12 -inkey juniper-ia.key -in juniper-ia.crt -chain -CAfile ca.crt
Enter Export Password:
Verifying - Enter Export Password:

Goto Control Panel -> Internet Options -> Content -> Certificates -> Trusted Root Certification Authorities and click on the “Import” button. Select “ca.crt” file and install the certificate. Once installed, make sure you can view the certificate.

Patch dsAccessService.exe

  1. Start IDA Pro, open the dsAccessService.exe file and generate the assembly code by going to File -> Produce File -> Create ASM File.

  2. Search for the string referece “WinVerifyTrust() failed“.

    The string reference is aWinverifytru_0.

  3. Search for the string reference in the assembly code.

  4. Look for a jz instruction above the string reference (highlighted above). This instruction has to be patched to become a jmp instruction. The jz instruction has an opcode of 0x74 with a rel8 offset. The corresponding jmp instruction with a rel8 offset has an opcode of 0xEB. Note that there are multiple opcodes for jz and jmp instructions. It depends on the operand size. In our case, the operand is rel8 or 8 bits for short loc_411ED7. If you check the values under Hex View in IDA Pro, the instruction takes just 2 bytes, which means the operand is only 1 byte or 8 bits.

    Opcode 0xE9 is also for jmp instruction, but it’s for 16/32 bit operand and it’s not applicable in our case.

  5. Get the hexadecimal data surrounding the jz instruction using the Hex View function of IDA Pro, open the file dsAccessService.exe in PsPad or some other hex editor and change the jz (0x74) instruction to a jmp (0xEB) instruction. After patching, the code will become like this:

Patch dsTMService.dll

The dsTMService.dll has to be patched at 3 places. Let’s look at the first patch.

  1. Start IDA Pro, open the dsTMService.dll file and generate the assembly code.

  2. Search for the string reference C_RouteTableMonitor::ThreadProc(): Route table notify event signaled.

    The string reference is aC_routetablemo.

  3. Search for the string reference in the assembly code.

  4. Look for a jz instruction above the string reference (highlighted above). This instruction has to be patched to become a jmp instruction – 0x74 will become 0xEB. After patching, the code will become like this:

Now, for the second patch.

  1. Search for the string reference C_RouteTableMonitor::ThreadProc(): Address table notify event signaled.

    The string reference is aC_routetable_0.

  2. Search for the string reference in the assembly code.

  3. Look for a jz instruction above the string reference (highlighted above). This instruction has to be patched to become a jmp instruction – 0x74 will become 0xEB. After patching, the code will become like this:

Now, for the last patch.

  1. Search for the string reference C_RoutePolicy::IsConflicted(): Start policy monitoring.

    The string reference is “aC_routepolicyI“. Note that the string is broken up into multiple short strings.

  2. Search for the string reference in the assembly code.

  3. Search for the function name above the string reference. Function names start with the string sub_. In our case, the function name is sub_10068EB1.

  4. Look for a call instruction to that function. Below that function call, there will be test instruction. We will need to remove the call instruction, and set register al to 0. This will make sure that the next test and jz instructions will work as expected.

    To set register al to 0, we can use the xor instruction.

    The call instruction occupies 5 bytes and the xor instruction occupies 2 bytes. So we need to use the nop (opcode: 90) instruction to fill the remaining 3 bytes.

  5. After patching, the code will become like this:

Sign dsTMService.dll

Sign the modified dsTMService.dll file using Microsoft SignTool with the generated certificate files.

C:\> signtool sign /v /t http://timestamp.verisign.com/scripts/timstamp.dll /f juniper-ia.p12 /d dsTMService.dll dsTMService.dll 

Once signed, check the digital signature properties of the file and make sure that the signature is valid. If it says that the signature cannot be verified, it means that the root CA has not been installed.

Overwrite Files

Stop the Junos Windows service, the Junos Pulse client, overwrite the two files, dsAccessService.exe and dsTMService.dll and restart the service.

Launch the Junos Pulse VPN client and connect to VPN. You should be able to modify routes now.

While connecting on a patched Pulse Secure installation, you may get a prompt saying: “An upgrade is available for Pulse Secure.” I did not get this prompt on Junos Pulse. Just click on cancel to proceed unless you are sure that it’s a genuine upgrade.

Final Words

I have not done much testing with other users for this workaround, so expect some hiccups. I have tested this workaround on version 5.0.4.45965 for both dsAccessService.exe and dsTMService.dll files.

Do also note that Junos Pulse product line is now owned and supported by Pulse Secure. So, the software client will be renamed to Pulse Secure. My VPN client just got upgraded to Pulse Secure the week after releasing this workaround.

Update (22-Mar-2015): Added information on Pulse Secure.

DISCLAIMER: THE ABOVE ARTICLE IS MEANT FOR EDUCATIONAL PURPOSES ONLY. I DO NOT PROMOTE OR SUPPORT ANY KIND OF REVERSE ENGINEERING OF COPYRIGHTED MATERIAL OR CONTENTS. THIS IS JUST A PROOF OF CONCEPT AND I AM NOT RESPONSIBLE FOR ANY LEGAL ISSUES YOU ENCOUNTER IF YOU FOLLOW THE ABOVE GUIDES/INSTRUCTIONS. I AM NOT RESPONSIBLE OF ANY OUTCOME, LOSS OF DATA OR ANY DAMAGES RESULTED DUE TO THE ACT OF YOU PROCEEDING WITH THE ABOVE GUIDES/INSTRUCTIONS.

ibrahim = { interested_in(unix, linux, android, open_source, reverse_engineering); coding(c, shell, perl, php, python, java, javascript, nodejs, angular, react); plays_on(xbox, ps4); linux_desktop_user(true); }