닷넷 상용 컴포넌트의 경우 지적 재산권을 보호하기 위해서 대부분 난독화가 되어있는데요,
일반적으로 노출되지 않는 요소들의 식별자를 의미없는 문자로 대체하거나 아래와 같이 제어문을 조작하는 방법이 많이 사용됩니다.
Opaque Predicates
opaque predicate는 정적 결과의 분기(that always taken or never taken)에 코드를 삽입하여 branch predicate의 계산 자체를 난독화하는 기술이다. 동일한 결과를 생성하는 trivial branch 명령어 대신 여분의 검사를 추가한다. 또한 절대 취해지지 않는(nerver taken) 분기를 추가해 분석을 더욱 혼란스럽게 만든다.
이 기술은 코드의 cyclomatic 복잡도를 폭발적으로 증가시키고, 대부분의 디컴파일러들이 난해한 코드를 생성하게 만든다.
- Opaque Predicates technique
Splitting
splitting은 함수나 기본 블록을 비조건 점프(unconditional jump), call, opaque predicates 등으로 구성되는 더 작은 함수나 블록으로 분할하여 interleave하는 기술이다. 이는 코드의 전체적인 흐름을 파악하는 것이 불가능하거나 매우 어려워지기 때문에 함수의 context나 상태를 추적하는 것을 어렵게 만든다.
splitting되면 코드는 함수가 덜 선형적으로 보이게 만들기 위해 종종 scramble되거나 랜덤화된다.
- Splitting technique
Control Flow Flattening
**제어 흐름 평탄화(control flow flattening)**는 함수의 논리 흐름을 state machine으로 바꾸고 상태를 기반으로 모든 경로를 단일 dispatch table로 평탄화(flatten)하는 기술이다.
각 분기는 다음 분기에 따라 상태를 업데이트한다. flattening은 해석되는 바이트 코드가 없다는 점을 제외하면 가상화와 모습이 비슷하다.
- Control Flow Flattening technique
출처: 바이너리 난독화 기법(Binary Obfuscation Techniques) (tistory.com)
간혹 상용컨트롤에 어떤 버그가 있거나 특정 로직을 바꿔서 사용하고 싶은 경우, dnspy로 해당 함수를 열었을 때 아래와 같은 코드가 보이면 아찔해 질 때가 있죠.
// Token: 0x06000789 RID: 1929 RVA: 0x00014B1C File Offset: 0x00012D1C
public static void gnx(long bkk, long bkl, out double bkm, out double bkn)
{
bkm = (double)bkk;
for (;;)
{
IL_5B:
int num = 1;
int num2 = 0;
for (;;)
{
num2 ^= 49;
for (;;)
{
int num3 = 5;
for (;;)
{
switch (num3 ^ 45)
{
case 40:
switch (num2)
{
case 47:
switch (num)
{
case 47:
if (bkk != bkl)
{
num = 0;
goto IL_07;
}
return;
case 48:
bkn = (double)bkl;
num = 30;
goto IL_07;
case 49:
if (bkm.CompareTo(bkn) != 0)
{
num = 3;
goto IL_07;
}
goto IL_9B;
case 50:
goto IL_BA;
}
goto Block_1;
case 48:
goto IL_5B;
case 49:
break;
default:
num3 = 4;
continue;
}
IL_07:
num ^= 49;
goto IL_35;
case 41:
goto IL_35;
case 42:
goto IL_3C;
}
break;
IL_35:
num2 = 30;
num3 = 7;
}
}
IL_3C:
continue;
Block_1:
num2 = 1;
}
}
IL_9B:
bool flag = false;
goto IL_C0;
IL_BA:
flag = true;
IL_C0:
if (!flag)
{
if (bkk < bkl)
{
bkn = bkn.wra(1L);
return;
}
bkm = bkm.wra(1L);
}
}
이 때 ChartGPT에게 다음과 같이 명령을 내렸습니다.
“최적화 한 뒤 적절한 변수 이름을 붙여줘”
public static void CalculateValues(long firstValue, long secondValue, out double result1, out double result2)
{
result1 = (double)firstValue;
result2 = (double)secondValue;
if (firstValue != secondValue)
{
if (result1.CompareTo(result2) == 0)
{
return;
}
else
{
result1 = result1.wra(1L);
return;
}
}
else
{
if (firstValue < secondValue)
{
result2 = result2.wra(1L);
return;
}
else
{
result1 = result1.wra(1L);
return;
}
}
}
짠!~ 똑같은 동작을 하는 예쁜 코드가 나왔습니다.
ChatGPT의 활용성은 무궁무진하네요ㅎ
