一個簡單的移動靶場總結
前言
app測試了許多,今年恰好有機會將測試點重新梳理了一遍。對很多點有了重新的理解,雖說攻防對抗是不斷迭代前進的,但還是有必要回顧一下最基礎的一些知識點。恰好部門有靶機需求,于是大佬建議做一次移動靶場。這次靶機的大概思路是結合app端的證書校驗考察一些基本的雙向校驗和簡單的代碼逆向還原。最終通過登錄成功后得到存在漏洞的后端接口,通過命令執行getshell。
雙向校驗
在我們平常瀏覽器訪問HTTPS的B/S架構里,一般只做了客戶端對服務端真偽的校驗。校驗流程如下圖:

這里可能要講上一句的是上圖的步驟3客戶端解密server.crt,驗證證書合法性,從證書中拿到公鑰:首先咱們了解一下何為證書:證書是用來認證公鑰持有者的身份的電子文檔,防止第三方進行冒充。一個證書中包含了公鑰、持有者信息、證明證書內容有效的簽名以及證書有效期,還有一些其他額外信息。如何解密證書之前,我們先來了解一下證書的簽發與認證。

簽發證書的步驟:
- Signing階段,首先撰寫證書的元信息:簽發人(Issuer)、地址、簽發時間、過期失效等;當然,這些信息中還包含證書持有者(owner)的基本信息,例如owner的DN(DNS Name,即證書生效的域名),owner的公鑰等基本信息。
- 通過通用的Hash算法將信息摘要提取出來;
- Hash摘要通過Issuer(CA)私鑰進行非對稱加密,生成一個簽名密文;
- 將簽名密文attach到文件證書上,使之變成一個簽名過的證書。
驗證證書的步驟:
- Verification階段,瀏覽器獲得之前簽發的證書;
- 將其解壓后分別獲得“元數據”和“簽名密文”;
- 將同樣的Hash算法應用到“元數據”獲取摘要;
- 將密文通過Issuer(CA)的公鑰(非對稱算法,私鑰加密,公鑰解密)解密獲得同樣的摘要值。
- 比對兩個摘要,如果匹配,則說明這個證書是被CA驗證過合法證書,里面的公鑰等信息是可信的。
當然這里也存在問題,如何保證Issuer(CA)公鑰的合法性,這里就牽扯到證書鏈了。簡單舉個栗子:BurpSuite作為中間人抓包。BurpSuite抓取https包的基本思路是偽裝成目標https服務器,讓瀏覽器(client)相信BurpSuite就是目標站點。為了達成目標,BurpSuite必須:生成一對公私鑰,并將公鑰和目標域名綁定并封裝為證書。讓瀏覽器相信此證書,即通過證書驗證。所以, BurpSuite需要在操作系統添加一個根證書,這個根證書可以讓瀏覽器信任所有BurpSuite頒發的證書。上面是客戶端對服務器端的認證,服務器對客戶的的認證一般是通過服務器配置客戶端證書,開啟客戶端認證來實現的。拿apache舉例:如果要實現對客戶端的認證就需要在原來https網站配置中加入下面兩行代碼:
SSLCACertificateFile /etc/apache2/cert/ca.cer SSLVerifyClient require
然后客戶端就必須要帶上服務器端配置的證書才能正常訪問服務器。回歸正題,我們講一下安卓里是如何實現證書雙向認證的:首先不同的網絡架構實現的接口不同,這里以最常見的okhttp為例。okhttp在初始化Builder的時候提供了sslSocketFactory這個配置接口,如果 sslSocketFactory沒有自定義配置的話,會使用 OkHttp 默認創建的。比如在 OkHttpClient 中有這樣的代碼來構造默認的 SSLSocketFactory:
X509TrustManager trustManager = systemDefaultTrustManager(); this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager); this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
systemDefaultSslSocketFactory 方法使用 sslContext來構造 sslSocketFactory
private SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);
return sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
這樣就是用了系統默認的 X509TrustManager,如果使用的是自定義證書,我們就需要手動創建一個 X509TrustManager。
// 使用包含自簽名證書的 KeyStore 構建一個 X509TrustManager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
// 使用 X509TrustManager 初始化 SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManagers[0]}, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
如果覺得單一校驗不夠安全,okhttp還提供了hostnameVerifier和CertificatePinner 。hostnameVerifier驗證證書里的 域名和 hostname 是否是否一致。默認代碼為:
public boolean verify(String host, SSLSession session) {
try {
Certificate[] certificates = session.getPeerCertificates();
return verify(host, (X509Certificate) certificates[0]);
} catch (SSLException e) {
return false;
}
}
CertificatePinner 用來將服務端的證書做hash摘要寫在代碼中做固定判斷,例如:
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("publicobject.com", "sha256/afwiKY3RxoMmLkuRWxxxxxxxxpdDROQjXw8ig=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
上述例子為單項校驗:SSL雙向驗證中,首先客戶端需要提供自己的證書供服務器端進行驗證。然后服務器端用客戶端的證書中的公鑰對握手數據加密,客戶端需要用自己的密鑰來解密握手數據。握手中需要產生隨機數。所以sslContext中的初始化方法init中需要這三個參數
其聲明方法為:
sslContext.init(KeyManager[] km, TrustManager[] tm, SecureRandom random)
具體的代碼調用為:
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
trustManager = chooseTrustManager(trustManagerFactory.getTrustManagers());//生成用來校驗服務器真實性的trustManager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore, pass);//生成服務器用來校驗客戶端真實性的KeyManager
//初始化SSLContext
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
sSLSocketFactory = sslContext.getSocketFactory();//通過sslContext獲取到SocketFactory
除了上面所述的,webview也存在雙向校驗。因為沒做過安卓開發我在這里踩了大坑,從網上扒了一段webview雙向校驗的代碼結果不能用,網上各種找原因也沒能解決。最后才知道webview有自己的實現方法,只需要重寫一下兩個方法即可實現webview的雙向校驗。,話不多說,直接上代碼:
webView.setWebViewClient(new WebViewClient() {
//服務器證書校驗
@Override
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
Bundle bundle = SslCertificate.saveState(error.getCertificate());
byte[] bytes = bundle.getByteArray("x509-certificate");
try {
if (MessageDigest.getInstance("MD5").isEqual(bytes, 服務端證書hash)) {
handler.proceed();
} else {
handler.cancel();
}
} catch (Exception e) {
handler.cancel();
}
}
//客戶端證書校驗
@Override
public void onReceivedClientCertRequest(WebView webView, ClientCertRequest request) {
//super.onReceivedClientCertRequest(view, request);
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream ksIn = new ByteArrayInputStream(證書);
keyStore.load(ksIn, pass);
Enumeration<?> localEnumeration;
localEnumeration = keyStore.aliases();
while (localEnumeration.hasMoreElements()) {
String str3 = (String) localEnumeration.nextElement();
clientCertPrivateKey = (PrivateKey) keyStore.getKey(str3,pass);
if (clientCertPrivateKey == null) {
continue;
} else {
Certificate[] arrayOfCertificate = keyStore.getCertificateChain(str3);
certificatesChain = new X509Certificate[arrayOfCertificate.length];
for (int j = 0; j < certificatesChain.length; j++) {
certificatesChain[j] = ((X509Certificate) arrayOfCertificate[j]);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
if ((null != clientCertPrivateKey) && ((null != certificatesChain) && (certificatesChain.length != 0))) {
request.proceed(clientCertPrivateKey, certificatesChain);//這里的key,cer在你雙向認證處理中都可以找到
} else {
request.cancel();
}
}
});
證書混淆
雙向認證完成了,但是有個致命缺陷就是證書,如果對證書不做任何處理,直接將客戶端證書和服務器證書配置到Charles之類的中間人工具中,那不等于白認證了。所以對證書進行了一些簡單的加密處理。加解密代碼用的這位大佬的,直接放鏈接,就不具體展示了:https://blog.csdn.net/howlaa/article/details/8863880
數據加密
這個app有個簡單的登錄功能及一個表單提交功能。這部分的數據采用了兩種加密方式。登錄用的是java端的Rabbit和RC4加密,先用Rabbit將字符串加密成 byte數組,然后使用RC4再將 byte數組加密成字符串。具體加解密代碼如下:
class Rabbit {
public static final int KEYSTREAM_LENGTH = 16;
public static final int IV_LENGTH = 8;
private static final int[] A = new int[]{1295307597, -749914925, 886263092, 1295307597, -749914925, 886263092, 1295307597, -749914925};
private final int[] X = new int[8];
private final int[] C = new int[8];
private byte b = 0;
private int keyindex = 0;
private byte[] keystream = null;
private final int rotl(int value, int shift) {
return value << shift | value >>> 32 - shift;
}
public Rabbit() {
}
public byte[] cc(String message, Charset charset, String key, String iv, boolean addPadding) {
if (message != null && key != null && charset != null && !message.isEmpty() && !key.isEmpty()) {
byte[] msg = null;
if (addPadding) {
msg = this.addPadding(message.getBytes(charset));
} else {
msg = message.getBytes(charset);
}
byte[] byteKey = this.getKeyFromString(key, charset);
this.reset();
this.setupKey(byteKey);
byte[] crypt;
if (iv != null && !iv.isEmpty()) {
crypt = this.getIVFromString(iv, charset);
this.setupIV(crypt);
}
crypt = this.crypt(msg);
this.reset();
return crypt;
} else {
throw new IllegalArgumentException();
}
}
private byte[] getIVFromString(String iv, Charset charset) {
return Arrays.copyOf(iv.getBytes(charset), 8);
}
private byte[] getKeyFromString(String key, Charset charset) {
return Arrays.copyOf(key.getBytes(charset), 16);
}
public byte[] cc(String message, String key, String iv, boolean addPadding) {
return this.cc(message, StandardCharsets.UTF_8, key, iv, addPadding);
}
public String dd(byte[] encMessage, Charset charset, String key, String iv, boolean trimPadding) {
if (encMessage != null && key != null && charset != null && !key.isEmpty()) {
byte[] byteKey = this.getKeyFromString(key, charset);
this.reset();
this.setupKey(byteKey);
byte[] crypt;
if (iv != null && !iv.isEmpty()) {
crypt = this.getIVFromString(iv, charset);
this.setupIV(crypt);
}
crypt = this.crypt(encMessage);
this.reset();
return trimPadding ? (new String(crypt, charset)).trim() : new String(crypt, charset);
} else {
throw new IllegalArgumentException();
}
}
public String dd(byte[] encMessage, String key, String iv, boolean trimPadding) {
return this.dd(encMessage, StandardCharsets.UTF_8, key, iv, trimPadding);
}
private byte[] addPadding(byte[] message) {
return message.length % 16 != 0 ? Arrays.copyOf(message, message.length + message.length % 16) : message;
}
public byte[] crypt(byte[] message) {
int index = 0;
while(index < message.length) {
if (this.keystream == null || this.keyindex == 16) {
this.keystream = this.keyStream();
this.keyindex = 0;
}
while(this.keyindex < 16 && index < message.length) {
int var10001 = index++;
message[var10001] ^= this.keystream[this.keyindex];
++this.keyindex;
}
}
return message;
}
private byte[] keyStream() {
this.nextState();
byte[] s = new byte[16];
int x = this.X[6] ^ this.X[3] >>> 16 ^ this.X[1] << 16;
s[0] = (byte)(x >>> 24);
s[1] = (byte)(x >> 16);
s[2] = (byte)(x >> 8);
s[3] = (byte)x;
x = this.X[4] ^ this.X[1] >>> 16 ^ this.X[7] << 16;
s[4] = (byte)(x >>> 24);
s[5] = (byte)(x >> 16);
s[6] = (byte)(x >> 8);
s[7] = (byte)x;
x = this.X[2] ^ this.X[7] >>> 16 ^ this.X[5] << 16;
s[8] = (byte)(x >>> 24);
s[9] = (byte)(x >> 16);
s[10] = (byte)(x >> 8);
s[11] = (byte)x;
x = this.X[0] ^ this.X[5] >>> 16 ^ this.X[3] << 16;
s[12] = (byte)(x >>> 24);
s[13] = (byte)(x >> 16);
s[14] = (byte)(x >> 8);
s[15] = (byte)x;
return s;
}
private void nextState() {
for(int j = 0; j < 8; ++j) {
long t = ((long)this.C[j] & 4294967295L) + ((long)A[j] & 4294967295L) + (long)this.b;
this.b = (byte)((int)(t >>> 32));
this.C[j] = (int)(t & -1L);
}
int[] G = new int[8];
for(int j = 0; j < 8; ++j) {
long t = (long)(this.X[j] + this.C[j]) & 4294967295L;
G[j] = (int)((t *= t) ^ t >>> 32);
}
this.X[0] = G[0] + this.rotl(G[7], 16) + this.rotl(G[6], 16);
this.X[1] = G[1] + this.rotl(G[0], 8) + G[7];
this.X[2] = G[2] + this.rotl(G[1], 16) + this.rotl(G[0], 16);
this.X[3] = G[3] + this.rotl(G[2], 8) + G[1];
this.X[4] = G[4] + this.rotl(G[3], 16) + this.rotl(G[2], 16);
this.X[5] = G[5] + this.rotl(G[4], 8) + G[3];
this.X[6] = G[6] + this.rotl(G[5], 16) + this.rotl(G[4], 16);
this.X[7] = G[7] + this.rotl(G[6], 8) + G[5];
}
public void reset() {
this.b = 0;
this.keyindex = 0;
this.keystream = null;
Arrays.fill(this.X, 0);
Arrays.fill(this.C, 0);
}
public void setupIV(byte[] IV) {
short[] sIV = new short[IV.length >> 1];
for(int i = 0; i < sIV.length; ++i) {
sIV[i] = (short)(IV[i << 1] << 8 | IV[5]);
}
this.setupIV(sIV);
}
public void setupIV(short[] iv) {
int[] var10000 = this.C;
var10000[0] ^= iv[1] << 16 | iv[0] & '\uffff';
var10000 = this.C;
var10000[1] ^= iv[3] << 16 | iv[1] & '\uffff';
var10000 = this.C;
var10000[2] ^= iv[3] << 16 | iv[2] & '\uffff';
var10000 = this.C;
var10000[3] ^= iv[2] << 16 | iv[0] & '\uffff';
var10000 = this.C;
var10000[4] ^= iv[1] << 16 | iv[0] & '\uffff';
var10000 = this.C;
var10000[5] ^= iv[3] << 16 | iv[1] & '\uffff';
var10000 = this.C;
var10000[6] ^= iv[3] << 16 | iv[2] & '\uffff';
var10000 = this.C;
var10000[7] ^= iv[2] << 16 | iv[0] & '\uffff';
this.nextState();
this.nextState();
this.nextState();
this.nextState();
}
public void setupKey(byte[] key) {
short[] sKey = new short[key.length >> 1];
for(int i = 0; i < sKey.length; ++i) {
sKey[i] = (short)(key[i << 1] << 8 | key[5]);
}
this.setupKey(sKey);
}
public void setupKey(short[] key) {
this.X[0] = key[1] << 16 | key[0] & '\uffff';
this.X[1] = key[6] << 16 | key[5] & '\uffff';
this.X[2] = key[3] << 16 | key[2] & '\uffff';
this.X[3] = key[0] << 16 | key[7] & '\uffff';
this.X[4] = key[5] << 16 | key[4] & '\uffff';
this.X[5] = key[2] << 16 | key[1] & '\uffff';
this.X[6] = key[7] << 16 | key[6] & '\uffff';
this.X[7] = key[4] << 16 | key[3] & '\uffff';
this.C[0] = key[4] << 16 | key[5] & '\uffff';
this.C[1] = key[1] << 16 | key[2] & '\uffff';
this.C[2] = key[6] << 16 | key[7] & '\uffff';
this.C[3] = key[3] << 16 | key[4] & '\uffff';
this.C[4] = key[0] << 16 | key[1] & '\uffff';
this.C[5] = key[5] << 16 | key[6] & '\uffff';
this.C[6] = key[2] << 16 | key[3] & '\uffff';
this.C[7] = key[7] << 16 | key[0] & '\uffff';
this.nextState();
this.nextState();
this.nextState();
this.nextState();
int[] var10000 = this.C;
var10000[0] ^= this.X[4];
var10000 = this.C;
var10000[1] ^= this.X[5];
var10000 = this.C;
var10000[2] ^= this.X[6];
var10000 = this.C;
var10000[3] ^= this.X[7];
var10000 = this.C;
var10000[4] ^= this.X[0];
var10000 = this.C;
var10000[5] ^= this.X[1];
var10000 = this.C;
var10000[6] ^= this.X[2];
var10000 = this.C;
var10000[7] ^= this.X[3];
}
}
class RC4 {
public static final String ENCRYPTION_ALGORITHM = "ARCFOUR";
public RC4() {
}
public static String aa(byte[] bArr, SecretKey secretKey, Cipher cipher) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException {
cipher.init(1, secretKey);
return Base64.encodeToString(cipher.doFinal(bArr),Base64.NO_WRAP);
}
}
這里存在一個問題就是客戶端加密使用的是java代碼,尤其是Rabbit加密代碼,而后端是用php寫的(四不像初步顯現),搜了一下php好像沒有直接使用的Rabbit代碼,自己重寫又不太可能。所以我這里采用了php調用java的思路來進行解密客戶端數據,這個在下面會說到。
設置token
前面說到既然存在登錄功能,那么如何實現登錄會話,其實最簡單可以使用php session會話,webview設置cookie來達到會話保持。但我發現其實采用token機制會更加方便一些。代碼如下:
<?php
class token
{
private $servername = "localhost";
private $username = "xxxx";
private $password = "xxxxxx";
public function GetRandStr($length){
//字符組合
$str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$len = strlen($str)-1;
$randstr = '';
for ($i=0;$i<$length;$i++) {
$num=mt_rand(0,$len);
$randstr .= $str[$num];
}
return $randstr;
}
public function set_token($user_name)
{
$information ['state'] = false;
$time = time();
$header = array('typ' => 'JWT');
$array = array(
'iat' => $time, // 時間戳
'exp' => 3600, // token有效期,1小時
'sub' => $this->GetRandStr(5),
'user_name' => $user_name
);
$str = base64_encode(json_encode($header)) . '.' . base64_encode(json_encode($array));// 數組轉成字符
$str = urlencode($str);
$information ['token'] = $str;
$this->save_token($user_name, $information['token']);
$information ['username'] = $user_name; // 返回用戶名
$information ['state'] = true;
return $information;
}
public function save_token($user_name, $token){
try {
$conn = new PDO("mysql:dbname=xxx;host=$this->servername;", $this->username, $this->password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "select * from auth where username=?";
$res = $conn->prepare($sql);
$res->execute(array($user_name));
if($res->fetch()){
$sql = "update auth set token = ? where username = ?";
$res = $conn->prepare($sql);
$res->execute(array($token,$user_name));
}else{
$sql = "insert into auth (username,token) values (?,?)";
$res = $conn->prepare($sql);
$res->execute(array($user_name,$token));
}
}
catch(PDOException $e)
{
echo $e->getMessage();
}
}
}
php調用java
前面講到客戶端登錄數據加密,后端為php無法解密(四不像二),我采用了php調用java來處理,這里詳細講述一下是如何調用的。主要是參考這位大佬的文章:https://www.cnblogs.com/youxin/archive/2013/02/23/2923425.html1.第一步:先在服務器上配置好java環境,直接下好jdk,然后解壓,再vim /etc/profile,配置jdk環境變量。
在文件最后寫入: export JAVA_HOME=/usr/server/yourjdk1.6.0_21 export CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH:$HOMR/bin
2.第二步:安裝php-java-bridge下載鏈接:http://sourceforge.net/projects/php-java-bridge/files/Binary%20package/php-java-bridge_6.2.1/exploded/JavaBridge.jar/download
執行監聽橋:(此步開啟Java監聽,注意8080為端口號,可以根據需要修改) #java -jar JavaBridge.jar SERVLET_LOCAL:8080
3.第三步:創建解密的jar文件,具體步驟創建java源文件,將源文件編譯,然后創建打包文件menifest-pl,最后使用jar編譯成jar文件:jar cvmf menifest-pl decrypt.jar com/php/decrypt.class最后將自己的包放到jdk拓展目錄:/usr/server/jdk1.6.0_21/jre/lib/ext4.第四步:下載Java.inc下載鏈接:https://jaist.dl.sourceforge.net/project/php-java-bridge/Binary%20package/php-java-bridge_7.2.1/exploded/Java.inc 該文件類似于php下面的Java擴展。最后php調用:
#vim test.php
define("JAVA_HOSTS", "127.0.0.1:8080");
require_once("Java.inc");
$tf = new Java('com.php.decrypt');
echo $tf->decrypt();
js調用java
前面有講登錄會話機制采用的是token,登錄成功之后服務端會將生成的token發回給客戶端,但是客戶端接收到登錄成功之后會加載webview,加載的webview會有個表單提交操作。表單提交會首先驗證token,所以需要將客戶端也就是java端接收到的token傳遞給webview里的js(四不像三)。通過一番資料查找及大佬的幫助,找到可以定義一個WebAppInterface靜態內部類,然后通過webView.addJavascriptInterface來綁定。具體代碼如下:
public static class WebAppInterface {
public String token;
public WebAppInterface(String token) {
this.token = token;
}
@JavascriptInterface
public String getToken() {
return token;
}
}
webView.addJavascriptInterface(new WebAppInterface(token), "token");
webview里的js直接可以通過:var token = window.token.getToken();來獲取token值
js加密
數據加密前面講了登錄的數據加密,現在再來看一下webview里的js數據加密:加密函數:(沒用的混淆@@)
eval(function(p,a,c,k,e,r){e=function(c){return c.toString(36)};if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'[0-9bdf]'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('3 en(e,t){1 n="MIGeMA",r=n+t;e=4.5(e);1 a=0.6.7.8(r),o=0.6.7.8(e),s=0.AES.encrypt(o,a,{9:0.9.ECB,padding:0.pad.Pkcs7});b s.2()}3 l(e){1 r="----BEGIN d\\n----",a="----\\nEND d----",o=0.f(4.5(e)).2(),s=r+"1641523830856"+o+"30401736"+a,c=0.f(s).2();b c}',[],16,'CryptoJS|var|toString|function|JSON|stringify|enc|Utf8|parse|mode||return||SIGN||MD5'.split('|'),0,{}))
var endata = en(data,parseInt(1641526902328 / 1e3));//加密
var slp = l(data,parseInt(1641526902328 / 1e3));//加簽
node生成可執行文件解密
還是和前面問題一樣加密容易解密難,在咨詢了大佬之后建議直接將解密代碼編譯成二進制文件調用(四不像四),臨走之前大佬還千叮嚀萬囑咐,一定要控制好輸入,否則就成了命令執行了。我微微一笑。node生成二進制:
npm install -g pkg pkg 1.js -o deapp
漏洞設置
既然是移動靶場,沒有點漏洞怎么行啊,雖然目前并不知道是否存在未知漏洞,但在我對大佬微微一笑之時我就想到如何制造漏洞了。那么不用敲黑板,漏洞點大家也應該知道了吧!!!
坑點記錄
第一次開發踩的坑是真多啊,這里簡要的提一下,除了前面提到的webview的雙向認證之外,因為環境最終是要制作成docker環境的,所以每次啟動之后之前的source /etc/profile就會失效,導致無法啟動JavaBridge.jar,最終解決辦法是直接將絕對路徑帶上將啟動命令寫入開機自動執行的start.sh腳本。第二個坑點是禁止ip訪問,從網上搜了好多都用不了,我甚至都懷疑是不是同個配置文件不能配置兩個相同的監聽端口,后來咬著牙分析了一下報錯發現是因為我第二個443端口配置開啟了ssl但是沒有配置證書。第三個就是ssl剛配置好時無法正常啟動apache2,原因是沒有開啟ssl和rewrite解決辦法:直接執行a2enmod rewrite、a2enmod ssl然后重啟apache2即可。
項目總結
總結就一句話:紙上得來終覺淺,絕知此事要躬行。