Home PicoCTF 2022 - Reverse Engineering Challenges
Post
Cancel

PicoCTF 2022 - Reverse Engineering Challenges

Bloat.py

Challenge

1
2
Can you get the flag?
Run this Python program in the same directory as this encrypted flag.

We are given 2 files:

  • bloat.flag.py : a script that asks for a password and gives the flag if it’s ok
  • flag.txt.enc : the encrypted flag

bloat.flag.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import sys

a = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ "


def arg133(arg432):
    if arg432 == a[71] + a[64] + a[79] + a[79] + a[88] + a[66] + a[71] + a[64] + a[77] + a[66] + a[68]:
        return True
    else:
        print(a[51] + a[71] + a[64] + a[83] + a[94] + a[79] + a[64] + a[82] + a[82] + a[86] + a[78] + a[81] + a[67] + a[
            94] + a[72] + a[82] + a[94] + a[72] + a[77] + a[66] + a[78] + a[81] + a[81] + a[68] + a[66] + a[83])
        sys.exit(0)
        return False


def arg111(arg444):
    return arg122(arg444.decode(),
        a[81] + a[64] + a[79] + a[82] + a[66] + a[64] + a[75] + a[75] + a[72] + a[78] + a[77])


def arg232():
    return input(
        a[47] + a[75] + a[68] + a[64] + a[82] + a[68] + a[94] + a[68] + a[77] + a[83] + a[68] + a[81] + a[94] + a[66] +
        a[78] + a[81] + a[81] + a[68] + a[66] + a[83] + a[94] + a[79] + a[64] + a[82] + a[82] + a[86] + a[78] + a[81] +
        a[67] + a[94] + a[69] + a[78] + a[81] + a[94] + a[69] + a[75] + a[64] + a[70] + a[25] + a[94])


def arg132():
    return open('flag.txt.enc', 'rb').read()


def arg112():
    print(a[54] + a[68] + a[75] + a[66] + a[78] + a[76] + a[68] + a[94] + a[65] + a[64] + a[66] + a[74] + a[13] + a[13] + a[13] + a[94] + a[88] + a[78] + a[84] + a[81] + a[94] + a[69] + a[75] + a[64] + a[70] + a[11] + a[94] + a[84] + a[82] + a[68] + a[81] + a[25])


def arg122(arg432, arg423):
    arg433 = arg423
    i = 0
    while len(arg433) < len(arg432):
        arg433 = arg433 + arg423[i]
        i = (i + 1) % len(arg423)
    return "".join([chr(ord(arg422) ^ ord(arg442)) for (arg422, arg442) in zip(arg432, arg433)])


arg444 = arg132()
arg432 = arg232()
arg133(arg432)
arg112()
arg423 = arg111(arg444)
print(arg423)
sys.exit(0)

Solution

