Monday, February 28, 2022

Javascript Deobfuscation Tips

 



Last week I've had to de-obfuscate a javascript file. The javascript file included about 10K lines of code, and I had to struggle to understand the hidden meaning of the code. After a week of struggle I was success, and I want to share some of the tricks and insights that I've found as part of this process.


First, copy the file to an online deobfuscator site such as de4js or obfuscateIO. The site will handle the first pass of the code, such as removing proxy functions, expression simplifications and more.

The code you get after this step is still a big mess. Don't expect anything that you can work with.

Next copy the code into a javascript editor, such as WebStorm, and format the code, so it would match the editor formatting standard.

You will probably want to simplify some of the common expressions, which are usually based on the minifier actions.

Examples of these are listed below.

  • Change from:  !to: true
  • Change from:  !to: false
  • Change from: void 0 to: undefined


Some obfuscation might be done manually by the code creator, for example, change of all string constants to a base64 encoded strings to hide the real consts, e.g. instead of:


var a = object["left"]


You might find:


const e = '\x95çí'
var a = object[btoa(e)]


In this case, a good approach might be to create a script to automatically translate all of the strings in the obfuscated code.


The last step is the real challenge. In this step we rename the functions to their actual meaningful name. When renaming a function, use the IDE, so it will rename all of the usages as well. 

The best tip for this step is to work bottom up. Look for functions that use items that cannot be renamed, such as document, window, navigator. These functions can be easily deciphered. Then, once the basic functions are handled, you can move up the hierarchy, and understand the next level. 

Make sure to rename not only the functions, but also the parameters names, and the the arguments names in the calling function. This will solve the puzzle piece by piece.

Good luck!


 






Monday, February 21, 2022

Symmetric Encrypt and Decrypt in Golang

 



In this post we will review AES-CBC symmetric encryption in Golang.


We start by key generation.



import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"radware.com/euda/commons/global/encryption/padding"
)

const keySizeBytes = 32

type EncryptedData struct {
Cipher string `json:"cipher"`
Iv string `json:"iv"`
}

type Key struct {
key []byte
}

func GenerateKey() (*Key, error) {
key := make([]byte, keySizeBytes)
_, err := rand.Read(key)
if err != nil {
return nil, fmt.Errorf("read failed: %v", err)
}
k := Key{key: key}
return &k, nil
}



The key can be easily exported and imported.


func LoadKey(keyBase64 string) (*Key, error) {
bytes, err := base64.StdEncoding.DecodeString(keyBase64)
if err != nil {
return nil, fmt.Errorf("base64 decode failed: %v", err)
}
k := Key{key: bytes}
return &k, nil
}

func (k *Key) ExportKey() string {
return base64.StdEncoding.EncodeToString(k.key)
}


And finally we can encrypt and decrypt using the key.



func (k *Key) Encrypt(clearText string) (string, error) {
iv := make([]byte, aes.BlockSize)
_, err := rand.Read(iv)
if err != nil {
return "", fmt.Errorf("read failed: %v", err)
}

aesCipher, err := aes.NewCipher(k.key)
if err != nil {
return "", fmt.Errorf("create cipher failed: %v", err)
}

cbc := cipher.NewCBCEncrypter(aesCipher, iv)

clearBytes := []byte(clearText)
clearBytesPadded, err := padding.Pkcs7Pad(clearBytes, aes.BlockSize)
if err != nil {
return "", fmt.Errorf("padding failed: %v", err)
}

cipherBytes := make([]byte, len(clearBytesPadded))

cbc.CryptBlocks(cipherBytes, clearBytesPadded)
data := EncryptedData{
Cipher: base64.StdEncoding.EncodeToString(cipherBytes),
Iv: base64.StdEncoding.EncodeToString(iv),
}

jsonBytes, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("marshal failed: %v", err)
}

return string(jsonBytes), nil
}

func (k *Key) Decrypt(jsonData string) (string, error) {
var data EncryptedData
err := json.Unmarshal([]byte(jsonData), &data)
if err != nil {
return "", fmt.Errorf("unmarshal failed: %v", err)
}

iv, err := base64.StdEncoding.DecodeString(data.Iv)
if err != nil {
return "", fmt.Errorf("decode iv failed: %v", err)
}

cipherBytes, err := base64.StdEncoding.DecodeString(data.Cipher)
if err != nil {
return "", fmt.Errorf("decode cipher failed: %v", err)
}

aesCipher, err := aes.NewCipher(k.key)
if err != nil {
return "", fmt.Errorf("create cipher failed: %v", err)
}
cbc := cipher.NewCBCDecrypter(aesCipher, iv)

paddedClearTextBytes := make([]byte, len(cipherBytes))
cbc.CryptBlocks(paddedClearTextBytes, cipherBytes)

clearTextBytes, err := padding.Pkcs7Unpad(paddedClearTextBytes, aes.BlockSize)
if err != nil {
return "", fmt.Errorf("un-padding failed: %v", err)
}

clearText := string(clearTextBytes)
return clearText, nil
}



