변조된 APK Sign값 후킹

홈 > 안드로이드 > 안드로이드 > 기술
안드로이드

변조된 APK Sign값 후킹

S tutorial1 48 14474 6

가끔 APK 를 분석하고 리패키징 및 싸인까지 마치고 Apk 를 실행시켰을 때 apk 에 싸인된 값을 들고가 서버에서 비교하는 보안 기법이 있습니다(disconnect Server).

이럴때 사용하는게 있는데 중국분이 만들어놓은 hookpms 입니다.

sign 값들을 불러오는 함수들이 몇 존재하는데 해당 함수들을 후킹하여 오리지날과 같은 sign 값으로 변경시켜 리턴하는 것입니다.

0c47914ab96d568b60b39429ef134bd0_1570115222_0263.JPG
앱 디컴파일 후 나오는 Smali 파일중 Main(apk의 시작점 정도)문을 찾던가 앱이 실행될 때에 최대한 빨리 후킹함수를 호출할 수 있도록 코드를 끼워넣어 줍니다.

cn.zip 이라는 압축파일을 앱 디컴파일 했을때 나오는 smali 폴더에 붙혀넣어줍니다.

0c47914ab96d568b60b39429ef134bd0_1570115493_0076.JPG
ServiceManagerWraper.smali 파일을 오픈했을때 해당 게임의 원본 Sign 값을 입력하여 리턴되도록 만들어 줍니다.

이렇게만 해주면 왠만한 Sign Check 보안을 우회할 수 있습니다.(인간 혹은 뱀파이어는 그랬음 아몰랑.ㅋ)



48 Comments
6 지존 2019.10.04 00:19  
몰랐던 정보네요 감사합니다!
S 코드몽키 2019.10.04 03:15  
이제 국내에서도 이러한 툴들을 제작하는 능력자분들이 속출할거라고 굳게 믿고있습니다!
S 코드몽키 2019.10.04 04:23  
방금 인간혹은뱀파이어에 시도해봤는데 앱이 바로 뻗어버리네요
1. 오리지날 사인값은 어디서볼수있나요?

제가 시도했던 순서는
디컴파일 - Androidmanifest.xml에서 시작점 확인 -> android:name="com.google.firebase.MessagingUnityPlayerActivity" 확인
-> MessagingUnityPlayerActivity.smali 에서 Oncreate메소드 바로 밑줄에

invoke-static {p1}, Lcn/wjdiankong/hookpms/ServiceManagerWraper;->hookPMS(Landroid/content/Context;)V

입력
시도1 : smali 폴더에 다운로드받은 cn.zip 넣음
컴파일 -> 설치 -> 앱뻗음

시도2 : smali 폴더에 다운로드받은 cn.zip 압축풀어서 나온 cn 폴더를 집어넣음
컴파일 -> 설치 -> 앱뻗음

일단 적어주신 설명대로는 그대로따라한거같은데 어디서 문제인지 파악이안되네요
S tutorial1 2019.10.04 12:10  
지금 cn.zip 올린것 자체가 인간 혹은 뱀파이어를 위한 hookpms 입니다. 그리고 앱이 바로 뻗는문제는
cn/wjdiankong/hookpms/ 경로에 ServiceManagerWraper 가 없어서 그런듯하네요.
\smali\cn\wjdiankong\hookpms\ 경로가 제대로 설정되있는지 확인해 보세요.
오리지날 사인값 찾는법은 잊어먹었네요.ㅜㅜ 안드로이드를 옛날에 건드려봐서
S 코드몽키 2019.10.04 14:18  
앗 감사합니다  이상하네요 cn/wjdiankong/hookpms/ 경로에 ServiceManagerWraper  맞는거같은데.. 압축풀고나온 cn 폴더를 그대로 스말리에 넣었거든요
S tutorial1 2019.10.04 12:16  
\smali\android\support\multidex 경로의 MultiDexApplication.smali 파일에서
attachBaseContext 메소드에다가 다음과같이 추가해 보세요. 저는 여기에 hookpms 메소드를 호출하도록 했습니다.
.method protected attachBaseContext(Landroid/content/Context;)V
    .locals 0

    invoke-static {p1}, Lcn/wjdiankong/hookpms/ServiceManagerWraper;->hookPMS(Landroid/content/Context;)V

    .prologue
    .line 38
    invoke-super {p0, p1}, Landroid/app/Application;->attachBaseContext(Landroid/content/Context;)V

    .line 39
    invoke-static {p0}, Landroid/support/multidex/a;->a(Landroid/content/Context;)V

    .line 40
    return-void
