一个简单的移动靶场总结

admin 2022年8月15日19:18:19评论8 views字数 18227阅读60分45秒阅读模式

前言

app测试了许多,今年恰好有机会将测试点重新梳理了一遍。对很多点有了重新的理解,虽说攻防对抗是不断迭代前进的,但还是有必要回顾一下最基础的一些知识点。恰好部门有靶机需求,于是大佬建议做一次移动靶场。这次靶机的大概思路是结合app端的证书校验考察一些基本的双向校验和简单的代码逆向还原。最终通过登录成功后得到存在漏洞的后端接口,通过命令执行getshell。

双向校验

在我们平常浏览器访问HTTPS的B/S架构里,一般只做了客户端对服务端真伪的校验。校验流程如下图:

一个简单的移动靶场总结

这里可能要讲上一句的是上图的步骤3客户端解密server.crt,验证证书合法性,从证书中拿到公钥:首先咱们了解一下何为证书:证书是用来认证公钥持有者的身份的电子文档,防止第三方进行冒充。一个证书中包含了公钥、持有者信息、证明证书内容有效的签名以及证书有效期,还有一些其他额外信息。如何解密证书之前,我们先来了解一下证书的签发与认证。

一个简单的移动靶场总结

签发证书的步骤:

  1. Signing阶段,首先撰写证书的元信息:签发人(Issuer)、地址、签发时间、过期失效等;当然,这些信息中还包含证书持有者(owner)的基本信息,例如owner的DN(DNS Name,即证书生效的域名),owner的公钥等基本信息。

  2. 通过通用的Hash算法将信息摘要提取出来;

  3. Hash摘要通过Issuer(CA)私钥进行非对称加密,生成一个签名密文;

  4. 将签名密文attach到文件证书上,使之变成一个签名过的证书。

验证证书的步骤:

  1. Verification阶段,浏览器获得之前签发的证书;

  2. 将其解压后分别获得“元数据”和“签名密文”;

  3. 将同样的Hash算法应用到“元数据”获取摘要;

  4. 将密文通过Issuer(CA)的公钥(非对称算法,私钥加密,公钥解密)解密获得同样的摘要值。

  5. 比对两个摘要,如果匹配,则说明这个证书是被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即可。

项目总结

总结就一句话:纸上得来终觉浅,绝知此事要躬行。


原文始发于微信公众号(雁行安全团队):一个简单的移动靶场总结

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年8月15日19:18:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一个简单的移动靶场总结https://cn-sec.com/archives/1237115.html

发表评论

匿名网友 填写信息