0xd13a

A rookie in a world of pwns

ASIS CTF Finals 2019 Writeup: Snake

Snake

253

Hercules travelled to the city of snakes to find a venomous, snake which lived underwater. For this task, Hercules was given a handbook and a mysterious box in order to find out about the snake. Can you help him to find the way to defeat the snake?

You need this information to solve the task:

File: /bin/bash

Size: 1113504 Blocks: 2176 IO Block: 4096 regular file

Device: 806h/2054d Inode: 1050418 Links: 1

Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)

Access: 2019-11-16 00:09:47.788589318 +0330

Modify: 2019-06-07 02:58:15.000000000 +0430

Change: 2019-07-09 09:30:05.424100060 +0430

Birth: -

Download: venomous_snake.txz

This is another reversing challenge, but decoding of the flag requires a lot of dynamic operations, so we will use Ghidra together with GDB or EDB.

Close inspection reveals a number of interesting functions:

  • FUN_001012f5() is the main function where all processing happens
  • FUN_00101102() does some intial checks on the correctness of the environment
  • FUN_00100d6a() initializes internal structures for decryption functions
  • FUN_00100dcb() decrypts piece of data correctly only when conditions are right
  • FUN_00100ec2() decrypts piece of data independent of whether the conditions are set correctly (less important data is decoded using it)
  • FUN_00100fb6() decodes the shell script that will produce the flag

Let’s annotate the main function with notes of how to carefully step over dangerous parts:

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
...

undefined * FUN_001012f5(uint param_1,char **param_2)
{

...

// This function checks the integrity of its own code, so the presence
// of the breakpoint could mess things up - we can use hardware 
// breakpoints, or step over this function altogether.
  local_3c = FUN_00101102((ulong)param_1); 
  
  FUN_00100d6a();
  FUN_00100dcb(&DAT_0030238c,0x100);
  
// Decode a message about executable being expired  
  FUN_00100ec2(&DAT_003024d6,0x3c); 

// Decode a constant timestamp
  FUN_00100ec2(&DAT_00302572,0xb);
  
// Verify that the current time is older than the timestamp - jump over this code
  if (DAT_00302572 != '\0') {
    lVar2 = atoll(&DAT_00302572);
    tVar3 = time((time_t *)0x0);
    if (lVar2 < tVar3) {
      return &DAT_003024d6;
    }
  }

// Decode text '/bin/bash'
  FUN_00100ec2(&DAT_0030252b,10);
  
// Decode text '-c'
  FUN_00100ec2(&DAT_003024ad,3);
  
// Decode 'exec' command  
  FUN_00100ec2(&DAT_00302518,0xf);
  
  FUN_00100ec2(&DAT_00302514,1);
  
  FUN_00100ec2(&DAT_0030253b,0x16);
  
// Decode a sanity check constant 
  FUN_00100dcb(&DAT_0030253b,0x16);
  FUN_00100ec2(&DAT_00302557,0x16);
  iVar1 = memcmp(&DAT_0030253b,&DAT_00302557,0x16);
  if (iVar1 == 0) {
  
// Decode another sanity check
    FUN_00100ec2(&DAT_003024b4,0x13);
    if (local_3c < 0) {
      puVar4 = &DAT_003024b4;
    }
    else {
	
// At this point program starts to build external command to execute
      __argv = (char **)calloc((long)(int)(param_1 + 10),8);

...

          FUN_00100ec2(&DAT_00302513,1);
		  
// Decode the in-memory script that will help produce the flag (see below)		  
          if ((DAT_00302513 == '\0') && (iVar1 = FUN_00100fb6(&DAT_0030252b), iVar1 != 0)) {
            return &DAT_0030252b;
          }
		  
// Decode remaining pieces of the command to execute - at this point
// we can simply extract the script we need from memory and execute it
          FUN_00100ec2(&DAT_0030257f,1);
          FUN_00100ec2(&DAT_003020c3,0x283);
          FUN_00100ec2(&DAT_00302581,0x13);
          FUN_00100dcb(&DAT_00302581,0x13);
          FUN_00100ec2(&DAT_00302022,0x13);
          iVar1 = memcmp(&DAT_00302581,&DAT_00302022,0x13);
          if (iVar1 != 0) {
            return &DAT_00302581;
          }
          local_30 = (char *)malloc(0x1283);
          if (local_30 == (char *)0x0) {
            return (undefined *)0;
          }
          memset(local_30,0x20,0x1000);
          memcpy(local_30 + 0x1000,&DAT_003020c3,0x283);
        }
...

Function FUN_00100fb6() is of particular interest, it gets a stat of /bin/bash and decodes the hidden script based on its data. Here is the stat data on my machine:

1
2
3
4
5
6
7
8
9
00007fff:ffffe140|01 08 00 00 00 00 00 00 71 02 1a 00 00 00 00 00|........q.......| Device ID, Inode
00007fff:ffffe150|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe160|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe170|c8 04 11 00 00 00 00 00 00 00 00 00 00 00 00 00|................| File size
00007fff:ffffe180|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe190|00 00 00 00 00 00 00 00 3a b3 26 5b 00 00 00 00|........:.&[....| Modification time
00007fff:ffffe1a0|00 00 00 00 00 00 00 00 76 1d 1b 5c 00 00 00 00|........v..\....| Creation time
00007fff:ffffe1b0|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe1c0|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|

Correct values were specified in the task description. We can replace them in memory as we debug the program:

1
2
3
4
5
6
7
8
9
00007fff:ffffe140|06 08 00 00 00 00 00 00 32 07 10 00 00 00 00 00|........q.......|
00007fff:ffffe150|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe160|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe170|a0 fd 10 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe180|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe190|00 00 00 00 00 00 00 00 7f 93 f9 5c 00 00 00 00|........:.&[....|   
00007fff:ffffe1a0|00 00 00 00 00 00 00 00 55 1f 24 5d 00 00 00 00|........v..\....|
00007fff:ffffe1b0|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|
00007fff:ffffe1c0|00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00|................|

With the correct data set, the script is correctly decoded in memory:

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
#!/bin/bash
# In the name of Allah
secret='L5rI#`8D+e4FqFQy.t?E'
top_secret='s7.#"{EE*(T+H!R\c#e=WMd^C'
version=`openssl version | cut -d" " -f 2`
if [[ $version == '1.1.1d' ]]; 
then
  if [[ $# -eq 1337 ]]; 
  then
    if [[ $2 == 'unl0ck_M3__PlE4ze__n0W' ]];
	then
	  openssl enc -aes-256-cbc -nosalt -d -in $1 -k $secret
	  echo ''
	  echo 'Your file unlocked successfully'
	fi
    if [[ $114 == 'l3T_m3_kN0w_fL49_Pl3Az3' ]];
    then
      openssl enc -aes-256-cbc -nosalt -d -in 'asis_flag.enc' -iter $((114 * ${#top_secret})) -k $top_secret
    fi
  else
    echo 'Try harder!!'
  fi
else
  echo 'Your OS is not satisfied to run this program, sorry!'
fi

We don’t need to run the whole script as it requires that we set up a crazy number of parameters, let’s simply run the important part of it:

1
$ openssl enc -aes-256-cbc -nosalt -d -in asis_flag.enc -iter 2850 -k 's7.#"{EE*(T+H!R\c#e=WMd^C' > flag.png

An image is produced and it contains the flag: ASIS{Rans0mw4R3_1nf3c7_tHE_W0rlD}.