.end method
S 코드몽키 2019.10.04 14:18  
답변감사합니다^^ 한번 빠르게 테스트 해볼게요 ㅎㅎ
S 코드몽키 2019.10.04 16:17  
방금 게임을 새로 다운받아 디컴파일을 해보니 \smali\android\support 경로에 multidex 라는 폴더가 존재하질않네요  노트패드++로  스말리폴더 통으로 검색해봐도 MultiDexApplication 라는 파일은 존재하지 않구요

혹시 근래  최신버전에 작업해보신적 있으신가요?
S tutorial1 2019.10.04 17:07  
\smali\com\google\firebase 폴더에
MessagingUnityPlayerActivity.smali 아닛!;; 여기로 바뀌었네요

# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 1
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;
invoke-static/range {p0 .. p0}, Lcn/wjdiankong/hookpms/ServiceManagerWraper;->hookPMS(Landroid/content/Context;)V
    .prologue
    .line 71
    iget-object v0, p0, Lcom/google/firebase/MessagingUnityPlayerActivity;->mUnityPlayer:Lcom/unity3d/player/UnityPlayer;

    if-eqz v0, :cond_0

    .line 72
    iget-object v0, p0, Lcom/google/firebase/MessagingUnityPlayerActivity;->mUnityPlayer:Lcom/unity3d/player/UnityPlayer;

    invoke-virtual {v0}, Lcom/unity3d/player/UnityPlayer;->quit()V

    .line 73
    const/4 v0, 0x0

    iput-object v0, p0, Lcom/google/firebase/MessagingUnityPlayerActivity;->mUnityPlayer:Lcom/unity3d/player/UnityPlayer;

    .line 75
    :cond_0
    invoke-super {p0, p1}, Lcom/unity3d/player/UnityPlayerActivity;->onCreate(Landroid/os/Bundle;)V

    .line 76
    return-void
.end method

OnCreate 메소드에서 호출하도록 했습니다.
S 코드몽키 2019.10.04 17:45  
네 MessagingUnityPlayerActivity.smali  저도 이곳 oncreate 에서 호출하도록 했습니다
invoke-static/range {p0 .. p0}, Lcn/wjdiankong/hookpms/ServiceManagerWraper;->hookPMS(Landroid/content/Context;)V 적어주신 코드로 cn 폴더를 넣으니 이젠 게임은 실행되는데 서버 선택하는 화면에서 종료되네요.. 하;; 점점 미궁으로 빠진다 ~.~
1 dl1233 2019.10.04 10:47  
어렵네요..ㅎㅎ
3 아토 2019.10.04 13:42  
보기만 하는데도 어질어질 ㅎㅎ
3 라피스넷 2019.10.04 16:13  
리패키징으로 각종 로그인이 되지 않는 이슈가 해결될 수 있겠네요
S tutorial1 2019.10.04 16:57  
고건 아닙니다. 구글 로그인은 여전히 할 수 없습니다.

Congratulation! You win the 15 축하드립니다! 당첨되셨어요~!

3 라피스넷 2019.10.04 17:42  
안되는군요.. 답변 감사드립니다
S 코드몽키 2019.10.05 12:37  
방금 블모넷에서 작업된 인간혹은뱀파이어 다운받아봤더니 hookpms 가 들어있네요 문제는 블모넷에서 작업된것도 역시 실행되자마자 로그인창에서 튕기는 현상이.. ㅎㅎ 아마 보안업뎃이 된듯하네요
S 붉은상어 2019.10.10 17:26  
팅기는 이유가 서버에서 서명값을 검증하고 있어서 팅기는것 같네요.
앱마다 고유한 서명값이 있는데요.
지금 cn 파일안에 코드는 특정 앱의 서명파일인것 같습니다.

