preface
In the process of penetration testing, we often encounter the situation that the login uses js to encrypt fields. In most cases, seeing this encryption method, we will give up brute force cracking on the login. This paper mainly explains how to bypass js encryption to achieve the purpose of blasting or anti climbing.
Blast a website that uses sm2 national secret algorithm at the registry
The graphic verification code of the website is invalid. As long as the password field can be encrypted accordingly, it can be exploded.
Visit the website, enter the user name: admin, password: 123456 and the correct graphic verification code to log in.

Packet capture analysis
After capturing the packet, you can see that the password field is encrypted into a long character.

F12 open the developer debugging mode and switch to the Network tab. Log in again and you can see that the password field is encrypted.

Switch to the Source tab and press ctrl+shift+F to call up the global search box and search the password field globally.

Skip to checkuser JS file, analyze the password field and encrypt it twice.
var password = hex_md5($("#password").val()); password = sm2Encrypt(password, publicKey_).toLocaleUpperCase();

First step encryption
The first step of encryption is to call hex_ The MD5 encryption function encrypts the password. Search hex globally_ MD5, in MD5 The function was found in the. JS file.
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}


Step 2 encryption
In the second step, the sm2Encrypt() function is called to encrypt the string encrypted in the first step.
We searched sm2Encrypt globally, and finally in sm2 The encryption function was found in the. JS file. Through Baidu search sm2 encryption algorithm, it is found that the algorithm is a state secret encryption algorithm.

SM2 state secret algorithm
SM2 is an elliptic curve public key cryptography algorithm issued by the State Encryption administration on December 17, 2010. SM2 algorithm and RSA algorithm are both public key cryptography algorithms. SM2 algorithm is a more advanced and secure algorithm, which is used to replace RSA algorithm in our national commercial cryptosystem.
With the development of cryptography and computer technology, the commonly used 1024 bit RSA algorithm is facing a serious security threat. After research, our national cryptographic management department decided to replace RSA algorithm with SM2 elliptic curve algorithm.

The above comes from Baidu Encyclopedia. More encryption methods of elliptic curve will not be discussed in detail.
Analog encryption
We need to simulate the sm2Encrypt encryption function. Here, nodejs is used for simulation.
1. Create SM2 locally JS file, put the website on SM2 Copy the sm2Encrypt() encryption function in the. JS file.
2. Add a console at the end Log() print, so that we can view the results.
3. Then put the MD5 Copy the JS file to SM2 JS in the same directory. Public key is in SM2 JS global definition.

