破解某國外收費的RTMP Client並成功在Android和Java上調用

來源:互聯網
上載者:User

Adboe的Red5流媒體伺服器免費並且是開源的,與Flash搭配的時候可謂是天生一對,但使用Java和Android作為用戶端調用卻可謂一波三折。

         Adobe的Red5原始碼裡有一個RTMPClient的類,這個類在使用上其實不複雜,但卻沒辦法成功調用。觀察日誌,發現是串連成功後在開始建立流的時候,服務端把串連斷開了。我能想到的解釋就是可能公司現在所使用的Red5伺服器的版本與這個RTMPClient不相容。

         國內有人把Red5的RTMPClient精簡了出來作為一個開源的類庫放在google的svn上,網址如下:http://code.google.com/p/android-rtmp-client/。這個類庫同樣也是沒辦法成功串連伺服器。

         國外還有一個收費的RTMPClient,價值是395刀。具體網址和產品的名稱我就不指出了,有心人肯定會找得到。這個用戶端類庫很強大,使用也很方便,我註冊了一個試用的key,發現能和Red5伺服器成功串連並且通訊良好。

由於實在是找不到其它的方法了,而且自己去摸索實現Red5的rtmp協議這基本上不太現實,於是我反編譯了一下這個類庫,發現除了幾個入口類之外,其它類全是混淆過的。

其中最重要的幾個類是NetConnection,NetStream, License,其中NetConnection,NetStream這兩個類是負責建立串連和回調服務端的資料。而License則顧名思義是負責驗證有沒有授權。由於按照官方給出的使用說明,在使用前必須調用License.setKey()方法傳入註冊所得到的key。

         按照破解的習慣,一向是先嘗試暴力破解,也就是繞過驗證。於是先把License.setKey()這個方法調用注釋掉,運行後拋出異常:

Exception inthread "main" java.lang.IllegalArgumentException: Your license key isinvalid!

         atcom.smaxe.uv.client.NetConnection.a(Unknown Source)

         atcom.smaxe.uv.client.NetConnection.b(Unknown Source)

         atcom.smaxe.uv.client.NetConnection.connect(Unknown Source)

         開啟JD並定位到NetConnect這個類的cononect方法,發現反編譯所得的代碼如下:

public void connect(String paramString,Object[] paramArrayOfObject)

  {

   b(k);

   UrlInfo localUrlInfo = UrlInfo.parseUrl(paramString);

   com.smaxe.uv.client.a.e locale = new com.smaxe.uv.client.a.e();

   locale.a(this.d);

   locale.a((ILogger)configuration().get("logger"));

   this.b = locale;

   this.c = new a();

   this.b.a(this, this.a.a(localUrlInfo.protocol, localUrlInfo.host,localUrlInfo.port, configuration()), paramString, localUrlInfo.getApp(),this.c, paramArrayOfObject);

   super.connect(paramString, paramArrayOfObject);

  }

大家會發現b(k)這個方法調用有點古怪。再開啟License類,其中setKey的代碼如下:

public static void setKey(String paramString)

  {

   NetConnection.a(a(paramString));

  }

回過頭來再看NetConnection的a(byte[])方法,如下:

static void a(byte[]paramArrayOfByte)

  {

    if ((paramArrayOfByte == null) ||(paramArrayOfByte.length != 25))

      return;

    k = paramArrayOfByte;

  }

果然NetConnection的b()就是用過驗證是否具有授權的。把“Your license key is invalid!”作為特徵碼在所有檔案中搜尋了一次,卻是無法搜尋到結果。分析了一下,發現作者很聰明,預先把這句話編碼成ASCII碼,在使用的時候再將ASCII碼轉為字串輸出,這樣就不能輕易地通過搜尋特徵碼定位到驗證的地方。

         不過可惜java的編譯特點,在爆破的過程中定位到驗證的代碼實在是太容易了。下一步就是把整個NetConnection的反編譯代碼複製到一個新檔案裡,整理好引用後發現有一堆的錯誤,分析了一下大部分都是jd的反編譯有點瑕疵,都是很容易可以修改好。但其中一個地方卻是死活想不明白,代碼如下:this.a.a(localUrlInfo.protocol, localUrlInfo.host,localUrlInfo.port, configuration()),eclipse的報錯提示是Thetype
com.smaxe.uv.a.c cannot be resolved. It is indirectly referenced fromrequired .class files。觀察分析後發現com.smaxe.uv.a.c是一個包名,但同時也存在著com.smaxe.uv.a.c這個類,這在Java的編譯機制裡是不合法的,但Java的VM卻是允許這樣的存在形式的。混淆器應該就是利用了這一點的特性,將編譯後的位元組碼檔案修改成這樣古怪的形式來“混淆視聽”。

         繞過這種機制的方法很簡單,就是利用反射,具體代碼等會帖出來,但思考的過程差點把腦袋想破了。調用的方法請查看官方給出的例子,只需要把其中的NetConnection和NetStream替換成以下的兩個即可。