.method public static hookPMS(Landroid/content/Context;)V
    .locals 3
    .param p0, "context"    # Landroid/content/Context;

    .prologue
    .line 46
    const-string v0, "3082019930820102a00302010202041c52f370300d06092a864886f70d01010505003010310e300c060355040a0c0568696465613020170d3137303333313039303435315a180f32303637303331393039303435315a3010310e300c060355040a0c05686964656130819f300d06092a864886f70d010101050003818d0030818902818100affca1417e72f253f8811284e0b27741a19550170bfc902814b33f216478971bd64dc1313da3aa622b31e9790f165289b0ef715d50435fe683e8efbbc40074c71e095dd4e7252e7d27530747d30a8c95a418c4348b35308b03e4f61d1546b9d994f9a885a8c0cd673b91267cce648a84bbfabfd90b6c84fe163d493a38e197e90203010001300d06092a864886f70d01010505000381810033dcbe0d8c10827bdea8e3c84a1eaa3912c4fc37f536b7069b323a4384cfa36c6de8fe4d0216d16c1ea539b49af557bc8e6f5b61d51d2c0c10601339fa7027f78faa8b16130a5affdffe873349bf5d9267ff419f56b71c895fd6c981ccfea6bcc952eca7460ed95544f4aeb046cbeca9e449bfc8d8acc79cc382d5b2014070e9"

    .line 46
    .line 47
    const-string v1, "com.hidea.manorvampire"

    const/4 v2, 0x0

    invoke-static {p0, v0, v1, v2}, Lcn/wjdiankong/hookpms/ServiceManagerWraper;->hookPMS(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;I)V

    .line 48
    return-void
.end method

여기서 저기 긴 문자열 있죠? 3082로 시작되는거요.
그 부분을 우회할 앱의 서명 값을 넣어야 됩니다.
S 붉은상어 2019.10.10 17:29  
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

public class GetSignature
{
  private static final Object mSync = new Object();
  private static WeakReference<byte[]> mReadBuffer;
 
  public static void main(String[] args)
  {
    if (args.length < 1) {
      System.exit(-1);
    }
    System.out.println(args[0]);
   
    String filePath = args[0];
   
    byte[] readBuffer = null;
    synchronized (mSync)
    {
      WeakReference<byte[]> readBufferRef = mReadBuffer;
      if (readBufferRef != null)
      {
        mReadBuffer = null;
        readBuffer = (byte[])readBufferRef.get();
      }
      if (readBuffer == null)
      {
        readBuffer = new byte[63];
        readBufferRef = new WeakReference(readBuffer);
      }
    }
    try
    {
      WeakReference<byte[]> readBufferRef = null;
      JarFile jarFile = new JarFile(filePath);
      Certificate[] certs = null;
     
      Enumeration<?> entries = jarFile.entries();
      while (entries.hasMoreElements())
      {
        JarEntry je = (JarEntry)entries.nextElement();
        if ((!je.isDirectory()) &&
          (!je.getName().startsWith("META-INF/")))
        {
          Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
          if (localCerts == null)
          {
            System.err.println("[error] Package has no certificates at entry " + je.getName());
            jarFile.close();
            return;
          }
          if (certs == null) {
            certs = localCerts;
          } else {
            for (int i = 0; i < certs.length; i++)
            {
              boolean found = false;
              for (int j = 0; j < localCerts.length; j++) {
                if ((certs[i] != null) && (certs[i].equals(localCerts[j])))
                {
                  found = true;
                  break;
                }
              }
              if ((!found) || (certs.length != localCerts.length))
              {
                System.err.println(
                  "[error] Package has mismatched certificates at entry " + je.getName());
                jarFile.close();
                return;
              }
            }
          }
        }
      }
      jarFile.close();
      synchronized (mSync)
      {
        mReadBuffer = readBufferRef;
      }
      if ((certs != null) && (certs.length > 0))
      {
        int N = certs.length;
        for (int i = 0; i < N; i++)
        {
          String charSig = new String(toChars(certs[i].getEncoded()));
          System.out.println(certs[i].toString());
          System.out.println("[Sign value] : " + charSig);
        }
      }
      else
      {
        System.err.println("[error]Package has no certificates");
        return;
      }
    }
    catch (CertificateEncodingException ex)
    {
      Logger.getLogger(GetSignature.class.getName()).log(Level.SEVERE, null, ex);
    }
    catch (IOException e)
    {
      System.err.println("[error]Exception reading " + filePath + "\n" + e);
      return;
    }
    catch (RuntimeException e)
    {
      System.err.println("[error]Exception reading " + filePath + "\n" + e);
      return;
    }
  }
 