Notice that the encryption result includes Initialization Vector (IV), which is a random buffer that is used in the encryption. The IV is returned from the encrypt function unchanged. The decrypt function uses both the cipher result and the IV along with the AES key to do its work. To simplify the usage of both IV and cipher, we use a JSON format.



We can test our code using the following:



func Test(t *testing.T) {
key, err := GenerateKey()
if err != nil {
t.Fatal(err)
}

encrypted, err := key.Encrypt("123456789012345")
if err != nil {
t.Fatal(err)
}

t.Logf("encrypted is %v", encrypted)

exportedKey := key.ExportKey()
t.Logf("exported key is %v", exportedKey)

importedKey, err := LoadKey(exportedKey)
if err != nil {
t.Fatal(err)
}

decrypted, err := importedKey.Decrypt(encrypted)
if err != nil {
t.Fatal(err)
}
t.Logf("decrypted is %v", decrypted)
}




Final Note


This post is part of a series of posts about encryption. The full posts list is below.




Symmetric Encrypt and Decrypt in Javascript


 

In this post we will review the steps to use AES-CBC encryption in javascript. We use the SubtleCrypto API, which is available on on secure context pages. Will also use some functions that are not supported on old browsers.


First, to check if the page is considered a secure context, use the following:



console.log('secure context', window.isSecureContext)



The SubtleCrypto API uses ArrayBuffer for it functions, but as we want to send the data to server, will will convert it from and to a base64 string using the following helper functions:



function arrayBufferToBase64(arrayBuffer) {
const bytes = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
return window.btoa(bytes)
}

function base64ToArrayBuffer(base64String) {
const chars = window.atob(base64String)
const arrayBuffer = new ArrayBuffer(chars.length)
const bufferView = new Uint8Array(arrayBuffer)
for (let i = 0, strLen = chars.length; i < strLen; i++) {
bufferView[i] = chars.charCodeAt(i)
}
return arrayBuffer
}


Special notes for NodeJS users

As btoa() does not exist in NodeJS, we need to use the Buffer, BUT we must specify the latin1 encoding to have the same encoding as in the browser.


NodeJS version:

function arrayBufferToBase64(arrayBuffer) {
const bytes = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
return Buffer.from(bytes,'latin1').toString('base64')
}



Now we can generate the AES key.


const ALGORITHM = 'AES-CBC'


async function generateKey() {
return await window.crypto.subtle.generateKey(
{
name: ALGORITHM,
length: 256,
},
true,
['encrypt', 'decrypt'],
)
}



The key can be exported and imported.



async function exportKey(key) {
const exported = await window.crypto.subtle.exportKey(
'raw',
key,
)

return arrayBufferToBase64(exported)
}

async function importKey(base64Key) {
const bytes = base64ToArrayBuffer(base64Key)
return await window.crypto.subtle.importKey(
'raw',
bytes,
'AES-CBC',
true,
['encrypt', 'decrypt'],
)
}



And finally we can encrypt and decrypt using AES-CBC:



async function encrypt(key, clearText) {
const encodedText = new TextEncoder().encode(clearText)
const iv = window.crypto.getRandomValues(new Uint8Array(16))
const cipherText = await window.crypto.subtle.encrypt(
{
name: ALGORITHM,
iv,
},
key,
encodedText,
)

const data = {
cipher: arrayBufferToBase64(cipherText),
iv: arrayBufferToBase64(iv),
}

return JSON.stringify(data)
}

async function decrypt(key, jsonData) {
const data = JSON.parse(jsonData)
const ciphertext = base64ToArrayBuffer(data.cipher)
const iv = base64ToArrayBuffer(data.iv)
const encodedText = await window.crypto.subtle.decrypt(
{
name: 'AES-CBC',
iv,
},
key,
ciphertext,
)

return new TextDecoder().decode(encodedText)
}


Notice that the encryption result includes Initialization Vector (IV), which is a random buffer that is used in the encryption. The IV is returned from the encrypt function unchanged. The decrypt function uses both the cipher result and the IV along with the AES key to do its work. To simplify the usage of both IV and cipher, we use a JSON format.