var md5=require("./md5.js") var publicKey = "0469623686396c766185cd705cbd517714b377ae80b4b919a9de2b688f1cfa3edb60f67a13b6ecc8eef422577083d90844d635a675efef9cb6fa48386045a94518"; var data="123456" /** * [SM2Encrypt description] * @param {[type]} data [[data to be encrypted] * @param {[type]} publickey [Public key hex] * @param {[type]} cipherMode [Encryption mode C1C3C2:1, C1C2C3:0] * @return {[type]} [Return encrypted data hex] */ function sm2Encrypt(data, publickey, cipherMode) { cipherMode = cipherMode == 0 ? cipherMode : 1; //msg = SM2.utf8tob64(msg); data=md5.hex_md5(data); var msgData = CryptoJS.enc.Utf8.parse(data); var pubkeyHex = publickey; if (pubkeyHex.length > 64 * 2) { pubkeyHex = pubkeyHex.substr(pubkeyHex.length - 64 * 2); } var xHex = pubkeyHex.substr(0, 64); var yHex = pubkeyHex.substr(64); var cipher = new SM2Cipher(cipherMode); var userKey = cipher.CreatePoint(xHex, yHex); msgData = cipher.GetWords(msgData.toString()); var encryptData = cipher.Encrypt(userKey, msgData); return '04' + encryptData; } console.log(sm2Encrypt(data,publicKey))
Run the js file and prompt CryptoJS is not defined
So add var cryptojs = require ("cryptojs") at the beginning;

Install the crypto JS module.

After installation, run again. Prompt SM2Cipher is not defined. Prompt: this error is because we have not copied some other functions used in this function. So you have to copy the dependent functions one by one.

Break the point at the encrypted place and follow up with F11.

Step by step, we found the SM2Cipher function and copied it to our js file.

Run again, and this time the prompt KJUR is not defined

Baidu found that it needs to install jsrsaign

Then install the module, and add the introduction statement var kjur = require ("jsrsaign") at the beginning of the script;

Run again and prompt unregistered EC curve name: sm2, which is the imported jsrsasing Exception reported by JS file.


So I guess I need to register the sm2 curve name. We continue to browse the website's sm2 JS file, and finally found the code to register the sm2 curve name. Copy it into our code.

If you run it again, you will be prompted with the following error: typeerror: ecpointfp decodeFromHex is not a function.

We search the function ecpointfp globally in the imported module Decodefromhex. It is found that the imported module actually has this function.

So we use the previous statement var kjur = require ("jsrsaign"); Change to var jsrsasign=require("jsrsasign"). Then run again, and perform a global search for the function that reports an error. If the function is included in the imported module, add jsrsasign. Before it

If the imported module does not contain the function, it means that the function is defined by the website itself. We go to SM2 Just copy the function in JS. For example, the function SM3Digest function is not included in the imported module, but it is defined in the website. We can copy it into our own code.


Finally, the encryption algorithm is simulated after tracking its dependence from function to function, as follows:

sm2.js
var CryptoJS = require("crypto-js"); var jsrsasign=require("jsrsasign"); var md5=require("./md5.js") var publicKey = "0469623686396c766185cd705cbd517714b377ae80b4b919a9de2b688f1cfa3edb60f67a13b6ecc8eef422577083d90844d635a675efef9cb6fa48386045a94518"; var data="123456" /** * [SM2Encrypt description] * @param {[type]} data [[data to be encrypted] * @param {[type]} publickey [Public key hex] * @param {[type]} cipherMode [Encryption mode C1C3C2:1, C1C2C3:0] * @return {[type]} [Return encrypted data hex] */ function sm2Encrypt(data, publickey, cipherMode) { cipherMode = cipherMode == 0 ? cipherMode : 1; //msg = SM2.utf8tob64(msg); data=md5.hex_md5(data); var msgData = CryptoJS.enc.Utf8.parse(data); var pubkeyHex = publickey; if (pubkeyHex.length > 64 * 2) { pubkeyHex = pubkeyHex.substr(pubkeyHex.length - 64 * 2); } var xHex = pubkeyHex.substr(0, 64); var yHex = pubkeyHex.substr(64); var cipher = new SM2Cipher(cipherMode); var userKey = cipher.CreatePoint(xHex, yHex); msgData = cipher.GetWords(msgData.toString()); var encryptData = cipher.Encrypt(userKey, msgData); return '04' + encryptData; } function SM2Cipher(cipherMode) { this.ct = 1; this.p2 = null; this.sm3keybase = null; this.sm3c3 = null; this.key = new Array(32); this.keyOff = 0; if (typeof(cipherMode) != 'undefined') { this.cipherMode = cipherMode } else { this.cipherMode = SM2CipherMode.C1C3C2 } } SM2Cipher.prototype = { getHexString: function(h) { if((h.length & 1) == 0){ return h; }else { return "0" + h; } }, Encrypt: function(pubKey, plaintext) { var data = new Array(plaintext.length); Array.Copy(plaintext, 0, data, 0, plaintext.length); var c1 = this.InitEncipher(pubKey); this.EncryptBlock(data); var c3 = new Array(32); this.Dofinal(c3); var hexString; switch(this.cipherMode) { case SM2CipherMode.C1C3C2: hexString = this.getHexString(c1.getX().toBigInteger().toRadix(16)) + this.getHexString(c1.getY().toBigInteger().toRadix(16)) + this.GetHex(c3).toString() + this.GetHex(data).toString(); //hexString = this.getHexString(c1.getX().toBigInteger().toRadix(16)) + this.getHexString(c1.getY().toBigInteger().toRadix(16)) + this.GetHex(c3).toString() + this.GetHex(data).toString(); //hexString = this.getHexString(c1.getX().toBigInteger().toRadix(16)) + this.getHexString(c1.getY().toBigInteger().toRadix(16)) + this.GetHex(c3).toString() + this.GetHex(data).toString(); break; case SM2CipherMode.C1C2C3: hexString = c1.getX().toBigInteger().toRadix(16) + c1.getY().toBigInteger().toRadix(16) + this.GetHex(data).toString() + this.GetHex(c3).toString(); break; default: throw new Error("[SM2:Decrypt]invalid type cipherMode("+ this.cipherMode +")"); } return hexString }, GetHex: function(arr) { var words = new Array(32); var j = 0; for (var i = 0; i < arr.length * 2; i += 2) { words[i >>> 3] |= parseInt(arr[j]) << (24 - (i % 8) * 4); j++ } var wordArray = new CryptoJS.lib.WordArray.init(words, arr.length); return wordArray }, Reset: function() { this.sm3keybase = new SM3Digest(); this.sm3c3 = new SM3Digest(); var xWords = this.GetWords(this.p2.getX().toBigInteger().toRadix(16)); var yWords = this.GetWords(this.p2.getY().toBigInteger().toRadix(16)); this.sm3c3.BlockUpdate(xWords, 0, xWords.length); this.sm3keybase.BlockUpdate(xWords, 0, xWords.length); this.sm3keybase.BlockUpdate(yWords, 0, yWords.length); this.ct = 1; this.NextKey() }, NextKey: function() { var sm3keycur = new SM3Digest(this.sm3keybase); sm3keycur.Update(this.ct >> 24 & 0xff); sm3keycur.Update(this.ct >> 16 & 0xff); sm3keycur.Update(this.ct >> 8 & 0xff); sm3keycur.Update(this.ct & 0xff); sm3keycur.DoFinal(this.key, 0); this.keyOff = 0; this.ct++ }, EncryptBlock: function(data) { this.sm3c3.BlockUpdate(data, 0, data.length); for (var i = 0; i < data.length; i++) { if (this.keyOff == this.key.length) { this.NextKey() } data[i] ^= this.key[this.keyOff++] } }, Dofinal: function(c3) { var yWords = this.GetWords(this.p2.getY().toBigInteger().toRadix(16)); this.sm3c3.BlockUpdate(yWords, 0, yWords.length); this.sm3c3.DoFinal(c3, 0); this.Reset() }, InitEncipher: function(userKey) { var k = null; var c1 = null; var ec = new jsrsasign.KJUR.crypto.ECDSA({ "curve": "sm2" }); var keypair = ec.generateKeyPairHex(); k = new jsrsasign.BigInteger(keypair.ecprvhex, 16); var pubkeyHex = keypair.ecpubhex; c1 = jsrsasign.ECPointFp.decodeFromHex(ec.ecparams['curve'], pubkeyHex); this.p2 = userKey.multiply(k); this.Reset(); return c1; }, GetWords: function(hexStr) { var words = []; var hexStrLength = hexStr.length; for (var i = 0; i < hexStrLength; i += 2) { words[words.length] = parseInt(hexStr.substr(i, 2), 16) } return words }, CreatePoint: function(x, y) { var ec = new jsrsasign.KJUR.crypto.ECDSA({ "curve": "sm2" }); var ecc_curve = ec.ecparams['curve']; var pubkeyHex = '04' + x + y; var point = jsrsasign.ECPointFp.decodeFromHex(ec.ecparams['curve'], pubkeyHex); return point } }; jsrsasign.KJUR.crypto.ECParameterDB.regist( "sm2", // name 256, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", // p "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", // a "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", // b "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", // n "1", // h "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", // gx "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", // gy ["sm2", "SM2"] ); // alias Array.Copy = function (sourceArray, sourceIndex, destinationArray, destinationIndex, length) { var cloneArray = sourceArray.slice(sourceIndex, sourceIndex + length); for (var i = 0; i < cloneArray.length; i++) { destinationArray[destinationIndex] = cloneArray[i]; destinationIndex++ } }; function SM3Digest() { this.BYTE_LENGTH = 64; this.xBuf = new Array(); this.xBufOff = 0; this.byteCount = 0; this.DIGEST_LENGTH = 32; //this.v0 = [0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e]; //this.v0 = [0x7380166f, 0x4914b2b9, 0x172442d7, -628488704, -1452330820, 0x163138aa, -477237683, -1325724082]; this.v0 = [1937774191, 1226093241, 388252375, -628488704, -1452330820, 372324522, -477237683, -1325724082]; this.v = new Array(8); this.v_ = new Array(8); this.X0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; this.X = new Array(68); this.xOff = 0; this.T_00_15 = 0x79cc4519; this.T_16_63 = 0x7a879d8a; if (arguments.length > 0) { this.InitDigest(arguments[0]) } else { this.Init() } } SM3Digest.prototype = { Init: function () { this.xBuf = new Array(4); this.Reset() }, InitDigest: function (t) { this.xBuf = new Array(t.xBuf.length); Array.Copy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length); this.xBufOff = t.xBufOff; this.byteCount = t.byteCount; Array.Copy(t.X, 0, this.X, 0, t.X.length); this.xOff = t.xOff; Array.Copy(t.v, 0, this.v, 0, t.v.length) }, GetDigestSize: function () { return this.DIGEST_LENGTH }, Reset: function () { this.byteCount = 0; this.xBufOff = 0; Array.Clear(this.xBuf, 0, this.xBuf.length); Array.Copy(this.v0, 0, this.v, 0, this.v0.length); this.xOff = 0; Array.Copy(this.X0, 0, this.X, 0, this.X0.length) }, GetByteLength: function () { return this.BYTE_LENGTH }, ProcessBlock: function () { var i; var ww = this.X; var ww_ = new Array(64); for (i = 16; i < 68; i++) { ww[i] = this.P1(ww[i - 16] ^ ww[i - 9] ^ (roateLeft(ww[i - 3], 15))) ^ (roateLeft(ww[i - 13], 7)) ^ ww[i - 6] } for (i = 0; i < 64; i++) { ww_[i] = ww[i] ^ ww[i + 4] } var vv = this.v; var vv_ = this.v_; Array.Copy(vv, 0, vv_, 0, this.v0.length); var SS1, SS2, TT1, TT2, aaa; //roateLeft for (i = 0; i < 16; i++) { aaa = roateLeft(vv_[0], 12); SS1 = aaa + vv_[4] + roateLeft(this.T_00_15, i); SS1 = roateLeft(SS1, 7); SS2 = SS1 ^ aaa; TT1 = this.FF_00_15(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[i]; TT2 = this.GG_00_15(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[i]; vv_[3] = vv_[2]; vv_[2] = roateLeft(vv_[1], 9); vv_[1] = vv_[0]; vv_[0] = TT1; vv_[7] = vv_[6]; vv_[6] = roateLeft(vv_[5], 19); vv_[5] = vv_[4]; vv_[4] = this.P0(TT2) } for (i = 16; i < 64; i++) { aaa = roateLeft(vv_[0], 12); SS1 = aaa + vv_[4] + roateLeft(this.T_16_63, i); SS1 = roateLeft(SS1, 7); SS2 = SS1 ^ aaa; TT1 = this.FF_16_63(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[i]; TT2 = this.GG_16_63(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[i]; vv_[3] = vv_[2]; vv_[2] = roateLeft(vv_[1], 9); vv_[1] = vv_[0]; vv_[0] = TT1; vv_[7] = vv_[6]; vv_[6] = roateLeft(vv_[5], 19); vv_[5] = vv_[4]; vv_[4] = this.P0(TT2) } for (i = 0; i < 8; i++) { vv[i] ^= (vv_[i]) } this.xOff = 0; Array.Copy(this.X0, 0, this.X, 0, this.X0.length) }, ProcessWord: function (in_Renamed, inOff) { var n = in_Renamed[inOff] << 24; n |= (in_Renamed[++inOff] & 0xff) << 16; n |= (in_Renamed[++inOff] & 0xff) << 8; n |= (in_Renamed[++inOff] & 0xff); this.X[this.xOff] = n; if (++this.xOff == 16) { this.ProcessBlock() } }, ProcessLength: function (bitLength) { if (this.xOff > 14) { this.ProcessBlock() } this.X[14] = (this.URShiftLong(bitLength, 32)); this.X[15] = (bitLength & (0xffffffff)) }, IntToBigEndian: function (n, bs, off) { bs[off] = (n >>> 24 & 0xFF); bs[++off] = (n >>> 16 & 0xFF); bs[++off] = (n >>> 8 & 0xFF); bs[++off] = (n & 0xFF); }, DoFinal: function (out_Renamed, outOff) { this.Finish(); for (var i = 0; i < 8; i++) { this.IntToBigEndian(this.v[i], out_Renamed, outOff + i * 4) } this.Reset(); return this.DIGEST_LENGTH }, Update: function (input) { this.xBuf[this.xBufOff++] = input; if (this.xBufOff == this.xBuf.length) { this.ProcessWord(this.xBuf, 0); this.xBufOff = 0 } this.byteCount++ }, BlockUpdate: function (input, inOff, length) { while ((this.xBufOff != 0) && (length > 0)) { this.Update(input[inOff]); inOff++; length-- } while (length > this.xBuf.length) { this.ProcessWord(input, inOff); inOff += this.xBuf.length; length -= this.xBuf.length; this.byteCount += this.xBuf.length } while (length > 0) { this.Update(input[inOff]); inOff++; length-- } }, Finish: function () { var bitLength = (this.byteCount << 3); this.Update((128)); while (this.xBufOff != 0) this.Update((0)); this.ProcessLength(bitLength); this.ProcessBlock() }, ROTATE: function (x, n) { return (x << n) | (this.URShift(x, (32 - n))) }, P0: function (X) { return ((X) ^ roateLeft((X), 9) ^ roateLeft((X), 17)) }, P1: function (X) { return ((X) ^ roateLeft((X), 15) ^ roateLeft((X), 23)) }, FF_00_15: function (X, Y, Z) { return (X ^ Y ^ Z) }, FF_16_63: function (X, Y, Z) { return ((X & Y) | (X & Z) | (Y & Z)) }, GG_00_15: function (X, Y, Z) { return (X ^ Y ^ Z) }, GG_16_63: function (X, Y, Z) { return ((X & Y) | (~X & Z)) }, URShift: function (number, bits) { console.error(number); if (number > Int32.maxValue || number < Int32.minValue) { //number = Int32.parse(number) console.error(number); number = IntegerParse(number); } if (number >= 0) { return number >> bits } else { return (number >> bits) + (2 << ~bits) } }, URShiftLong: function (number, bits) { var returnV; var big = new jsrsasign.BigInteger(); big.fromInt(number); if (big.signum() >= 0) { returnV = big.shiftRight(bits).intValue() } else { var bigAdd = new BigInteger(); bigAdd.fromInt(2); var shiftLeftBits = ~bits; var shiftLeftNumber = ''; if (shiftLeftBits < 0) { var shiftRightBits = 64 + shiftLeftBits; for (var i = 0; i < shiftRightBits; i++) { shiftLeftNumber += '0' } var shiftLeftNumberBigAdd = new BigInteger(); shiftLeftNumberBigAdd.fromInt(number >> bits); var shiftLeftNumberBig = new BigInteger("10" + shiftLeftNumber, 2); shiftLeftNumber = shiftLeftNumberBig.toRadix(10); var r = shiftLeftNumberBig.add(shiftLeftNumberBigAdd); returnV = r.toRadix(10) } else { shiftLeftNumber = bigAdd.shiftLeft((~bits)).intValue(); returnV = (number >> bits) + shiftLeftNumber } } return returnV }, GetZ: function (g, pubKeyHex) { var userId = CryptoJS.enc.Utf8.parse("1234567812345679"); var len = userId.words.length * 4 * 8; this.Update((len >> 8 & 0x00ff)); this.Update((len & 0x00ff)); var userIdWords = this.GetWords(userId.toString()); this.BlockUpdate(userIdWords, 0, userIdWords.length); var aWords = this.GetWords(g.curve.a.toBigInteger().toRadix(16)); var bWords = this.GetWords(g.curve.b.toBigInteger().toRadix(16)); var gxWords = this.GetWords(g.getX().toBigInteger().toRadix(16)); var gyWords = this.GetWords(g.getY().toBigInteger().toRadix(16)); var pxWords = this.GetWords(pubKeyHex.substr(0, 64)); var pyWords = this.GetWords(pubKeyHex.substr(64, 64)); this.BlockUpdate(aWords, 0, aWords.length); this.BlockUpdate(bWords, 0, bWords.length); this.BlockUpdate(gxWords, 0, gxWords.length); this.BlockUpdate(gyWords, 0, gyWords.length); this.BlockUpdate(pxWords, 0, pxWords.length); this.BlockUpdate(pyWords, 0, pyWords.length); var md = new Array(this.GetDigestSize()); this.DoFinal(md, 0); return md }, GetWords: function (hexStr) { var words = []; var hexStrLength = hexStr.length; for (var i = 0; i < hexStrLength; i += 2) { words[words.length] = parseInt(hexStr.substr(i, 2), 16) } return words }, GetHex: function (arr) { var words = []; var j = 0; for (var i = 0; i < arr.length * 2; i += 2) { words[i >>> 3] |= parseInt(arr[j]) << (24 - (i % 8) * 4); j++ } var wordArray = new CryptoJS.lib.WordArray.init(words, arr.length); return wordArray } }; Array.Clear = function (destinationArray, destinationIndex, length) { for (elm in destinationArray) { destinationArray[elm] = null } }; function roateLeft(n, distance) { //return ((n << distance) | (n >>> (32 - distance))); return (n << distance)|(n >>> -distance); } SM2CipherMode = { C1C2C3: 0, C1C3C2: 1 }; console.log(sm2Encrypt(data,publicKey))
Finally, we can use burpsuite's plug-in to call this js encryption function, as follows:

Related articles: Analyze the encryption algorithm of website login (I)
Source: childe Xie's blog
Editor in charge: godunt