  private static char[] toChars(byte[] mSignature)
  {
    byte[] sig = mSignature;
    int N = sig.length;
    int N2 = N * 2;
    char[] text = new char[N2];
    for (int j = 0; j < N; j++)
    {
      byte v = sig[j];
      int d = v >> 4 & 0xF;
      text[(j * 2)] = ((char)(d >= 10 ? 97 + d - 10 : 48 + d));
      d = v & 0xF;
      text[(j * 2 + 1)] = ((char)(d >= 10 ? 97 + d - 10 : 48 + d));
    }
    return text;
  }
 
  private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer)
  {
    try
    {
      InputStream is = jarFile.getInputStream(je);
      while (is.read(readBuffer, 0, readBuffer.length) != -1) {}
      is.close();
     
      return je != null ? je.getCertificates() : null;
    }
    catch (IOException e)
    {
      System.err.println("[error] Exception reading " + je.getName() + " in " + jarFile.getName() + ": " + e);
    }
    return null;
  }
}
-------------------------------------------------------------------------------------------------------------------------------------
컴파일 하셔서 사용하세요. 원본 서명 값을 확인할 수 있어요~
명령어는 java -jar GetSignature.jar  (apk 이름) 입니다.
12 모야모야 2020.01.17 21:56  
아하 이해했습니다 저번에 강의를 올리셨던거 봤습니다. ...cn이라는 파일을 어느폴더에 넣고 어떻게 적용하라는지 잘모르겠네요... 좀 알려주실수있을까요?
6 arahan 2019.10.31 16:31  
유용한 정보 감사드립니다
2 에스트 2019.11.04 12:43  
잘봤습니다 감사합니다
2 djucd 2019.11.06 17:27  
굉장히 복잡하네요
4 jmj877 2019.12.06 10:58  
유용한 정보 감사합니다.
바로 확인해봐야겠어요
댓글이 참 풍년이에요
2 95grit 2019.12.07 00:00  
감사합니다

Congratulation! You win the 20 축하드립니다! 당첨되셨어요~!

2 빙굴 2019.12.12 08:00  
감사합니당
3 가람 2019.12.15 13:58  
원본 sign 값으로 검증시 후킹하는방법이 이렇게 진행되는거였군요
2 songod 2019.12.19 17:10  
감사합니다.
2 코몽님추종자 2019.12.25 20:14  
괴물분들이 많다는걸 새삼 느낍니다..
1 케록 2020.01.08 16:41  
나중에 다시 한번....
13 Rohas 2020.01.09 16:14  
대박,.,,
3 lofin 2020.02.27 12:54  
전부 옮게 했는데 앱이 검정색 화면에서 진행이 안되네요.
1 니가가라하와이 2020.03.02 02:07  
공부하겠습니다
2 호랑이쿨광 2020.03.09 05:58  
감사합니다.!
7 래바리 2020.04.03 18:48  
감사합니다.
3 Linjjang 2020.04.21 13:17  
감사합니다
1 h2ro 2020.05.16 19:03  
감사합니다 ㅎ
1 이나얼아니 2020.05.20 13:27  
감사합니다
7 임중호 2020.05.23 22:52  
감사합니다~
7 임중호 2020.05.24 01:28  
잘 보고가겠습니다.
2 간식마렵다 2020.07.17 20:31  
감사합니다

Congratulation! You win the 7 축하드립니다! 당첨되셨어요~!

4 쿨록 2020.08.07 19:14  
재미있네요
1 KK102 2020.08.15 04:18  
ApkSignatureKiller과 hookpms 2 개
사용하려면 어떻게 smali를 쓰시겠습니까?
1 KK102 2020.08.15 06:02  
말이 나빴다
Package 계를 확인하는 것과
toCharsString이있는 경우에 대처하는 방법을 알고 싶다
1 린엔 2020.11.18 14:38  
보기만 하는데도 어질어질 ㅎㅎ
1 0413 2021.04.27 03:39  
감사합니다.!
1 korminho 2021.07.26 20:48  
감사합니다아~

Congratulation! You win the 3 축하드립니다! 당첨되셨어요~!

3 h123 2021.09.22 19:16  
정보 감사합니다
2 leeSSi 2022.04.06 10:11  
좋은 정보 감사합니다!