We can now test our code. The following is an example of using the encryption.



async function unitTest() {
const logPrefix = 'symmetric unit-test'
const key = await generateKey()
console.log(logPrefix, 'key', key)
const clearText = '123456789sdfghjkl%$^&*(XXX'
console.log(logPrefix, 'clearText', clearText)
const cipher = await encrypt(key, clearText)
console.log(logPrefix, 'cipher', cipher)
const exportedKey = await exportKey(key)
console.log(logPrefix, 'exportedKey', exportedKey)
const importedKey = await importKey(exportedKey)
console.log(logPrefix, 'importedKey', importedKey)
const decrypted = await decrypt(importedKey, cipher)
console.log(logPrefix, 'decrypted', decrypted)
}





Final Note


This post is part of a series of posts about encryption. The full posts list is below.




Monday, February 14, 2022

XMLHttpRequest Proxy

 



In a previous post about XMLHttpRequest we've describe how to capture requests so that we can examine the URLs, and possibly add our own headers. In this post, we will display a more intrusive method, where we create a proxy for the XMLHttpRequest, so that we can replace the request, and the response, as well as sending the data to a different URL.


The proxy structure is as follows:



const originalXhrClass = XMLHttpRequest

XMLHttpRequest = function () {
// the proxy code goes here
}



We replace the XMLHttpRequest with our own code, and keep an internal reference to the original XMLHttpRequest. Let's examine the proxy code. Let declare some fields that we will later use.



const originalXhrObject = new originalXhrClass()
const self = this
self.onreadystatechange = null
const doneEventHandlers = []
const requestHeaders = []
let lastDoneEvent
let changeResponseDone = false
let response



In the open method call we can replace the target URL:



Object.defineProperty(self, 'open', {
value: function () {
const url = arguments[1]
arguments[1] = `https://my.example.com/my/modified/url?url=${url}`
return originalXhrObject['open'].apply(originalXhrObject, arguments)
},
})



The addRequestHeader calls are kept for later use.



Object.defineProperty(self, 'setRequestHeader', {
value: function () {
const [name, value] = arguments
requestHeaders.push([name, value])
},
})



The methods/getters/setters that we do not want to change are passed through to the original object.



const getters = ['status', 'statusText', 'readyState', 'responseXML', 'upload']
getters.forEach(function (property) {
Object.defineProperty(self, property, {
get: function () {
return originalXhrObject[property]
},
})
})

const getterAndSetters = ['ontimeout, timeout', 'responseType', 'withCredentials', 'onload', 'onerror', 'onprogress']
getterAndSetters.forEach(function (property) {
Object.defineProperty(self, property, {
get: function () {
return originalXhrObject[property]
},
set: function (val) {
originalXhrObject[property] = val
},
})
})

const standardMethods = ['removeEventListener', 'abort', 'getAllResponseHeaders', 'getResponseHeader', 'overrideMimeType']
standardMethods.forEach(function (method) {
Object.defineProperty(self, method, {
value: function () {
return originalXhrObject[method].apply(originalXhrObject, arguments)
},
})
})



Upon send of the data, we can update the request body.



Object.defineProperty(self, 'send', {
value: async function () {
originalXhrObject.setRequestHeader('Content-Type', 'application/json')
const body = arguments[0]
const newBody = {
body,
requestHeaders,
}
arguments[0] = JSON.stringify(newBody)

return originalXhrObject['send'].apply(originalXhrObject, arguments)
},
})



The response is modified once it arrives.



async function modifyResponse() {
if (changeResponseDone) {
return
}
response = `my modified response${originalXhrObject.responseText}`
changeResponseDone = true
}

originalXhrObject.onreadystatechange = async function () {
if (originalXhrObject.readyState === 4) {
await modifyResponse()
}
if (self.onreadystatechange) {
return self.onreadystatechange()
}
if (lastDoneEvent) {
doneEventHandlers.forEach((handler) => {
handler(lastDoneEvent)
})
}
}



we supply response getters.



const responseGetters = ['response', 'responseText']

responseGetters.forEach(function (property) {
Object.defineProperty(self, property, {
get: function () {
return response
},
})
})




and we handle the notification per the addEventListener, and for the onload event.



Object.defineProperty(self, 'addEventListener', {
value: function () {
const eventType = arguments[0]
if (eventType === 'load') {
const handler = arguments[1]
doneEventHandlers.push(handler)
return
}
return originalXhrObject['addEventListener'].apply(originalXhrObject, arguments)
},
})

