// 유니티에서 작성한 코드
public class GoldManager
{
private int _gold = 100;
public bool UseGold(int gold)
{
if (gold <= _gold)
{
_gold -= gold;
return true;
}
return false;
}
}
// IL2CPP 빌드 결과물을 디컴파일러를 이용해 복원한 코드
[Token(Token = "0x2000002")]
public class GoldManager
{
[Token(Token = "0x4000001")]
[FieldOffset(Offset = "0x8")]
private int _gold;
[Token(Token = "0x6000001")]
[Address(Offset = "0x1E2C14", RVA = "0x1E2C14", VA = "0x1E2C14")]
public bool UseGold(int gold) => new bool();
[Token(Token = "0x6000002")]
[Address(Offset = "0x1E2C34", RVA = "0x1E2C34", VA = "0x1E2C34")]
public GoldManager() {}
}
GoldManager
가 골드에 관련된 클래스이고, UseGold
메서드가 골드를 사용한 후 성공 여부를 bool형으로 반환한다는 것을 유추할 수 있습니다. UseGold
메소드부분을 libil2cpp.so
에서 찾아 항상 true 값으로 반환하도록 수정한다면 무한 골드 사용이 가능합니다.GoldManager
, UseGold
등의 이름이 전혀 알 수 없는 문자가 된다면 앱의 분석 난이도가 대폭 상승할 것입니다. 이것이 코드 난독화의 핵심입니다.
GoldManager
이름이 의미 없는 CCOOFEENFCA
로 바뀌고 메서드 이름도 무작위로 바뀌었습니다. 이렇게 의미 없는 이름으로 난독화가 적용되어, 앱 분석을 방해할 수 있습니다.// 난독화 전
[Token(Token = "0x2000002")]
public class GoldManager
{
[Token(Token = "0x4000001")]
[FieldOffset(Offset = "0x8")]
private int _gold;
[Token(Token = "0x6000001")]
[Address(Offset = "0x1E2C14", RVA = "0x1E2C14", VA = "0x1E2C14")]
public bool UseGold(int gold) => new bool();
[Token(Token = "0x6000002")]
[Address(Offset = "0x1E2C34", RVA = "0x1E2C34", VA = "0x1E2C34")]
public GoldManager() {}
}
// 난독화 후
[Token(Token = "0x2000002")]
public class CCOOFEENFCA
{
[Token(Token = "0x4000001")]
[FieldOffset(Offset = "0x8")]
private int JODFLMBMOHC;
[Token(Token = "0x6000001")]
[Address(Offset = "0x1E2C64", RVA = "0x1E2C64", VA = "0x1E2C64")]
public bool JMPNPBAGNFF(int BEMMJODKCFC) => new bool();
[Token(Token = "0x6000002")]
[Address(Offset = "0x1E2C84", RVA = "0x1E2C84", VA = "0x1E2C84")]
public CCOOFEENFCA() {}
}
// class, method가 난독화 되었지만
// namespace의 존재로 'Shop'에 관련된 것이라는 중요한 힌트가 제공됩니다.
namespace Shop
{
[Token(Token = "0x2000002")]
public class CCOOFEENFCA
{
[Token(Token = "0x6000002")]
[Address(Offset = "0x1E2C84", RVA = "0x1E2C84", VA = "0x1E2C84")]
public CCOOFEENFCA() {}
}
}
public static class AesCrypt
{
public static string GetIK()
{
return "ik123456"; // <---- 직접 암호화키를 코드에 사용
}
public static string GetIV()
{
return "iv123456"; // <---- 직접 암호화키를 코드에 사용
}
}
[Token(Token = "0x2000002")]
public static class AesCrypt
{
[Token(Token = "0x6000001")]
[Address(Offset = "0x1DBE64", RVA = "0x1DBE64", VA = "0x1DBE64")]
public static string GetIK() => (string) null;
[Token(Token = "0x6000002")]
[Address(Offset = "0x1DBEAC", RVA = "0x1DBEAC", VA = "0x1DBEAC")]
public static string GetIV() => (string) null;
}
public static class AesCrypt
{
// 난독화 X
public static string GetIV() => "iv123456";
// 난독화 O
public static string GetIK()
{
// key = "ik123456"
string key = "\uD38C\uF38D\u1387\uD388\u938A\uD38A\u138B\uD38C";
for (int pxVvD = 0, cIsSP = 0; pxVvD < 8; pxVvD++)
{
cIsSP = key[pxVvD];
cIsSP += 0xBAF2;
cIsSP = ~cIsSP;
cIsSP--;
cIsSP += pxVvD;
cIsSP = ((cIsSP << 3) | ((cIsSP & 0xFFFF) >> 13)) & 0xFFFF;
cIsSP += 0x7393;
cIsSP = ~cIsSP;
cIsSP ^= pxVvD;
key = key.Substring(0, pxVvD) + (char)(cIsSP & 0xFFFF) + key.Substring(pxVvD + 1);
}
return key;
}
}
BuyItem
인지 파악이 어려워집니다. 가짜 코드는 런타임 시 실행 속도에 영향이 없습니다. 추가된 가짜 코드만큼 앱의 용량이 증가합니다.public class ShopManager
{
public void BuyItem(int id)
{
// buy item
}
}
public class ShopManager
{
[Token(Token = "0x6000011")]
[Address(Offset = "0x25524C", RVA = "0x25524C", VA = "0x25524C")]
public ShopManager() {}
[Token(Token = "0x600000F")]
[Address(Offset = "0x255244", RVA = "0x255244", VA = "0x255244")]
public void GOEJALHHONB(int MLLCPANPAGA) {}
[Token(Token = "0x6000010")]
[Address(Offset = "0x255248", RVA = "0x255248", VA = "0x255248")]
public void IJLGBLMKGJO(int MLLCPANPAGA) {}
[Token(Token = "0x6000012")]
[Address(Offset = "0x255254", RVA = "0x255254", VA = "0x255254")]
public void MMJFCKPBKEL(int MLLCPANPAGA) {}
[Token(Token = "0x6000013")]
[Address(Offset = "0x255258", RVA = "0x255258", VA = "0x255258")]
public void DDFHLDEEMPN(int MLLCPANPAGA) {}
[Token(Token = "0x6000014")]
[Address(Offset = "0x25525C", RVA = "0x25525C", VA = "0x25525C")]
public void PNFOEBIJHGD(int MLLCPANPAGA) {}
[Token(Token = "0x6000015")]
[Address(Offset = "0x255260", RVA = "0x255260", VA = "0x255260")]
public void OIIGMBCOAHP(int MLLCPANPAGA) {}
}
class Program
{
static void Main()
{
// C# Language Specification
// 유니코드의 사용이 가능합니다.
var Δ = 1;
Δ++;
System.Console.WriteLine(Δ);
}
}
public class ⴆⴊⴌⴍⴒⴇⴏⴓⴊⴌⴈ
{
public ⴆⴊⴌⴍⴒⴇⴏⴓⴊⴌⴈ()
{
}
private int ⴆⴅⴑⴆⴅⴊⴅⴐⴇⴍⴎ;
public bool ⴄⴌⴅⴄⴉⴎⴌⴏⴄⴆⴉ(int ⴊⴓⴏⴈⴈⴆⴏⴎⴑⴍⴒ) => new bool();
public bool ⴉⴊⴄⴌⴄⴑⴄⴍⴆⴍⴌ(int ⴊⴓⴏⴈⴈⴆⴏⴎⴑⴍⴒ) => new bool();
public bool ⴄⴏⴇⴅⴐⴊⴋⴓⴎⴒⴋ(int ⴊⴓⴏⴈⴈⴆⴏⴎⴑⴍⴒ) => new bool();
public bool ⴑⴈⴌⴏⴐⴎⴏⴎⴋⴎⴎ(int ⴊⴓⴏⴈⴈⴆⴏⴎⴑⴍⴒ) => new bool();
public bool ⴊⴍⴐⴐⴏⴎⴌⴅⴊⴌⴅ(int ⴊⴓⴏⴈⴈⴆⴏⴎⴑⴍⴒ) => new bool();
public bool ⴏⴆⴉⴆⴄⴆⴅⴋⴐⴎⴄ(int ⴊⴓⴏⴈⴈⴆⴏⴎⴑⴍⴒ) => new bool();
}
public class ӦӨөӧӧӧөӨӧӨӨөӨӦӧөӨөөөӧӧӨ
{
public ӦӨөӧӧӧөӨӧӨӨөӨӦӧөӨөөөӧӧӨ()
{
}
private int ӦөӦөӧөӨӨӧӦӧөӨӨӨӨӧӨӦөӦӦө;
public bool ӦӦөӧӨӧӧӦӨөөөөӦӨӦӦөөӧӧӦӦ(int ӨөӦөөӦөӧӧөӨӨӦӦөөӨөөӦӧӨӨ) => new bool();
public bool ӨӨӧөөӦөөӨӨӦӧӧӨӧӦӨөӦӨӧӨӦ(int ӨөӦөөӦөӧӧөӨӨӦӦөөӨөөӦӧӨӨ) => new bool();
public bool ӧӧӨӨөӧӨӨӦӦӨӧӦӧөӧӦӦӧӧӨӦӧ(int ӨөӦөөӦөӧӧөӨӨӦӦөөӨөөӦӧӨӨ) => new bool();
public bool ӨӦӨӧӨөӦөӧӨӨӨӧӦӧӧӦӦӧӦөөө(int ӨөӦөөӦөӧӧөӨӨӦӦөөӨөөӦӧӨӨ) => new bool();
public bool ӨөӨӧӦӧөӨӨөөӨөӧӧӧӦөөӧӨӨө(int ӨөӦөөӦөӧӧөӨӨӦӦөөӨөөӦӧӨӨ) => new bool();
public bool ӦӧӧӦӧӨӨӦӨӨөӦӧӧӦөӦӨөӨӧөӨ(int ӨөӦөөӦөӧӧөӨӨӦӦөөӨөөӦӧӨӨ) => new bool();
}
view raw
// 한글 난독화
public class 괆괆괁괆괇괄괋관괄괆괅
{
public 괆괆괁괆괇괄괋관괄괆괅()
{
}
private int 괎괉관괂괇괊괌괂괇괃괉;
public bool 관괏괋괉괂괄괍관괍괌괏(int 괆괉괈괍괅괈괏괍괍괅괈) => new bool();
public bool 괄괉괅괆괉괊괄괍괉괍괁(int 괆괉괈괍괅괈괏괍괍괅괈) => new bool();
public bool 괆괋괈괄괅괋괉괄괉괆괍(int 괆괉괈괍괅괈괏괍괍괅괈) => new bool();
public bool 괌괈괆괇괍괎관괈괌괈괎(int 괆괉괈괍괅괈괏괍괍괅괈) => new bool();
public bool 괌괅괃괆괈괏괇괊괋괍관(int 괆괉괈괍괅괈괏괍괍괅괈) => new bool();
public bool 괋괄괃관괊괎괃괍괂괆괈(int 괆괉괈괍괅괈괏괍괍괅괈) => new bool();
}
view raw