17th 2018 PoC 동계 해킹캠프 PPAP Writeup

17th hacking camp reversing PPAP writeup

17th 2018 PoC 동계 해킹캠프 PPAP Writeup

인사말

안녕하세요 리버싱 PPAP 문제 출제자 SHGroup 입니다!

이 문제는 Java 로 지금까지 개발을 해오면서 사용했던 안티 디컴파일 방지 기술을 사용하여 냈던 문제입니다.

이 문제를 통해 Java 안티 디컴파일링 기법에 대해 배워가셨으면 좋겠습니다!

이제 라업 시작합니다!

문제 다운로드

다운로드

분석 과정

문제에서 PPAP.jar 파일을 줍니다.

이 파일은 JAR(Java Archive File) 파일로써, Java에서 클래스파일, 메타파일, 리소스파일 등 실행에 필요한 파일들을 압축해놓은 배포용 패키지 파일입니다.

이 파일을 보통 리버싱하기 위해서는 jad(또는 GUI 래핑 버젼인 jd-gui)를 사용하는데, jd-gui 로 이 파일을 열게 되면

jd-gui-1

이렇게 패키지는 잘 나오지만, 막상 클래스 파일을 열게 되면

jd-gui-2

다음과 같이 오류가 나게 됩니다.

jd-gui 는 Java 7 로 만들어졌는데, 이 파일은 Java 8 함수를 사용하여 바이트코드를 계속 읽지 못해서 일어나는 현상입니다.

해결법은 간단합니다. Java 8 을 지원하는 디컴파일러로 여는 것입니다. 저는 luyten 을 사용하였습니다.

luyten-decompile

소스가 나왔습니다.

package com.SHGroup.PPAP;

import java.util.*;
import java.lang.reflect.*;