originalXhrObject.addEventListener('load', (event) => {
if (changeResponseDone) {
doneEventHandlers.forEach((handler) => {
setTimeout(() => {
handler(event)
}, 100)

})
} else {
lastDoneEvent = event
}
})




Final Note


We have shown how to proxy the XMLHttpRequest. A different simpler approach could be by using a service worker, where proxy will not be required, and hence there is no need to handle the various event listeners, and getters/setters.



Monday, February 7, 2022

A-Symmetric Encrypt and Decrypt on Golang

 



In this post we will review the steps to use RSA encryption/decryption on GO. 


This post is about using a-symmetric encryption for encrypt/decrypt. In case of need of a-symmetric encryption for sign/verify, check this post.


We start by creation a struct to represent the encryption:


package encryption

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"hash"
"io"
"os"
"radware.com/euda/commons/global/log"
)

type Key struct {
privateKey *rsa.PrivateKey
hash hash.Hash
random io.Reader
}

func (k *Key) init() {
k.hash = sha256.New()
k.random = rand.Reader
}


We can generate a new key, which takes about 5 seconds on my desktop machine



func GenerateKey() (*Key, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, fmt.Errorf("generate key failed: %v", err)
}

k := Key{privateKey: privateKey}
k.init()
return &k, nil
}



To save the key to a file:



func (k *Key) SavePrivateKey() error {
privateKeyBytes := x509.MarshalPKCS1PrivateKey(k.privateKey)
privateKeyBlock := pem.Block{
Type: "PRIVATE KEY",
Bytes: privateKeyBytes,
}

var writerBuffer bytes.Buffer

err := pem.Encode(&writerBuffer, &privateKeyBlock)
if err != nil {
return fmt.Errorf("encode private key failed: %v", err)
}

fileData := writerBuffer.String()
err = os.WriteFile(Config.PrivateKeyPath, []byte(fileData), 0644)
if err != nil {
return fmt.Errorf("write file failed: %v", err)
}
return nil
}



To load the key from a file:



func LoadPrivateKey() (*Key, error) {
pemFile, err := os.ReadFile(Config.PrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("read file failed: %v", err)
}
pemBlock, _ := pem.Decode(pemFile)
privateKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unmarshal key failed: %v", err)
}

rsaPrivateKey,ok :=privateKey.(*rsa.PrivateKey)
if !ok{
return nil, fmt.Errorf("convert failed: %v", err)
}

k := Key{privateKey: rsaPrivateKey}
k.init()

log.Info("private key loaded %v", Config.PrivateKeyPath)
return &k, nil
}



To get the public key for sending it to other parties:



func (k *Key) GetPublicKeyPem() (string, error) {
publicKeyBytes, err := x509.MarshalPKIXPublicKey(k.privateKey.Public())
if err != nil {
return "", fmt.Errorf("marshal public key failed: %v", err)
}

publicKeyBlock := pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
}

var writerBuffer bytes.Buffer

err = pem.Encode(&writerBuffer, &publicKeyBlock)
if err != nil {
return "", fmt.Errorf("encode public key failed: %v", err)
}

return writerBuffer.String(), nil
}



And finally, we can encrypt and decrypt:



func (k *Key) EncryptString(clearText string) (string, error) {
encryptedBytes, err := rsa.EncryptOAEP(
k.hash,
k.random,
&k.privateKey.PublicKey,
[]byte(clearText),
nil,
)

if err != nil {
return "", fmt.Errorf("encrypt failed: %v", err)
}
encryptedBase64 := base64.StdEncoding.EncodeToString(encryptedBytes)
return encryptedBase64, nil
}

func (k *Key) DecryptString(base64Cipher string) (string, error) {
encryptedBytes, err := base64.StdEncoding.DecodeString(base64Cipher)
if err != nil {
return "", fmt.Errorf("base64 decode failed: %v", err)
}

clearTextBytes, err := rsa.DecryptOAEP(
k.hash,
k.random,
k.privateKey,
encryptedBytes,
nil,
)
if err != nil {
return "", fmt.Errorf("decrypt failed: %v", err)
}

clearText := string(clearTextBytes)
return clearText, nil
}




Final Note


This post is part of a series of posts about encryption. The full posts list is below.




Sunday, February 6, 2022

A-Symmetric Encrypt and Decrypt on JavaScript


 


In this post we will review the steps to use RSA encryption/decryption on Javascript. We use the SubtleCrypto API, which is available on on secure context pages. Will also use some functions that are not supported on old browsers.


This post is about using a-symmetric encryption for encrypt/decrypt. In case of need of a-symmetric encryption for sign/verify, check this post.



First, to check if the page is considered a secure context, use the following:



