제어 흐름 난독화란? if, switch와 같은 제어/분기 구문에 난독화를 하는 것

아래는 예제이다.

package obfuscate.test;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import java.util.Random;
import bam.boo.zoo;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Random을 넣은 이유는, 상수를 넣으면 최적화 되어 버린다.
        Random rand = new Random();
        int num = rand.nextInt(100000);
        System.out.println("hohoh!oho " + num);
        zoo.func1(num);

        System.out.println("end!");
    } 
}

난독화 테스트를 위해 제어 흐름을 if 구문을 사용하여 코딩하였다.

package bam.boo;

public class zoo {
    public static void func1(int num)
    {
        if (num > 1)
        {
            System.out.println("if");
        }
        else
        {
            System.out.println("else");
        }
    }
}

zoo class를 decompile하면 아래와 같다.

package a.a;

import java.io.PrintStream;

public class a {
   public static void a(int var0) {
      PrintStream var1;
      String var2;
      if (var0 > 1) {
         var1 = System.out;
         var2 = "if";
      } else {
         var1 = System.out;
         var2 = "else";
      }

      var1.println(var2);
   }
}

아래는 smali 코드이다.

smali 코드로 변환하면서 goto 명령어가 생성되었다.

goto와 같은 분기 명령어에 pass를 적용한 후 dummy 코드를 포함하여 ifne/goto 명령어를 적절히 추가하면 분기가 복잡하게 보이도록 만들 수 있다.

@Override
public void visitProgramClass(ProgramClass programClass)
{
    if ((programClass.getAccessFlags() & AccessConstants.INTERFACE) != 0)
    {
        return;
    }
    // 수를 참조할때 static 변수가 아니면 최적화되어 버리므로 번거롭지만 생성하여야 한다.
    ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass);

    ClassEditor classEditor = new ClassEditor(programClass);
    int nameIndex = constantPoolEditor.addUtf8Constant("valueX");
    int descriptorIndex = constantPoolEditor.addUtf8Constant("I");
    
    ProgramField opaqueField = new ProgramField(
        AccessConstants.PRIVATE | AccessConstants.STATIC, nameIndex, descriptorIndex, null);
    classEditor.addField(opaqueField);

    new InitializerEditor(programClass).addStaticInitializerInstructions(/*mergeIntoExistingInitializer=*/true,
            // static 변수에 저장
            ____ -> {
                ____.ldc(rand.nextInt(10000)) // 양수 생성
                    .putstatic(programClass, opaqueField);
            });
    programClass.accept(new AllMethodVisitor(new AllAttributeVisitor(this)));
}

@Override
public void visitBranchInstruction(Clazz             clazz,
                                   Method            method,
                                   CodeAttribute     codeAttribute,
                                   int               offset,
                                   BranchInstruction branch)
{
    // goto 명령어에만 적용한다.
    if (branch.opcode != Instruction.OP_GOTO) {
        return;
    }
    InstructionSequenceBuilder ____ = new InstructionSequenceBuilder((ProgramClass)clazz);
    FrameFinder finder = new FrameFinder(this.partialEvaluator, offset);
    codeAttribute.instructionsAccept(clazz, method, finder);
    if (!finder.targets.isEmpty())
    {
            // x + 1 != 0
            // x 값으로 양수를 생성했으므로 위의 수식은 무조건 성립한다.
            ____.getstatic(clazz.getName(), "valueX", "I") // 위에서 생성한 x값
                .iconst_1() // 상수 1
                .iadd() // 덧셈
                .ifne(branch.branchOffset) // 0이 아니면 분기
                // 여기서부터 절대 실행되면 안됨(dummy)
                .getstatic("java/lang/System", "out", "Ljava/io/PrintStream;")
                .ldc("never seen")
                .invokevirtual("java/io/PrintStream", "println", "(Ljava/lang/String;)V")
                .goto_(finder.targets.get(rand.nextInt(finder.targets.size())) - offset);
                // 끝
                
                // 명령어 교체
                codeAttributeEditor.replaceInstruction(offset, ____.instructions());
    }
}

위 pass를 적용하면 goto 명령어가 getstatic ~ goto로 대체된다.

apk 빌드 후 smali 코드를 확인해 보면 아래와 같다.

위에서는 ifne가 goto 명령어를 대체하므로, 무조건 참이 되는 수식을 작성하여 원래 가고자 했던 곳으로 점프시킨다.

그 밑에는 실행되면 안되는 fake코드를 넣고, 마지막에는 goto 명령어를 넣어서 임의의 위치를 가리키게 한다.

 

java 코드로 변환하면 아래와 같이 난독화 된 것을 볼 수 있다.

+ Recent posts