public class EntryMain
{
    public static void main(final String[] args) {
        final Class<?> ppap = new ClassLoader(EntryMain.class.getClassLoader()) {
            public Class<?> getCustomClass() {
                final byte[] bs = { -54, -2, -70, -66, 0, 0, 0, 52, 0, 71, 7, 0, 2, 1, 0, 4, 80, 80, 65, 80, 7, 0, 4, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 10, 0, 3, 0, 9, 12, 0, 5, 0, 6, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 6, 76, 80, 80, 65, 80, 59, 1, 0, 4, 112, 112, 97, 112, 7, 0, 16, 1, 0, 17, 106, 97, 118, 97, 47, 117, 116, 105, 108, 47, 83, 99, 97, 110, 110, 101, 114, 9, 0, 18, 0, 20, 7, 0, 19, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101, 109, 12, 0, 21, 0, 22, 1, 0, 2, 105, 110, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 59, 10, 0, 15, 0, 24, 12, 0, 5, 0, 25, 1, 0, 24, 40, 76, 106, 97, 118, 97, 47, 105, 111, 47, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109, 59, 41, 86, 10, 0, 15, 0, 27, 12, 0, 28, 0, 29, 1, 0, 8, 110, 101, 120, 116, 76, 105, 110, 101, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 10, 0, 15, 0, 31, 12, 0, 32, 0, 6, 1, 0, 5, 99, 108, 111, 115, 101, 10, 0, 34, 0, 36, 7, 0, 35, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 12, 0, 37, 0, 38, 1, 0, 6, 108, 101, 110, 103, 116, 104, 1, 0, 3, 40, 41, 73, 10, 0, 34, 0, 40, 12, 0, 41, 0, 42, 1, 0, 6, 99, 104, 97, 114, 65, 116, 1, 0, 4, 40, 73, 41, 67, 9, 0, 18, 0, 44, 12, 0, 45, 0, 46, 1, 0, 3, 111, 117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 8, 0, 48, 1, 0, 15, 67, 79, 82, 82, 69, 67, 84, 32, 68, 65, 71, 65, 78, 89, 65, 10, 0, 50, 0, 52, 7, 0, 51, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 12, 0, 53, 0, 54, 1, 0, 7, 112, 114, 105, 110, 116, 108, 110, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 8, 0, 56, 1, 0, 12, 84, 82, 89, 32, 65, 71, 65, 73, 78, 46, 46, 46, 1, 0, 7, 115, 99, 97, 110, 110, 101, 114, 1, 0, 19, 76, 106, 97, 118, 97, 47, 117, 116, 105, 108, 47, 83, 99, 97, 110, 110, 101, 114, 59, 1, 0, 5, 105, 110, 112, 117, 116, 1, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 7, 115, 117, 99, 99, 101, 115, 115, 1, 0, 1, 73, 1, 0, 4, 97, 114, 114, 115, 1, 0, 2, 91, 73, 1, 0, 1, 105, 1, 0, 3, 110, 111, 119, 1, 0, 13, 83, 116, 97, 99, 107, 77, 97, 112, 84, 97, 98, 108, 101, 7, 0, 64, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 80, 80, 65, 80, 46, 106, 97, 118, 97, 0, 33, 0, 1, 0, 3, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 8, -79, 0, 0, 0, 2, 0, 10, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 11, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 12, 0, 13, 0, 0, 0, 1, 0, 14, 0, 6, 0, 1, 0, 7, 0, 0, 2, -30, 0, 7, 0, 7, 0, 0, 1, -39, -69, 0, 15, 89, -78, 0, 17, -73, 0, 23, 76, 43, -74, 0, 26, 77, 43, -74, 0, 30, 3, 62, 16, 56, -68, 10, 89, 3, 16, 75, 79, 89, 4, 16, 68, 79, 89, 5, 16, 74, 79, 89, 6, 16, 66, 79, 89, 7, 16, 67, 79, 89, 8, 16, 108, 79, 89, 16, 6, 16, 75, 79, 89, 16, 7, 16, 79, 79, 89, 16, 8, 16, 115, 79, 89, 16, 9, 16, 119, 79, 89, 16, 10, 16, 123, 79, 89, 16, 11, 16, 127, 79, 89, 16, 12, 16, 99, 79, 89, 16, 13, 16, 103, 79, 89, 16, 14, 16, 107, 79, 89, 16, 15, 16, 111, 79, 89, 16, 16, 5, 79, 89, 16, 17, 16, 23, 79, 89, 16, 18, 16, 27, 79, 89, 16, 19, 16, 31, 79, 89, 16, 20, 6, 79, 89, 16, 21, 16, 7, 79, 89, 16, 22, 16, 11, 79, 89, 16, 23, 16, 15, 79, 89, 16, 24, 16, 51, 79, 89, 16, 25, 16, 55, 79, 89, 16, 26, 16, 59, 79, 89, 16, 27, 16, 63, 79, 89, 16, 28, 16, 35, 79, 89, 16, 29, 16, 39, 79, 89, 16, 30, 16, 36, 79, 89, 16, 31, 16, 47, 79, 89, 16, 32, 17, 0, -80, 79, 89, 16, 33, 17, 0, -55, 79, 89, 16, 34, 17, 0, -44, 79, 89, 16, 35, 17, 0, -33, 79, 89, 16, 36, 17, 0, -94, 79, 89, 16, 37, 17, 0, -39, 79, 89, 16, 38, 17, 0, -88, 79, 89, 16, 39, 17, 0, -64, 79, 89, 16, 40, 17, 0, -105, 79, 89, 16, 41, 17, 0, -9, 79, 89, 16, 42, 17, 0, -5, 79, 89, 16, 43, 17, 0, -29, 79, 89, 16, 44, 17, 0, -10, 79, 89, 16, 45, 17, 0, -24, 79, 89, 16, 46, 17, 0, -113, 79, 89, 16, 47, 17, 0, -17, 79, 89, 16, 48, 17, 0, -109, 79, 89, 16, 49, 17, 0, -117, 79, 89, 16, 50, 17, 0, -8, 79, 89, 16, 51, 17, 0, -112, 79, 89, 16, 52, 17, 0, -125, 79, 89, 16, 53, 17, 0, -28, 79, 89, 16, 54, 17, 0, -107, 79, 89, 16, 55, 17, 0, -94, 79, 58, 4, 44, -74, 0, 33, 25, 4, -66, -97, 0, 8, 4, 62, -89, 0, 57, 3, 54, 5, -89, 0, 42, 44, 21, 5, -74, 0, 39, 54, 6, 29, 25, 4, 21, 5, 46, 21, 6, 21, 5, 6, 21, 5, 4, 96, 104, 96, -126, -97, 0, 7, 4, -89, 0, 4, 3, 96, 62, -124, 5, 1, 21, 5, 44, -74, 0, 33, -95, -1, -45, 29, -102, 0, 14, -78, 0, 43, 18, 47, -74, 0, 49, -89, 0, 11, -78, 0, 43, 18, 55, -74, 0, 49, -79, 0, 0, 0, 3, 0, 10, 0, 0, 0, 82, 0, 20, 0, 0, 0, 7, 0, 11, 0, 8, 0, 16, 0, 9, 0, 20, 0, 10, 0, 22, 0, 11, 0, -109, 0, 12, 1, 41, 0, 13, 1, 122, 0, 11, 1, 124, 0, 14, 1, -122, 0, 15, 1, -120, 0, 16, 1, -117, 0, 17, 1, -111, 0, 18, 1, -103, 0, 19, 1, -75, 0, 17, 1, -63, 0, 22, 1, -59, 0, 23, 1, -51, 0, 24, 1, -48, 0, 25, 1, -40, 0, 27, 0, 11, 0, 0, 0, 72, 0, 7, 0, 0, 1, -39, 0, 12, 0, 13, 0, 0, 0, 11, 1, -50, 0, 57, 0, 58, 0, 1, 0, 16, 1, -55, 0, 59, 0, 60, 0, 2, 0, 22, 1, -61, 0, 61, 0, 62, 0, 3, 1, 124, 0, 93, 0, 63, 0, 64, 0, 4, 1, -114, 0, 51, 0, 65, 0, 62, 0, 5, 1, -103, 0, 28, 0, 66, 0, 62, 0, 6, 0, 67, 0, 0, 0, 81, 0, 8, -1, 1, -117, 0, 5, 7, 0, 1, 7, 0, 15, 7, 0, 34, 1, 7, 0, 68, 0, 0, -4, 0, 5, 1, -1, 0, 32, 0, 7, 7, 0, 1, 7, 0, 15, 7, 0, 34, 1, 7, 0, 68, 1, 1, 0, 1, 1, -1, 0, 0, 0, 7, 7, 0, 1, 7, 0, 15, 7, 0, 34, 1, 7, 0, 68, 1, 1, 0, 2, 1, 1, -6, 0, 4, -6, 0, 8, 14, 7, 0, 1, 0, 69, 0, 0, 0, 2, 0, 70 };
                return super.defineClass("PPAP", bs, 0, bs.length);
            }
        }.getCustomClass();
        try {
            final Object ins = ppap.newInstance();
            final Method m = ppap.getDeclaredMethod("ppap", (Class<?>[])new Class[0]);
            m.setAccessible(true);
            m.invoke(ins, new Object[0]);
        }
        catch (Exception e) {
            final ArrayList<Integer> list = new ArrayList<Integer>(Arrays.asList(0, 5, 10));
            list.stream().filter(num -> num % 2 == 0);
            e.printStackTrace();
        }
    }
}

