제어 흐름 난독화란? 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 코드로 변환하면 아래와 같이 난독화 된 것을 볼 수 있다.

x64 Native Tools Command Prompt for VS 2022 프롬프트 실행

git clone https://github.com/curl/curl.git
cd curl
buildconf.bat
Set RTLIBCFG=static
cd winbuild
nmake /f MakeFile.vc mode=static vc=17

빌드는 아래 경로에 생성 됨

curl\builds\libcurl-vc17-x64-release-static-ipv6-sspi-schannel

curl.zip
1.25MB

프로젝트 설정

#include <curl.h>
int main()
{
    CURL* curl = curl_easy_init();
    CURLcode res;
}

 

'Windows > Dev' 카테고리의 다른 글

[VC++] 문자열 복사, 이동  (0) 2024.05.09
detours 빌드 및 적용  (0) 2024.04.11
[VC++] string deallocate  (0) 2022.05.23
get EIP (gcc / vc)  (0) 2019.04.11
C용 초경량 XML 파서 : Mini-XML 소개 및 사용법  (0) 2017.06.29

1. ipsw.me에서 기종 + 버전 선택 후 다운로드

2.

맥 : https://github.com/blacktop/ipsw 다운로드 

우분투(wsl에서 실행 불가) : sudo snap install ipsw 

우분투 : apfs-fuse  다운로드

apfs-fuse
0.44MB

./ipsw extract -d iPhone13,1_15.6.1_19G82_Restore.ipsw

3. https://github.com/keith/dyld-shared-cache-extractor 다운로드 후 make

mac용 다운로드

extractor
0.03MB

4. dyld_shared_cache_arm64e 추출

./extractor dyld_shared_cache_arm64e ~/bin

'iOS' 카테고리의 다른 글

[iOS] C++, Objective C, Swift 상호 함수 호출  (0) 2022.10.18

+ Recent posts