문제
분석
java 파일처럼 보이는 소스코드 하나가 주어진다.
독특한 class 명이 보인다. '_', 'c', 'e', 'h', 'i', 'k', ... '{', '}'.
이것을 보고 아~ flag를 만드는데 쓰이는 것들이구나! 를 깨달았다.
해당 class는 모두 Letter class를 상속받고 있으며 내부에 유의미한 함수나 변수는 없었다.
그렇다면 p도 있고 {도 있고 }도 있지만 '4'를 포함한 숫자는 어떻게 하지?? 라는 의문이 생기는 순간,
'AnonymousClass0'부터 'AnonymousClass7'까지 5개의 클래스가 눈에 들어왔다.
물론 'AnonymousClass4'도 있었다.
그리고 이들도 Letter 클래스를 상속받고 있었다!
처음에는 클래스가 많아보여 당황했지만 이로써 절반의 클래스는 그냥 낱자를 표현하기 위해 존재한다는 것을 알아내었다.
main 함수는 FlagCheckerKt 클래스 내부에 존재한다.
argv로 값 하나를 전달받고 그 값을 validateFlag 함수로 전달한다.
public static final int validateFlag(@NotNull String flag) { Intrinsics.checkParameterIsNotNull(flag, "flag"); SizeResult check = SizeResultFactory.Companion.check(flag.length(), IllegalMonitorStateException.class); if (check instanceof Correct) { Map destination$iv$iv = new LinkedHashMap(); for (Pair element$iv$iv : SequencesKt___SequencesKt.mapIndexed(SequencesKt___SequencesKt.filter(SequencesKt___SequencesKt.map(StringsKt___StringsKt.asSequence(flag), FlagCheckerKt$validateFlag$1.INSTANCE), FlagCheckerKt$validateFlag$2.INSTANCE), FlagCheckerKt$validateFlag$3.INSTANCE)) { List list$iv$iv; Character key$iv$iv = Character.valueOf(((Character) ((FlagChar) element$iv$iv.getFirst()).getC()).charValue()); Map $receiver$iv$iv$iv = destination$iv$iv; List value$iv$iv$iv = $receiver$iv$iv$iv.get(key$iv$iv); if (value$iv$iv$iv == null) { ArrayList answer$iv$iv$iv = new ArrayList(); $receiver$iv$iv$iv.put(key$iv$iv, answer$iv$iv$iv); list$iv$iv = answer$iv$iv$iv; } else { list$iv$iv = value$iv$iv$iv; } list$iv$iv.add(Integer.valueOf(((Number) element$iv$iv.getSecond()).intValue())); } return checksum(destination$iv$iv); } else if (check instanceof Incorrect) { System.out.print("Failed"); System.exit(-1); throw new RuntimeException("System.exit returned normally, while it was supposed to halt JVM."); } else { throw new NoWhenBranchMatchedException(); } }
매우 복잡해보이지만 사실 그렇지만은 않다.
destination은 LinkedHashMap 객체이다.
이 객체는 검색해보면 '순서를 유지하는 해시맵'이라 나온다.
아~ 해시테이블 같은거구나... 라고 이해했다.
앞으로 이 해시테이블을 키-value 테이블이라 하겠다.
그리고 SequenceKt.mapIndexed, filter, map, asSequence... 이런 함수들에 대한 구글링을 곁들여서 위 코드를 이해한 결과는 아래와 같다.
(나중에 알고 보니 이것은 java가 아닌 kotlin이라는 언어로 작성된 것이었다. kotlin은 JVM 위에서 동작하는 프로그래밍 언어이다.)
0. flag의 길이는 IllegalMonitorStateException 클래스 이름의 길이와 같아야 한다. (즉, 28이다.)
1. flag에서 index0 부터 (글자를) 하나하나 받아온다.
2-1. destination 해시테이블에 해당 글자를 키로 하는 value가 없다면 그 글자를 키로 두고 value는 index 값으로 설정.
2-2. destination 해시테이블에 해당 글자를 키로 하는 value가 있다면 해당 value에다가 index 값도 연결함 (arrayList처럼)
3. checksum함수로 destination을 전달하여 유효성 검사
public static final int checksum(@NotNull Map> grouped) { Intrinsics.checkParameterIsNotNull(grouped, "grouped"); try { Collection destination$iv$iv = new ArrayList(grouped.size()); for (Entry item$iv$iv : grouped.entrySet()) { destination$iv$iv.add(new Pair(Class.forName("team.p4.pudliszki." + String.valueOf(((Character) item$iv$iv.getKey()).charValue())).newInstance(), Integer.valueOf(compress((List) item$iv$iv.getValue())))); } Iterable iterable = (List) destination$iv$iv; destination$iv$iv = new ArrayList(CollectionsKt__IterablesKt.collectionSizeOrDefault(iterable, 10)); for (Pair pair : iterable) { int intValue; Object first = pair.getFirst(); if (first instanceof p) { intValue = ((Number) pair.getSecond()).intValue() - 27040; } else if (first instanceof AnonymousClass4) { intValue = ((Number) pair.getSecond()).intValue() - 1; } else if (first instanceof {) { intValue = ((Number) pair.getSecond()).intValue() - 2; } else if (first instanceof AnonymousClass0) { intValue = ((Number) pair.getSecond()).intValue() - 452; } else if (first instanceof AnonymousClass1) { intValue = ((Number) pair.getSecond()).intValue() - 327; } else if (first instanceof AnonymousClass5) { intValue = ((Number) pair.getSecond()).intValue() - 17; } else if (first instanceof AnonymousClass7) { intValue = ((Number) pair.getSecond()).intValue() - 22; } else if (first instanceof c) { intValue = ((Number) pair.getSecond()).intValue() - 23; } else if (first instanceof e) { intValue = ((Number) pair.getSecond()).intValue() - 21; } else if (first instanceof h) { intValue = ((Number) pair.getSecond()).intValue() - 786; } else if (first instanceof i) { intValue = ((Number) pair.getSecond()).intValue() - 16; } else if (first instanceof k) { intValue = ((Number) pair.getSecond()).intValue() - 643; } else if (first instanceof l) { intValue = ((Number) pair.getSecond()).intValue() - 486; } else if (first instanceof n) { intValue = ((Number) pair.getSecond()).intValue() - 8; } else if (first instanceof s) { intValue = ((Number) pair.getSecond()).intValue() - 11; } else if (first instanceof t) { intValue = ((Number) pair.getSecond()).intValue() - 5; } else if (first instanceof u) { intValue = ((Number) pair.getSecond()).intValue() - 25; } else if (first instanceof _) { intValue = ((Number) pair.getSecond()).intValue() - 19849; } else if (first instanceof }) { intValue = ((Number) pair.getSecond()).intValue() - 27; } else { intValue = -1337; } destination$iv$iv.add(Integer.valueOf(intValue)); } return CollectionsKt___CollectionsKt.sumOfInt((List) destination$iv$iv); } catch (ClassNotFoundException e) { System.out.print("Failed"); System.exit(-1); throw new RuntimeException("System.exit returned normally, while it was supposed to halt JVM."); } }
checksum 함수는 올바른 flag에 의해 destination 해시테이블이 형성되었을 때만 0을 반환하게 되어있고 구체적으로 아래와 같은 동작을 수행한다.
1. hash table의 value들이 모두 연결리스트 형식인데 이를 compress함수에 넣어 특정 값을 얻어냄
2. 키는 destination 해시테이블 것이 유지되는데 1에서 얻어낸 값을 value로 하는 두번째 hash table을 생성함
3. 2에서 생성한 hash table에서 키가 'p'일때 value는 27040, 키가 '4'일때 value는 1, 키가 '{'일때 value는 2... 이런식으로 주어진 조건을 만족해야지만이 0을 return 함
솔직히 위 과정에서 compress 함수 때문에 약간 애를 먹었다.
예를들어 키가 '0'일때 value는 452여야 하는데 이는 누가봐도 index 2개가 compress된 형태였다.
이 compress 함수에 대해 구글링을 열~~심히 해봤지만 번번히 실패하고...
그러던 찰나에 주어진 파일에 compress 함수가 떡하니 존재하는게 보였다.
public static final int compress(@NotNull Listvalue) { Intrinsics.checkParameterIsNotNull(value, "value"); return SequencesKt___SequencesKt.sumOfInt(SequencesKt___SequencesKt.map(CollectionsKt___CollectionsKt.asSequence(value), new FlagCheckerKt$compress$1(new Multiplier(0, 1, null)))); }
'이런 바보...'라 생각하며 compress 함수의 내용 및 이와 연관된 Multiplier 클래스 등을 면밀히 살펴봤는데 잘 이해가 되지 않았다.
그러던 중! Multiplier클래스의 get함수에서
굳이 this.m값을 32로 곱했다가 32로 나눈 뒤 반환하는게 이상하다는 생각이 들었다.
그냥 32라는 수가 눈에 띄었다
그리고 flag 길이는 28 설마..?
그렇다. compress는 여러 숫자가 있을때 이를 이어붙여 두자리수 또는 세자리수를 하나 만들고 이를 32진법으로 읽었을때의 수를 반환하는 과정이었다.
예를 들어, 452 = 14 * 32 + 4 이므로
'0'은 flag의 index14와 index4에 해당한다는 의미!
이런 식으로 flag를 완성할 수 있었다.
FL4G
'CTFs' 카테고리의 다른 글
[0CTF/TCTF 2019 Quals] If on a winters night a traveler writeup (118) | 2019.03.27 |
---|---|
[CONFidence CTF 2019 Teaser] Oldschool writeup (0) | 2019.03.20 |
[CONFidence CTF 2019 Teaser] Bro, do you even lift? writeup (0) | 2019.03.19 |
[CONFidence CTF 2019 Teaser] Count me in! writeup (0) | 2019.03.19 |
[HumanCTF] More than privacy writeup (0) | 2018.10.13 |