음? 뭔가 이상한 것 같네요..?

분명 이 파일을 실행하면 다음과 같이 입력을 받고 검사를 합니다.

run_file

근데 저 문자열은 물론 비교하는 구문마저 나오지가 않습니다 ㅠㅠㅠ...

? 그럼 어떻게 해야되는거지?

사실 이 프로그램의 구조는 다음과 같습니다.

  1. 클래스 파일을 메모리에 로드한다.
  2. 클래스 파일에서 PPAP 라는 클래스를 가져온다.
  3. 그 클래스에서 ppap 메소드를 호출한다.

말은 거창하게 써놨지만, 결론은 저 바이트 배열이 바로 클래스파일 이라는 것입니다!

(더 자세히 알아보고 싶으시다면 Java ReflectionClass Loader 에 대해 공부해보세요!)

(저기 catch 블럭에서 사용되는 ArrayList 의 stream 기능이 Java 8 에서 추가된 기능입니다. 이 문제에서는 Java 8의 기능은 사용하지 않았습니다.)

이제 저 바이트 배열을 클래스파일로 따로 뽑아서 분석하면 이 문제를 풀 수 있을 것입니다.

이 배열을 파일로 만들기 위해 다음과 같이 소스를 짰습니다.

import java.io.File;
import java.io.FileOutputStream;

public class solve {
    public static void main(String[] args){
        byte[] bs = { /* byte array */ };
        try{
            File f = new File("ppap.class");
            f.createNewFile();

            FileOutputStream fos = new FileOutputStream(f);
            fos.write(bs);
            fos.close();
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

그리고 컴파일하고 실행합니다.

compile

그러면 다음과 같이 ppap.class 파일이 추출됩니다.

ppap.class

이제 이 파일을 디컴파일하면

ppap.java

정상적으로 소스를 분석할 수 있는 상태가 됩니다.

소스는 다음과 같습니다.

import java.util.*;

public class PPAP
{
    public void ppap() {
        final Scanner scanner = new Scanner(System.in);
        final String input = scanner.nextLine();
        scanner.close();
        int success = 0;
        final int[] arrs = { 75, 68, 74, 66, 67, 108, 75, 79, 115, 119, 123, 127, 99, 103, 107, 111, 2, 23, 27, 31, 3, 7, 11, 15, 51, 55, 59, 63, 35, 39, 36, 47, 176, 201, 212, 223, 162, 217, 168, 192, 151, 247, 251, 227, 246, 232, 143, 239, 147, 139, 248, 144, 131, 228, 149, 162 };
        if (input.length() != arrs.length) {
            success = 1;
        }
        else {
            for (int i = 0; i < input.length(); ++i) {
                final int now = input.charAt(i);
                success += ((arrs[i] != (now ^ i + 3 * (i + 1))) ? 1 : 0);
            }
        }
        if (success == 0) {
            System.out.println("CORRECT DAGANYA");
        }
        else {
            System.out.println("TRY AGAIN...");
        }
    }
}

마지막으로 파이썬으로 역연산을 해주면

import sys
arr = [75, 68, 74, 66, 67, 108, 75, 79, 115, 119, 123, 127, 99, 103, 107, 111, 2, 23, 27, 31, 3, 7, 11, 15, 51, 55, 59, 63, 35, 39, 36, 47, 176, 201, 212, 223, 162, 217, 168, 192, 151, 247, 251, 227, 246, 232, 143, 239, 147, 139, 248, 144, 131, 228, 149, 162]
for i, c in enumerate(arr):
    sys.stdout.write(chr(c ^ i + 3 * (i + 1)))
print("")

final_solve

플래그가 나옵니다!!

또한 원래 jar 파일에서 이 플래그를 입력하면 맞았다고 뜹니다!

correct_daganya

플래그

HCAMP{PPPPPPPPPPAPPPPPPPPPPPPP_P3N_P1N3_4PPLE_4PPL3_P3N}