The script is obviously obfuscated. Modified version that gives the flag without asking for password :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import sys
a = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"+"[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ "

def arg133(arg432):
  if arg432 == a[71]+a[64]+a[79]+a[79]+a[88]+a[66]+a[71]+a[64]+a[77]+a[66]+a[68]:
    return True
  else:
    print(a[51]+a[71]+a[64]+a[83]+a[94]+a[79]+a[64]+a[82]+a[82]+a[86]+a[78]+\
a[81]+a[67]+a[94]+a[72]+a[82]+a[94]+a[72]+a[77]+a[66]+a[78]+a[81]+\
a[81]+a[68]+a[66]+a[83])
    sys.exit(0)
    return False

def arg111(arg444):
  return arg122(arg444.decode(), a[81]+a[64]+a[79]+a[82]+a[66]+a[64]+a[75]+\
a[75]+a[72]+a[78]+a[77])

def arg232():
  return input(a[47]+a[75]+a[68]+a[64]+a[82]+a[68]+a[94]+a[68]+a[77]+a[83]+\
a[68]+a[81]+a[94]+a[66]+a[78]+a[81]+a[81]+a[68]+a[66]+a[83]+\
a[94]+a[79]+a[64]+a[82]+a[82]+a[86]+a[78]+a[81]+a[67]+a[94]+\
a[69]+a[78]+a[81]+a[94]+a[69]+a[75]+a[64]+a[70]+a[25]+a[94])

def arg132():
  return open('flag.txt.enc', 'rb').read()

def arg112():
  print(a[54]+a[68]+a[75]+a[66]+a[78]+a[76]+a[68]+a[94]+a[65]+a[64]+a[66]+\
a[74]+a[13]+a[13]+a[13]+a[94]+a[88]+a[78]+a[84]+a[81]+a[94]+a[69]+\
a[75]+a[64]+a[70]+a[11]+a[94]+a[84]+a[82]+a[68]+a[81]+a[25])

def arg122(arg432, arg423):
    arg433 = arg423
    i = 0
    while len(arg433) < len(arg432):
        arg433 = arg433 + arg423[i]
        i = (i + 1) % len(arg423)        
    return "".join([chr(ord(arg422) ^ ord(arg442)) for (arg422,arg442) in zip(arg432,arg433)])

arg444 = arg132()
arg423 = arg111(arg444)
print(arg423)
sys.exit(0)

Fresh Java

Challenge

1
2
Can you get the flag?
Reverse engineer this Java program.

Solution

We have to decompile the java class to get the flag. I used cfr for the task

Solve script:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python
import subprocess
import re
cfrjar = '/opt/cfr/cfr_0_115.jar'
dec = subprocess.run(['java','-jar',cfrjar,'./KeygenMe.class'], capture_output=True).stdout.decode()
chars = re.compile("\'([\w{}])\'").findall(dec)
flag = ''
for c in chars[::-1]:
    flag += c
print(flag)

Unpackme

Challenge

1
2
Can you get the flag?
Reverse engineer this binary.

We are given a UPX binary

Solution

  • Unpack the binary
    1
    
    $ upx -d unpackme-upx
    
  • Load the binary into gdb
    1
    
    $ gdb-peda -q ./unpackme-upx
    
  • Disassemble the ‘main’ function
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    gdb-peda$ disass main
    Dump of assembler code for function main:
     0x0000000000401e73 <+0>:     endbr64 
     0x0000000000401e77 <+4>:     push   rbp
     0x0000000000401e78 <+5>:     mov    rbp,rsp
      [...]
     0x0000000000401ec6 <+83>:    mov    WORD PTR [rbp-0x14],0x4e
     0x0000000000401ecc <+89>:    lea    rdi,[rip+0xb1131]
     0x0000000000401ed3 <+96>:    mov    eax,0x0
     0x0000000000401ed8 <+101>:   call   0x410df0 <printf> # <--- First display (What's my favorite number)
     0x0000000000401edd <+106>:   lea    rax,[rbp-0x3c]
     0x0000000000401ee1 <+110>:   mov    rsi,rax
     0x0000000000401ee4 <+113>:   lea    rdi,[rip+0xb1135]
     0x0000000000401eeb <+120>:   mov    eax,0x0
     0x0000000000401ef0 <+125>:   call   0x410f80 <__isoc99_scanf> # <--- User input
     0x0000000000401ef5 <+130>:   mov    eax,DWORD PTR [rbp-0x3c]
     0x0000000000401ef8 <+133>:   cmp    eax,0xb83cb # <--- Number validation 
     0x0000000000401efd <+138>:   jne    0x401f42 <main+207>
     0x0000000000401eff <+140>:   lea    rax,[rbp-0x30]
     0x0000000000401f03 <+144>:   mov    rsi,rax
      [...]
     0x0000000000401f53 <+224>:   mov    rcx,QWORD PTR [rbp-0x8]
     0x0000000000401f57 <+228>:   xor    rcx,QWORD PTR fs:0x28
     0x0000000000401f60 <+237>:   je     0x401f67 <main+244>
     0x0000000000401f62 <+239>:   call   0x45cdf0 <__stack_chk_fail_local>
     0x0000000000401f67 <+244>:   leave  
     0x0000000000401f68 <+245>:   ret    
    End of assembler dump.
    
  • Decode number
    1
    2
    
    gdb-peda$ python print(0xb83cb)
    754635
    
  • Run and win
    1
    2
    3
    4
    
    gdb-peda$ run
    Starting program: unpackme-upx 
    What's my favorite number? 754635
    picoCTF{up><_m3_f7w_ed7b0850}
    

Wizardlike

Challenge

1
2
3
4
5
6
7
8
9
10
Do you seek your destiny in these deplorable dungeons?
If so, you may want to look elsewhere. Many have gone before you and honestly,
they've cleared out the place of all monsters, ne'erdowells, bandits
and every other sort of evil foe. The dungeons themselves have seen better
days too. There's a lot of missing floors and key passages blocked off.
You'd have to be a real wizard to make any progress in this sorry excuse for a dungeon!
Download the game.
'w', 'a', 's', 'd' moves your character and 'Q' quits.
You'll need to improvise some wizardly abilities to find the flag in this dungeon crawl.
'.' is floor, '#' are walls, '<' are stairs up to previous level, and '>' are stairs down to next level.

This is a console game where we can move with ‘wasd’ We can’t go through wall obviously and we can move between levels with the doors ‘>’ ‘<’

As we move through the levels, walls and floors are appearing and some are at places we can’t go because of the walls.

Level Examples

  • Start of level 1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
     ###
    #.@.....#
    #.......#
    #........
    #.......#  .#
     .......#   #
     .......#
     .......#
     .......#
     .......#
     .......#
     .......#
     .......#
     .......
     ......>
     #######
    
  • After Moving all the way south
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    #########
    #.......#  ......#   ..    ..............
    #.......#  ............
    #........  .#
    #.......#  .#
    #.......#   #
    #.......#
    #.......#
    #.......#
    #.......#
    #.......#
    #.......#
    #.......#
    #.......#
    #.@....>#
    #########
    
  • After walking everywhere
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    #########
    #.......#  ......#  .....................
    #.......#  ............
    #........  .#
    #.......#  .#
    #.......#  .#
    #.......#  .#
    #.......#  .#
    #.......#   .
    #.......#
    #.......#
    #.......#
    #.......#
    #......@#
    #......>#
    #########
    

Solution

For this one we’ll be using IDA Free to reverse engineer and hack the game

  • Load the binary in IDA Free. Launch IDA from the command line to be able to interact with the process
    1
    
    cat | /opt/idafree-7.7/ida64
    
  • Decompile the main function by pressing F5 after selecting the function

At the very end of the function, we can see the functions that’s responsible for the keypress action for w,a,s,d,Q

wizard-keypress

In every one of those functions, we can see that the first thing it does is to get a result from another function.

wizard-keypress2

Let’s look into it

Pseudo-code: wizard-keypress3

Assembly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
15AC sub_15AC        proc near
15AC var_8           = dword ptr -8
15AC var_4           = dword ptr -4
15AC                 endbr64
15B0                 push    rbp
15B1                 mov     rbp, rsp
15B4                 mov     [rbp+var_4], edi
15B7                 mov     [rbp+var_8], esi
15BA                 cmp     [rbp+var_4], 63h <---- check if position is in range
15BE                 jg      loc_1664 <---- jump to 1664 if not
15C4                 cmp     [rbp+var_8], 63h <---- check if position is in range
15C8                 jg      loc_1664 <---- jump to 1664 if not
15CE                 cmp     [rbp+var_4], 0 <---- check if position is in range
15D2                 js      loc_1664 <---- jump to 1664 if not
15D8                 cmp     [rbp+var_8], 0 <---- check if position is in range
15DC                 js      loc_1664 <---- jump to 1664 if not
15E2                 mov     eax, [rbp+var_4]
15E5                 movsxd  rcx, eax
15E8                 mov     eax, [rbp+var_8]
15EB                 movsxd  rdx, eax
15EE                 mov     rax, rdx
15F1                 shl     rax, 2
15F5                 add     rax, rdx
15F8                 lea     rdx, ds:0[rax*4]
1600                 add     rax, rdx
1603                 shl     rax, 2
1607                 lea     rdx, [rax+rcx]
160B                 lea     rax, byte_1FEA0
1612                 add     rax, rdx
1615                 movzx   eax, byte ptr [rax]
1618                 cmp     al, 23h ; '#' <---- check if position is a wall
161A                 jz      short loc_1656 <---- jump to 1656 if it is
161C                 mov     eax, [rbp+var_4]
161F                 movsxd  rcx, eax
1622                 mov     eax, [rbp+var_8]
1625                 movsxd  rdx, eax
1628                 mov     rax, rdx
162B                 shl     rax, 2
162F                 add     rax, rdx
1632                 lea     rdx, ds:0[rax*4]
163A                 add     rax, rdx
163D                 shl     rax, 2
1641                 lea     rdx, [rax+rcx]
1645                 lea     rax, byte_1FEA0
164C                 add     rax, rdx
164F                 movzx   eax, byte ptr [rax]
1652                 cmp     al, 20h ; ' '
1654                 jnz     short loc_165D <---- if all if good, jump to 165D
1656
1656 loc_1656:
1656                 mov     eax, 0 <---- set function return to 0
165B                 jmp     short loc_1669
165D ; ---------------------------------------------------------------------------
165D
165D loc_165D:
165D                 mov     eax, 1 <---- set function return to 1
1662                 jmp     short loc_1669
1664 ; ---------------------------------------------------------------------------
1664
1664 loc_1664:
1664                 mov     eax, 0 <---- set function return to 0
1669
1669 loc_1669:
1669                 pop     rbp
166A                 retn <---- return result
166A sub_15AC        endp

What it does is to check if the cursor will still be in range (100x100) if moving to the next position and return 0 if not. Then it checks if the next position is a wall and return 0 if it is indeed a wall (#) If the cursor is allowed to move, it returns 1

With the graph view, we can see the 3 jump locations and their results:

wizard-keypress

What we want is this function to always return 1.

  • Edit the function assembly

Edit > Patch program > Assemble…

We will insert a jump instruction to 165D at the very beginning of the function to skip all the validations

The hacked function’s assembly now look like this:

wizard-hackedfunction1

The pseudocode:

wizard-hackedfunction2

And the graph:

wizard-hackedfunction3

We should now be able to walk through wall to reveal the flag

Save the binary (Edit > Patch program > Apply patches to input file)

  • Run the game and win

Level 1:

wizard-level1

Level 2:

wizard-level2

Level 3:

wizard-level3

Level 4:

wizard-level4

Level 5:

wizard-level5

Level 6:

wizard-level6

Level 7:

wizard-level7

Level 8:

wizard-level8

Level 9:

wizard-level9

Flag: picoCTF{ur_4_w1z4rd_2A05D7A}

Thanks for reading <3

h3x

This post is licensed under CC BY 4.0 by the author.