CONFidence CTF 2019 Teaser - Pudliszki (128pt)

March 17, 2019
kotlin

Pudliszki (128pt)

Reversing

Description: For those who don’t like all those pesky low-level reversing problems.

Files: pudliszki.tar.gz

Solution:

In this problem, we are given a .jar file that performs some sort of flag validation. After opening it in JD-GUI, it is clear that some of the original source was written in Kotlin.

Decompiling main in team.p4.pudliszki.FlagCheckerKt, we obtain the following:

public static final void main(@NotNull String[] args)
  {
    Intrinsics.checkParameterIsNotNull(args, "args");String[] $receiver = args;int $i$a$-with-FlagCheckerKt$main$1 = 0;
    SizeResult localSizeResult = SizeResultFactory.Companion.check($receiver.length, A.class);
    if ((localSizeResult instanceof Correct))
    {
      String str1;
      if (validateFlag($receiver[0]) == 0)
      {
        str1 = "Nice!";System.out.print(str1);
      }
      else
      {
        str1 = "Not today";System.out.print(str1);
        int i = -1;System.exit(i);throw ((Throwable)new RuntimeException("System.exit returned normally, while it was supposed to halt JVM."));
      }
    }
    else if ((localSizeResult instanceof Incorrect))
    {
      String str2 = "Failed";System.out.print(str2);
      int j = -1;System.exit(j);throw ((Throwable)new RuntimeException("System.exit returned normally, while it was supposed to halt JVM."));
    }
  }

It is a bit messy, but still understandable. First we see a call that is SizeResultFactory.Companion.check(args.length, A.class) that seems to return either Correct or Incorrect. Let’s decompile that method:

public final <T> SizeResult check(int i, @NotNull Class<T> c)
    {
      Intrinsics.checkParameterIsNotNull(c, "c");
      return i == c.getSimpleName().length() ? (SizeResult)Correct.INSTANCE : (SizeResult)Incorrect.INSTANCE;
    }

This method simply compares the integer argument to the length of the class argument’s name and returns Correct if they are equal.

So args.length must equal 1. (This is likely the flag)

Next, we see a call to validateFlag(args[0]). Let’s examine the validateFlag method:

public static final int validateFlag(@NotNull String flag)
  {
    Intrinsics.checkParameterIsNotNull(flag, "flag");
    SizeResult localSizeResult = SizeResultFactory.Companion.check(flag.length(), IllegalMonitorStateException.class);
    Object $receiver$iv;
    if ((localSizeResult instanceof Correct))
    {
      Sequence localSequence1 = SequencesKt.mapIndexed(SequencesKt.filter(SequencesKt.map(StringsKt.asSequence((CharSequence)flag), (Function1)validateFlag.1.INSTANCE), (Function1)validateFlag.2.INSTANCE), (Function2)validateFlag.3.INSTANCE);
      int $i$f$groupBy;
      Object localObject1 = $receiver$iv;Map destination$iv$iv = (Map)new LinkedHashMap();
      int $i$f$groupByTo;
      Sequence $receiver$iv$iv;
      List localList1;
      Integer localInteger;
      for (Iterator localIterator = $receiver$iv$iv.iterator(); localIterator.hasNext(); localList1.add(localInteger))
      {
        Object element$iv$iv = localIterator.next();
        Pair c = (Pair)element$iv$iv;int $i$a$-groupBy-FlagCheckerKt$validateFlag$4 = 0;Object key$iv$iv = Character.valueOf(((Character)((FlagChar)c.getFirst()).getC()).charValue());
        Map $receiver$iv$iv$iv = destination$iv$iv;
        int $i$f$getOrPut;
        Object value$iv$iv$iv = $receiver$iv$iv$iv.get(key$iv$iv);
        int $i$a$2$getOrPut;
        Object answer$iv$iv$iv = new ArrayList();
        
        $receiver$iv$iv$iv.put(key$iv$iv, answer$iv$iv$iv);List list$iv$iv = (List)(value$iv$iv$iv == null ? 
          answer$iv$iv$iv : 
          
          value$iv$iv$iv);
        c = (Pair)element$iv$iv;localList1 = list$iv$iv;
        Pair c;
        int $i$a$-groupBy-FlagCheckerKt$validateFlag$5 = 0;localInteger = Integer.valueOf(((Number)c.getSecond()).intValue());
      }
      return checksum(
      
        destination$iv$iv);
    }
    if ((localSizeResult instanceof Incorrect))
    {
      $receiver$iv = "Failed";System.out.print($receiver$iv);
      int i = -1;System.exit(i);throw ((Throwable)new RuntimeException("System.exit returned normally, while it was supposed to halt JVM."));
    }
    throw new NoWhenBranchMatchedException();
  }

The decompiled code is quite messy here because it was originally Kotlin source code. First we see another size check: SizeResultFactory.Companion.check(flag.length(), IllegalMonitorStateException.class). So our flag must be 28 characters long.

If the size check passes, we enter into the main body of the code. First we see some sequence operations in functional code style. They reference some other generated classes but we can decompile that code and obtain the following pseudo-code:

Sequence localSequence1 = mapIndexed(
    filter(
        map(
            asSequence(flag),
            (c)=>(new FlagChar(Character.valueOf(c)))
        ),
        (c)=>(c.getC() instance of Character) // always true?
    ),
    (c,i)=>(new Pair(c,i)) // create tuples of (FlagChar, index)
)

