Dylan's Blog

HTB Cyber Apocalypse 2023 - Reverse Engineering Challenges

Category: CTF

She Shells C Shells

Description

She Shells C Shells Description

For this challenge, we’re given a single executable, shell.

$file shell 
shell: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c8ab24eb713f3b6c9036da743112176b91e58f1b, for GNU/Linux 3.2.0, not stripped

Solution

Opening the file in Ghidra, I find a function called: func_flag

  fgets((char *)&local_118,0x100,stdin);
  for (local_c = 0; local_c < 0x4d; local_c = local_c + 1) {
    *(byte *)((long)&local_118 + (long)(int)local_c) =
         *(byte *)((long)&local_118 + (long)(int)local_c) ^ m1[(int)local_c];
  }
  local_14 = memcmp(&local_118,t,0x4d);
  if (local_14 == 0) {
    for (local_10 = 0; local_10 < 0x4d; local_10 = local_10 + 1) {
      *(byte *)((long)&local_118 + (long)(int)local_10) =
           *(byte *)((long)&local_118 + (long)(int)local_10) ^ m2[(int)local_10];
    }
    printf("Flag: %s\n",&local_118);
    uVar1 = 0;
  }

This code reads in input from stdin and stores it in local_118, which is then XOR’d with m1 and stored back in local_118.

local_118 is then compared with t and if it matches, it will XOR with m2.

Finally, the program prints out local_118 as the flag. So to solve for the flag, we just need to XOR t and m2, which we can just copy out of Ghidra as a bytestring and use something like Cyberchef to XOR the values together.

t = 2c4ab799a3e57078936e97d9476d38bdffbb85996fe14aab74c37ba8b29fd7ecebcd63b23923e184929609c699f258facb6f6f5e1fbe2b138ea5a99993ab8f701cc0c43ea6fe933590c3c910e9 m2 = 641ef5e2c097441bf85ff9be185d488e91e4f6f15c8d269e2ba102f7c6f7e4b398fe57ed4a4bd1f6a1eb09c699f258facb6f6f5e1fbe2b138ea5a99993ab8f701cc0c43ea6fe933590c3c910e9

Flag: HTB{cr4ck1ng_0p3n_sh3ll5_by_th3_s34_sh0r3}

Needle in a Haystack

Description

Needle in a Haystack Description

Solution

Straightforward grep to win challenge, run strings on the binary and grep for the flag prefix.

$strings haystack | grep HTB
HTB{d1v1ng_1nt0_th3_d4tab4nk5}

Flag: HTB{d1v1ng_1nt0_th3_d4tab4nk5}

Cave System

Description

Cave System Description

Solution