console.log('secure context', window.isSecureContext)



The SubtleCrypto API uses ArrayBuffer for it functions, but as we want to send the data to server, will will convert it from and to a base64 string using the following helper functions:



function arrayBufferToBase64(arrayBuffer) {
const bytes = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
return window.btoa(bytes)
}

function base64ToArrayBuffer(base64String) {
const chars = window.atob(base64String)
const arrayBuffer = new ArrayBuffer(chars.length)
const bufferView = new Uint8Array(arrayBuffer)
for (let i = 0, strLen = chars.length; i < strLen; i++) {
bufferView[i] = chars.charCodeAt(i)
}
return arrayBuffer
}


Special notes for NodeJS users

As btoa() does not exist in NodeJS, we need to use the Buffer, BUT we must specify the latin1 encoding to have the same encoding as in the browser.


NodeJS version:

function arrayBufferToBase64(arrayBuffer) {
const bytes = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer))
return Buffer.from(bytes,'latin1').toString('base64')
}



We now can generate a new RSA key. This is a intensive operation, and it takes ~1 second on a desktop machine.



async function generateKey() {
return await window.crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['encrypt', 'decrypt'],
)
}



The generated key is actually a pair that includes a public key and a private key. 


In most cases we will store the private key locally so that in case of restart, we will reuse it. To export the private key we run the following:


function convertBinaryToPem(base64Cert, label) {
var pemCert = '-----BEGIN ' + label + '-----\r\n'
var nextIndex = 0
while (nextIndex < base64Cert.length) {
if (nextIndex + 64 <= base64Cert.length) {
pemCert += base64Cert.substr(nextIndex, 64) + '\r\n'
} else {
pemCert += base64Cert.substr(nextIndex) + '\r\n'
}
nextIndex += 64
}
pemCert += '-----END ' + label + '-----\r\n'
return pemCert
}

async function exportPrivateKey(privateKey) {
const exported = await window.crypto.subtle.exportKey(
'pkcs8',
privateKey,
)
const exportedAsBase64 = arrayBufferToBase64(exported)
return convertBinaryToPem(exportedAsBase64, 'RSA PRIVATE KEY')
}


The public key can be sent to other parties. We use the export and import to do this.


const pemHeader = '-----BEGIN PUBLIC KEY-----\n'
const pemFooter = '\n-----END PUBLIC KEY-----'


function breakKeyToShortLines(publicKey) {
const maxLineLength = 64
let pemCert = ''
let nextIndex = 0
while (nextIndex < publicKey.length) {
if (nextIndex + maxLineLength <= publicKey.length) {
pemCert += publicKey.substr(nextIndex, maxLineLength) + '\n'
} else {
pemCert += publicKey.substr(nextIndex)
}
nextIndex += maxLineLength
}
return pemCert
}

async function exportSubjectPublicKey(publicKey) {
const exported = await window.crypto.subtle.exportKey(
'spki',
publicKey,
)
const exportedAsBase64 = arrayBufferToBase64(exported)
const shortLinesKey = breakKeyToShortLines(exportedAsBase64)
return `${pemHeader}${shortLinesKey}${pemFooter}`
}

function importSubjectPublicKey(pem) {
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length)
const binaryDer = base64ToArrayBuffer(pemContents)

return window.crypto.subtle.importKey(
'spki',
binaryDer,
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['encrypt'],
)
}



Finally we can use the encrypt and decrypt functions:



async function encryptMessage(publicKey, clearText) {
const encodedText = new TextEncoder().encode(clearText)
const cipherText = await window.crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
publicKey,
encodedText,
)

return arrayBufferToBase64(cipherText)
}

async function decryptMessage(privateKey, cipherBase64) {
const cipherText = base64ToArrayBuffer(cipherBase64)
const encodedText = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
privateKey,
cipherText,
)

return new TextDecoder().decode(encodedText)
}



An example for using these functions is below:



async function example() {
const keyPair = await generateKey()
console.log('key pair', keyPair)
const exportedPublicKey = await exportSubjectPublicKey(keyPair.publicKey)
console.log('exported public', exportedPublicKey)
const importedPublicKey = await importSubjectPublicKey(exportedPublicKey)
console.log('imported public', importedPublicKey)
for (let i = 0; i < 2; i++) {
const text = 'mypass123'
const encrypted = await encryptMessage(importedPublicKey, text)
console.log('encrypted', encrypted)
const decrypted = await decryptMessage(keyPair.privateKey, encrypted)
console.log('decrypted', decrypted)
}
}




Final Note


This post is part of a series of posts about encryption. The full posts list is below.