Next we see a for loop that iterates over this sequnce of (FlagChar, index) tuples and generates a mapping of type: Map<Character, List<Integer>>. Each character maps to a list of integers coresponding the indexes where that character was in the original string.

Then it calls checksum on this mapping and returns the value. (If our flag is valid, this should return zero).

Decompiling checksum, we obtain:

public static final int checksum(@NotNull Map<Character, ? extends List<Integer>> grouped)
  {
    Intrinsics.checkParameterIsNotNull(grouped, "grouped");
    try { Map localMap = grouped;
      
      int $i$f$map;
      
      Object localObject1 = $receiver$iv;Collection destination$iv$iv = (Collection)new ArrayList(((Map)$receiver$iv).size());
      int $i$f$mapTo; Object localObject2 = $receiver$iv$iv; Map.Entry item$iv$iv; Collection localCollection1; int $i$a$-map-FlagCheckerKt$checksum$1; Object localObject3; for (Iterator localIterator = ((Map)localObject2).entrySet().iterator(); localIterator.hasNext(); localCollection1.add(localObject3))
      {
        item$iv$iv = (Map.Entry)localIterator.next();
        Map.Entry localEntry1 = item$iv$iv;localCollection1 = destination$iv$iv; Map.Entry entry; $i$a$-map-FlagCheckerKt$checksum$1 = 0;localObject3 = new Pair(Class.forName("team.p4.pudliszki." + String.valueOf(((Character)entry.getKey()).charValue())).newInstance(), Integer.valueOf(compress((List)entry.getValue()))); }
      Object $receiver$iv = (Iterable)destination$iv$iv;
      int $i$f$map; Iterable $receiver$iv; Object $receiver$iv$iv = $receiver$iv;Collection destination$iv$iv = (Collection)new ArrayList(CollectionsKt.collectionSizeOrDefault($receiver$iv, 10));
      int $i$f$mapTo; for (localObject2 = ((Iterable)$receiver$iv$iv).iterator(); ((Iterator)localObject2).hasNext(); localCollection1.add(localObject3))
      {
        Object item$iv$iv = ((Iterator)localObject2).next();
        item$iv$iv = (Pair)item$iv$iv;localCollection1 = destination$iv$iv; Pair entry; int $i$a$-map-FlagCheckerKt$checksum$2 = 0;$i$a$-map-FlagCheckerKt$checksum$1 = entry.getFirst();localObject3 = Integer.valueOf(($i$a$-map-FlagCheckerKt$checksum$1 instanceof }) ? ((Number)entry.getSecond()).intValue() - 27 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof _) ? ((Number)entry.getSecond()).intValue() - 19849 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof u) ? ((Number)entry.getSecond()).intValue() - 25 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof t) ? ((Number)entry.getSecond()).intValue() - 5 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof s) ? ((Number)entry.getSecond()).intValue() - 11 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof n) ? ((Number)entry.getSecond()).intValue() - 8 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof l) ? ((Number)entry.getSecond()).intValue() - 486 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof k) ? ((Number)entry.getSecond()).intValue() - 643 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof i) ? ((Number)entry.getSecond()).intValue() - 16 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof h) ? ((Number)entry.getSecond()).intValue() - 786 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof e) ? ((Number)entry.getSecond()).intValue() - 21 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof c) ? ((Number)entry.getSecond()).intValue() - 23 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof 7) ? ((Number)entry.getSecond()).intValue() - 22 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof 5) ? ((Number)entry.getSecond()).intValue() - 17 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof 1) ? ((Number)entry.getSecond()).intValue() - 327 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof 0) ? ((Number)entry.getSecond()).intValue() - 452 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof {) ? ((Number)entry.getSecond()).intValue() - 2 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof 4) ? ((Number)entry.getSecond()).intValue() - 1 : ($i$a$-map-FlagCheckerKt$checksum$1 instanceof p) ? ((Number)entry.getSecond()).intValue() - 27040 : 64199);
      }
      return CollectionsKt.sumOfInt(
      
        (Iterable)destination$iv$iv);
    }
    catch (ClassNotFoundException e)
    {
      Object $receiver$iv$iv = "Failed";System.out.print($receiver$iv$iv);
      int i = -1;System.exit(i);throw ((Throwable)new RuntimeException("System.exit returned normally, while it was supposed to halt JVM."));
    }
  }

First this method takes the mapping and generates a new list of type: List<Letter, Integer>. The first element is obtained by attempting to find a class in the package team.p4.pudliszki that has a name equal to the character. (A failure occurs if the class could not be found). In the jar, the following character classes exist (all extend the Letter class): _{}01457cehiklnpstu. So here we know the charset that the flag must use.

The second (integer) argument is obtained by calling compress on the original integer list. That method appears to be a lambda function that reduces a list with the formula: (a,b)=>(a*32+b). Since all our indexes are strictly less than 28, we have no loss of information here.

Finally, the method iterates through the new list of (Letter, Integer) tuples and computes the sum after applying the pair to a bunch of nested ternary operators. Essentially here, it adds the integer value and subtracts some constant based on what character it is.

Since the total must be zero, we can solve this by assuming that each intermediate addition will add a net of zero to the total. Therefore for each character, the compressed index value must equal the subtracted constant.

For instance, if the character is t we subtract 5 and therefore we know that t is at index 5 in the buffer. For h we subtract 786 and we know that 786 is composed of the following compressed indexes: 18, 24.

Using all this information, we can deduce the flag:

p4{k0tl1n_1s_p0li5h_ke7chup}

comments powered by Disqus