Opening the file in Ghidra, we see a very long block of conditional checks against our input:

  printf("What route will you take out of the cave? ");
  fgets((char *)&local_88,0x80,stdin);
  iVar1 = memcmp(&local_88,&DAT_00102033,4);
  if (((((((iVar1 == 0) && ((byte)(local_78._5_1_ * (char)local_58) == '\x14')) &&
         ((byte)((byte)local_68 - local_68._4_1_) == -6)) &&
        (((((((byte)(local_68._5_1_ - local_70._2_1_) == -0x2a &&
             ((byte)((byte)local_78 - (char)local_58) == '\b')) &&
            (((char)(local_58._7_1_ - (char)local_80) == -0x2b &&
             (((byte)(local_70._2_1_ * local_88._7_1_) == -0x13 &&
              ((char)(local_88._4_1_ * (char)local_70) == -0x38)))))) &&
           ((local_68._2_1_ ^ local_70._4_1_) == 0x55)) &&
          (((((byte)(local_70._6_1_ - local_58._7_1_) == '4' &&
             ((byte)(local_50._3_1_ + local_58._2_1_) == -0x71)) &&
            ((byte)(local_60._4_1_ + local_70._3_1_) == -0x2a)) &&
           (((local_78._1_1_ ^ local_80._6_1_) == 0x31 &&
            ((byte)((byte)local_50 * local_78._4_1_) == -0x54)))))) &&
         (((((byte)(local_50._2_1_ - local_70._2_1_) == -0x3e &&
            (((local_70._2_1_ ^ local_88._6_1_) == 0x2f &&
             ((local_80._6_1_ ^ local_68._7_1_) == 0x5a)))) &&
           ((local_60._4_1_ ^ local_68._7_1_) == 0x40)) &&
          ((((((byte)local_60 == local_70._2_1_ &&
              ((byte)(local_78._7_1_ + local_58._1_1_) == -0x68)) &&
             ((byte)(local_78._7_1_ * local_50._3_1_) == 'h')) &&
            (((byte)(local_88._1_1_ - local_70._4_1_) == -0x25 &&
             ((byte)((char)local_70 - local_70._5_1_) == -0x2e)))) &&
           (((char)(local_68._6_1_ - (char)local_70) == '.' &&
            ((((byte)local_68 ^ local_78._6_1_) == 0x1a &&
             ((byte)(local_60._4_1_ * local_88._4_1_) == -0x60)))))))))))) &&
       ((((((byte)(local_68._6_1_ * local_70._3_1_) == '^' &&
           ((((byte)(local_80._7_1_ - (byte)local_60) == -0x38 &&
             ((local_58._1_1_ ^ local_58._5_1_) == 0x56)) &&
            ((local_70._2_1_ ^ local_60._5_1_) == 0x2b)))) &&
          ((((((local_58._6_1_ ^ local_80._1_1_) == 0x19 &&
              ((byte)(local_70._4_1_ - local_60._7_1_) == '\x1a')) &&
             (((byte)(local_58._2_1_ + local_78._3_1_) == -0x5f &&
              (((byte)(local_68._5_1_ + local_50._1_1_) == 'V' &&
               ((local_70._5_1_ ^ local_78._2_1_) == 0x38)))))) &&
            ((local_60._4_1_ ^ local_50._4_1_) == 9)) &&
           ((((((char)(local_80._7_1_ * local_68._6_1_) == 'y' &&
               ((local_68._5_1_ ^ local_70._6_1_) == 0x5d)) &&
              ((byte)(local_88._2_1_ * (byte)local_68) == '\\')) &&
             (((byte)(local_80._2_1_ * local_78._2_1_) == '9' && (local_70._5_1_ == local_78._5_1_))
             )) && (((byte)(local_68._3_1_ * local_78._5_1_) == '/' &&
                    (((byte)((char)local_80 * local_68._5_1_) == -0x55 &&
                     ((byte)(local_68._7_1_ + local_70._2_1_) == -0x6d)))))))))) &&
         (((((((local_70._2_1_ ^ local_68._2_1_) == 0x73 &&
              ((((local_78._4_1_ ^ local_70._7_1_) == 0x40 &&
                ((byte)(local_70._1_1_ + (byte)local_78) == -0x57)) &&
               ((local_68._7_1_ ^ local_50._3_1_) == 0x15)))) &&
             ((((byte)((byte)local_88 + local_50._3_1_) == 'i' &&
               ((byte)(local_68._2_1_ + local_60._6_1_) == -0x5b)) &&
              (((local_70._6_1_ ^ local_58._4_1_) == 0x37 &&
               (((byte)((byte)local_88 * local_70._4_1_) == '\b' &&
                ((byte)(local_68._2_1_ - (byte)local_50) == -0x3b)))))))) &&
            ((byte)(local_78._2_1_ + local_50._4_1_) == -0x1c)) &&
           (((((local_68._3_1_ ^ (byte)local_60) == 0x6e &&
              ((byte)((byte)local_50 * (byte)local_78) == -0x54)) &&
             ((byte)(local_58._6_1_ - local_60._7_1_) == '\r')) &&
            ((((byte)(local_70._6_1_ + local_58._7_1_) == -100 &&
              ((byte)(local_88._6_1_ + local_68._1_1_) == -0x2c)) &&
             (((byte)(local_88._7_1_ * local_70._5_1_) == -0x13 &&
              ((((byte)local_50 ^ local_70._5_1_) == 0x38 &&
               ((byte)(local_88._1_1_ * local_68._5_1_) == 'd')))))))))) &&
          ((((byte)local_50 ^ local_50._2_1_) == 0x46 &&
           (((((((char)(local_88._2_1_ * local_78._3_1_) == '&' &&
                ((local_70._2_1_ ^ local_78._6_1_) == 0x2b)) &&
               ((byte)(local_88._1_1_ + local_88._7_1_) == -0x79)) &&
              (((local_70._3_1_ ^ (byte)local_88) == 0x2a &&
               ((byte)(local_78._5_1_ - local_88._1_1_) == '\v')))) &&
             ((byte)(local_70._3_1_ + local_58._6_1_) == -0x32)) &&
            (((local_78._1_1_ ^ local_80._5_1_) == 0x3b &&
             ((byte)(local_78._3_1_ - local_50._2_1_) == '\x12')))))))))) &&
        ((((local_78._1_1_ == local_80._2_1_ &&
           ((((byte)(local_80._6_1_ - local_50._2_1_) == 'M' &&
             ((byte)(local_60._2_1_ * local_58._4_1_) == 'N')) && (local_58._2_1_ == (byte)local_68)
            ))) && (((local_60._7_1_ ^ local_58._3_1_) == 0x38 &&
                    ((char)(local_68._6_1_ + local_70._1_1_) == -0x6c)))) &&
         ((byte)(local_60._1_1_ + local_58._4_1_) == -0x31)))))) &&
      ((((local_60._4_1_ == local_78._4_1_ && ((char)(local_80._4_1_ + local_70._1_1_) == 'f')) &&
        (((byte)(local_50._4_1_ + local_68._4_1_) == -0xf &&
         ((((byte)(local_60._1_1_ - local_78._5_1_) == '\x11' &&
           ((byte)(local_68._4_1_ - local_58._1_1_) == 'D')) &&
          ((byte)(local_80._1_1_ - local_68._3_1_) == 'D')))))) &&
       ((((local_58._5_1_ ^ local_58._3_1_) == 1 && ((local_68._2_1_ ^ local_50._1_1_) == 0xd)) &&
        ((((byte)(local_80._3_1_ - local_70._4_1_) == -0x15 &&
          (((((char)(local_78._7_1_ + (char)local_70) == -0x67 &&
             ((byte)((char)local_70 + local_80._5_1_) == -0x6b)) &&
            (((byte)(local_80._4_1_ - (byte)local_88) == -0x17 &&
             (((((byte)(local_68._2_1_ + local_70._7_1_) == '`' &&
                ((byte)(local_88._5_1_ + local_58._5_1_) == -0x6a)) &&
               ((byte)(local_58._1_1_ * local_60._2_1_) == '`')) &&
              (((byte)((char)local_58 * local_78._5_1_) == '\x14' &&
               ((byte)(local_70._3_1_ - local_58._4_1_) == '\x03')))))))) &&
           ((byte)(local_50._1_1_ + local_78._4_1_) == -0x6b)))) &&
         ((((byte)(local_80._2_1_ * local_58._5_1_) == -0x26 &&
           ((byte)(local_88._1_1_ + local_60._1_1_) == -0x3c)) &&
          (((byte)(local_60._7_1_ - local_88._1_1_) == '\v' &&
           (((local_60._3_1_ == local_78._3_1_ && ((byte)(local_68._7_1_ + local_60._7_1_) == -0x6d)
             ) && ((byte)(local_80._4_1_ * local_50._2_1_) == 'Q')))))))))))))) &&
     (((((byte)((char)local_80 * local_70._2_1_) == 'A' &&
        ((byte)(local_60._6_1_ - local_70._7_1_) == 'E')) &&
       ((byte)(local_88._7_1_ + local_68._5_1_) == 'h')) &&
      (((((char)(local_68._4_1_ + local_88._4_1_) == -0x44 &&
         ((byte)(local_70._7_1_ + (byte)local_68) == -0x5e)) &&
        (((char)(local_70._1_1_ + local_88._5_1_) == 'e' &&
         ((((byte)(local_60._3_1_ * local_70._5_1_) == -0x13 &&
           ((local_80._5_1_ ^ local_60._5_1_) == 0x10)) &&
          ((char)((char)local_58 - local_80._4_1_) == ';')))))) &&
       (((((char)(local_78._7_1_ - (char)local_80) == '\t' &&
          ((local_88._7_1_ ^ local_60._2_1_) == 0x41)) &&
         ((char)(local_88._5_1_ - local_60._3_1_) == -3)) &&
        (((((local_50._4_1_ ^ local_78._2_1_) == 0x1a && ((local_88._1_1_ ^ local_88._3_1_) == 0x2f)
           ) && (((byte)(local_78._1_1_ - local_68._7_1_) == '+' &&
                 (((((byte)((char)local_80 + local_78._4_1_) == -0x2d &&
                    ((byte)(local_80._3_1_ * local_58._5_1_) == -0x28)) &&
                   ((byte)(local_70._3_1_ + local_88._6_1_) == -0x2e)) &&
                  (((byte)(local_88._5_1_ + local_88._3_1_) == -0x55 &&
                   ((byte)(local_68._3_1_ - local_60._7_1_) == -0x2e)))))))) &&
         (((byte)local_78 ^ local_68._1_1_) == 0x10)))))))))) {
    puts("Freedom at last!");
  }
  else {
    puts("Lost in the darkness, you\'ll wander for eternity...");
  }
  return 0;
}

It’s definitely possible to solve this by hand, but I would rather not. So instead, I use Angr.

Angr is a binary analysis platform written in Python and can perform symbolic execution, which we can use to solve the conditional and give us our flag.

Majority of the code below is just setting up angr to work with the binary. All you really need to provide is the address you want angr to reach, (in this case, we want to reach the “Freedom at last!” string).

From there we can just run the script and angr will find the input that satisfies the criteria.

import angr
import sys


p = angr.Project("cave")

good = (0x401aba)
initial_state = p.factory.entry_state()
simulation = p.factory.simgr(initial_state)
simulation.explore(find=good)

if simulation.found:
    solution_state = simulation.found[0]
    solution = solution_state.posix.dumps(sys.stdin.fileno())
    print(solution.decode("utf-8"))

else:
    raise Exception("Could not find solution")
$python3 solve.py 
WARNING  | 2023-03-30 15:18:58,870 | cle.loader     | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
HTB{H0p3_u_d1dn't_g3t_th15_by_h4nd,1t5_4_pr3tty_l0ng_fl4g!!!}

Flag: HTB{H0p3_u_d1dn't_g3t_th15_by_h4nd,1t5_4_pr3tty_l0ng_fl4g!!!}