完整代碼請以這裡下載:http://download.csdn.net/detail/visualcatsharp/4294733

沒資源分的朋友請留下郵箱。

修改過的NetConnection:
import java.io.File;import java.lang.reflect.Method;import java.util.Calendar;import java.util.Map;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import com.smaxe.logger.ILogger;import com.smaxe.uv.ProtocolLayerInfo;import com.smaxe.uv.Responder;import com.smaxe.uv.UrlInfo;import com.smaxe.uv.client.INetConnection;import com.smaxe.uv.client.a.d;import com.smaxe.uv.client.a.h;import com.smaxe.uv.client.a.i;import com.smaxe.uv.client.a.k;public final class UltraNetConnection extends i  implements INetConnection{  private final h a;  private d b = null;  private a c = null;  private ExecutorService d = null;  private ScheduledExecutorService e = null;  private boolean f = false;  private boolean g = false;  private static final int h = 19;  private static final int i = 9;  private static final int[] j = { 5, 2, 7, 1, 0, 3, 6, 4 };  private static byte[] k = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };  public static void setSwfFileSizeAndHash(Map<String, Object> paramMap, File paramFile)    throws Exception  {    a(paramMap, paramFile);  }  public UltraNetConnection()  {    this(null);  }  public UltraNetConnection(Map<String, Object> paramMap)  {    this(paramMap, null, null);  }  public UltraNetConnection(Map<String, Object> paramMap, ExecutorService paramExecutorService, ScheduledExecutorService paramScheduledExecutorService)  {    super(paramMap);    this.d = (paramExecutorService == null ? Executors.newCachedThreadPool() : paramExecutorService);    this.e = (paramScheduledExecutorService == null ? Executors.newSingleThreadScheduledExecutor() : paramScheduledExecutorService);    this.f = (paramExecutorService == null);    this.g = (paramScheduledExecutorService == null);    this.a = new k(this.e);  }  public void addHeader(String paramString, boolean paramBoolean, Object paramObject)  {    this.b.a(paramString, paramBoolean, paramObject);  }  public void call(String paramString, Responder paramResponder, Object[] paramArrayOfObject)  {    if (!connected())      return;    this.b.a(paramString, paramResponder, paramArrayOfObject);  }  public void close()  {    b(com.smaxe.uv.a.e.b("NetConnection.Connect.Closed", "Connection is closed."));  }  public void connect(String paramString, Object... paramArrayOfObject)  {//    b(k);    UrlInfo localUrlInfo = UrlInfo.parseUrl(paramString);    com.smaxe.uv.client.a.e locale = new com.smaxe.uv.client.a.e();    locale.a(this.d);    locale.a((ILogger)configuration().get("logger"));    this.b = locale;    this.c = new a();    try {    Method bitchMethod = com.smaxe.uv.client.a.h.class.getMethod("a", String.class, String.class, int.class, Map.class);        Object btichResult = bitchMethod.invoke(this.a, localUrlInfo.protocol, localUrlInfo.host, localUrlInfo.port, configuration());        Method[] aryMethod = com.smaxe.uv.client.a.d.class.getMethods();        for(Method method : aryMethod) {        if(method.getName().equals("a") && method.getParameterTypes().length == 6) {        method.invoke(this.b, this, btichResult, paramString, localUrlInfo.getApp(), this.c, paramArrayOfObject);        break;        }        }    } catch(Exception ex) {    ex.printStackTrace();    }        super.connect(paramString, paramArrayOfObject);  }  public boolean connected()  {    if (this.b == null)      return false;    return this.b.a() == 3;  }  public String connectedProxyType()  {    return connected() ? this.b.b() : null;  }  public boolean usingTLS()  {    return connected() ? this.b.c() : false;  }  public ProtocolLayerInfo getInfo()  {    return this.b.d();  }  public int getUploadBufferSize()  {    return this.b.e();  }  public void setMaxUploadBandwidth(int paramInt)  {    if (paramInt < 0)      throw new IllegalArgumentException("Parameter 'bandwidth' is negative: " + paramInt);    this.b.a(paramInt);  }  public void onBWDone()  {  }  public void onBWDone(Object[] paramArrayOfObject)  {  }  private void b(Map<String, Object> paramMap)  {    if (this.b == null)      return;    this.b.a(paramMap);    if ((this.f) && (this.d != null))      this.d.shutdown();    if ((this.g) && (this.e != null))      this.e.shutdown();    this.d = null;    this.e = null;  }  static void a(byte[] paramArrayOfByte)  {    if ((paramArrayOfByte == null) || (paramArrayOfByte.length != 25))      return;    k = paramArrayOfByte;  }  d a()  {    return this.b;  }  private static void b(byte abyte0[])  throws IllegalArgumentException{  int l = 0;  for(int i1 = 1; i1 < abyte0.length - 1; i1++)      l += abyte0[i1] & 0xff;  l &= 0xff;  int j1 = abyte0[1] & 0xf;  if((abyte0[0] & 0xff) != (byte)(l >> 0 & 0xf) || (abyte0[abyte0.length - 1] & 0xff) != (byte)(l >> 4 & 0xf) || abyte0[1] + abyte0[abyte0.length - 2] != 15)      a(16);  boolean aflag[] = new boolean[21];  byte abyte1[] = new byte[8];  int k1 = 1;  int l1 = j1;  for(int i2 = 0; i2 < abyte1.length; i2++)  {      for(; aflag[l1 % aflag.length]; l1++);      aflag[l1 % aflag.length] = true;      abyte1[i2] = abyte0[2 + l1 % aflag.length];      k1 += 2;      l1 += k1;  }  if((abyte1[1] & 0xf) != 3)      a(32);  boolean flag = (abyte1[3] & 0xf) >= 8;  int j2 = (flag ? abyte1[3] - 8 : abyte1[3]) & 0xf;  if(j2 < 1)      a(1);  if(flag)  {      Calendar calendar = Calendar.getInstance();      calendar.set(1, 2000 + (abyte1[4] & 0xf));      calendar.set(2, (abyte1[5] & 0xf) - 1);      calendar.set(5, ((abyte1[6] & 0xf) << 4) + (abyte1[7] & 0xf));      if(System.currentTimeMillis() - calendar.getTimeInMillis() > 0L)          a(18);  }}  private static void a(int paramInt)  {    switch (paramInt & 0xF)    {    case 0:      throw new IllegalArgumentException(a(new long[] { 8460391658548064800L, 8315163859177334048L, 8319872964449869929L, 7205878151055483136L }));    case 1:      throw new IllegalArgumentException(a(new long[] { 8460391658548064800L, 8315163859177334048L, 8319309735340351598L, 7811060823377406308L, 7162256601089340786L, 8532478991051810162L, 120946281218048L }));    case 2:      throw new IllegalArgumentException(a(new long[] { 8462924959242482208L, 2314957309810076517L, 2335505025909089656L, 2378011653932580864L }));    }  }  private static String a(long[] paramArrayOfLong)  {    byte[] arrayOfByte = new byte[paramArrayOfLong.length * 8];    int m = 0;    for (int n = 0; n < paramArrayOfLong.length; n++)      for (int i1 = 0; i1 < 8; i1++)      {        byte i2 = (byte)(int)(paramArrayOfLong[n] >> j[i1] * 8 & 0xFF);        if (i2 == 0)          break;        arrayOfByte[(n * 8 + i1)] = i2;        m++;      }    return new String(arrayOfByte, 0, m);  }  static void a(UltraNetConnection netconnection, String s, Exception exception)  {      netconnection.a(s, exception);  }  static void a(UltraNetConnection netconnection, String s)  {      netconnection.a(s);  }  static void a(UltraNetConnection netconnection, Map map)  {      netconnection.b(map);  }  static void b(UltraNetConnection netconnection, Map map)  {      netconnection.a(map);  }  static void c(UltraNetConnection netconnection, Map map)  {      netconnection.a(map);  }    private class a extends d.a  {    public a()    {    }    public void a(String paramString, Exception paramException)    {      UltraNetConnection.a(UltraNetConnection.this, paramString, paramException);    }    public void a(String paramString)    {      UltraNetConnection.a(UltraNetConnection.this, paramString);    }    public void a(Map<String, Object> paramMap)    {      String str = (String)paramMap.get("code");      if ((!"NetConnection.Connect.Success".equals(str)) && (!"NetConnection.Connect.Bandwidth".equals(str)) && (!"NetConnection.Call.Failed".equals(str)))        UltraNetConnection.a(UltraNetConnection.this, paramMap);      UltraNetConnection.b(UltraNetConnection.this, paramMap);    }    public void a(long paramLong1, long paramLong2)    {      if (!((Boolean)UltraNetConnection.this.configuration().get("enableAcknowledgementEventNotification")).booleanValue())        return;      Map localMap = com.smaxe.uv.a.e.b("NetConnection.Connect.Bandwidth", "'Acknowledgement' event notification.");      localMap.put("acknowledgement", Long.valueOf(paramLong1));      localMap.put("info", new ProtocolLayerInfo(UltraNetConnection.this.getInfo()));      localMap.put("uploadBufferSize", Long.valueOf(paramLong2));      UltraNetConnection.c(UltraNetConnection.this, localMap);    }  }}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.