BROP

BROP对存在栈溢出的ELF进行指令盲注,Papper描述攻击可谓充满了艺术。要满足BROP,需要几个条件:1.进程crash后可以自动重启,类似于httpd、nginx等Daemned服务。2.进程通过fork重启、而非execve(),fork clone父进程状态,从而多次restart间能保持状态,如canary不变等。

指令盲注基于几个特性:

1.Stop gadgets,如sleep、loop代码,通过连接timeout来判定(non loss connection)。

2.trap gadgets,即crash gadgets,如 \0 pointer dereferences。

3.Probe  gadgets,当时正在探测。

盲注判断条件:

1.正常返回,nocrash。

2.超时返回,inf,即为stop gadgets。

3.丢失连接,crashed,即为trap gadgets。

攻击目标最终要拿到shell,即要指行execv('/bin/sh',0),需要能够ret2plt执行(如果有execv),或者syscall execv。X86-64位机器下需要rax传syscall number,rdi、rsi、rdx传参。Paper通过 __libc_csu_init函数指令偏移解决了rdi和rsi问题:


pop RDX;ret在文件中罕见,使用strcmp系列函数来指定rdx:rdx保存cmp的内存字节数。

最后只缺RAX,这个使用ret2plt来绕过。 plt区域判断比较有艺术性,特征如下:


1.每个plt长16bytes。

2.+6和+b两路径执行不会挂,+b为上面下箭头处,自己在栈上放plt num。


Paper exp使用ruby编写,有如下几个过程:

1.find_overflow_len,获取溢出长度,代码比较简单,暴力猜测。

2.find_rip 获取原RIP,同上,猜测各字节内容,单字节范围在(0-255)间,捕获inf和nocrash两种情况,inf即为stop gadget,nocrash为正确内容,另外rip有个判断条件

[0x400000,0x400000 + 0x600000]

内存:AAAAAAAAA + canary + rip。在找到rip之前,已经把canary找到了。有一段canary检查代码,又是一些特征的运用,文章作者几乎是二进制层面的hacker,canary8字节,最后一字节不为0。

3.find_inf 查找stop gadgets,即未断连接,但是超时返回gadget,从0x400000 + 0x1000,即0x401000开始,每隔0x10字节发送['AAAA'*olen,canary,addr,addr,DEATH]进行盲注,选择inf返回的addr,另外通过paranoid_inf()二次验证,二次验证规则如下:

验证5次,从inf addr + 0x10开始,每次地址+ 0x10,发送

['AAAA'*olen,canary,addr,addr +6,inf,inf] 注,本次发送未判断,及下面

['AAAA'*olen,canary,addr,addr +6,DEATH],用判断plt逻辑来查找plt。在前面也提到了,对于plt,plt + 0,plt + 6,plt+b都会正常执行,不会死。

4.find_depth,查找嵌套函数调用深度。

i in 1..30层探测后面有几个ret指令,发送['AAAA'*olen,canary, @plt*i],通过正常返回来判断(no_crash),如5层调用堆栈如下

[canary,rip,rip,rip,rip,rip,others],探测时栈变成[canary,plt,plt,plt,plt,plt,others] 这个地方有个bug,代码执行plt及其后指令,有可能其后就有crash指令

5.find_gadget 查找BROP,即__libc_csu_init6个连续pop,

addr 初始值设为@plt + 0x200(plt section或者text section),

while true 循环,addr + 7开始,每次探测地址+7(原因在后面有提)

一、通过check_instr() 来验证是否是六个brop。

a:depth为0时,发送

['AAAA'*olen,canary,addr,6个@plt,inf,inf,death],通过返回值为inf来检测(probe addr为6个pop,会把6个@plt弹出,Paper用6个death,对应exp用6个plt,6个pop并ret之后来到inf)。

b,当depth> 0时,发送

['AAAA'*olen,canary,addr,6个@plt,@plt直到depth],通过返回值为no_crash判断。这个地方和find_depth有同样的问题。

二、通过verify_gadget()做二次验证,同时设置RDI:

a.通过get_dist()函数探测left:pop rsp和right:pop rdi,这也是Paper最难理解的部分,上面提到了+7,下面解释为啥是+7,

6个连续pop+ret共11 bytes,如下:

加7是想让addr落到 pop rbp(第6个字节)处,

以depth=0为例,

a1:left探测(图pop rbp往上), 发送

['AAAA'*olen,canary,(41 rex.B),6个@plt,inf,inf,death] 返回inf,正常

['AAAA'*olen,canary,(5c pop rsp),6个@plt,inf,inf,death],pop rsp会令进程crash,与期望的inf相违背,异常,left为1。

a2:right探测(图pop rbp往下),发送

['AAAA'*olen,canary,(41 rex.B),6个@plt,inf,inf,death]正常

['AAAA'*olen,canary,(5e pop rsi),6个@plt,inf,inf,death]正常

['AAAA'*olen,canary,(41 rex.B),6个@plt,inf,inf,death]正常

['AAAA'*olen,canary,(5f pop rdi),6个@plt,inf,inf,death]异常

pop rdi会令进程crash,这里有疑问,rdi不像rsp那样破坏堆栈,怎么会crash?

right为3.rex.b指令为amd64新增,扩操作数长度,单独执行不会crash,另有rex.w等。

通过left(1) + right(3) == 3做进一步校验,

获取ret: = gadget + right + 2,发送

['AAAA'*olen,canary,6个@ret,inf,inf,death],通过返回值inf做进一步校验。

获取rdi = ret -1 ,通过check_rdi_bad_inf()检验,发送

['AAAA'*olen,canary,death]让程序挂(why?),通过test_vsyscall()验证,发送

['AAAA'*olen,canary,(time = VSYSCALL + 0x400),inf,inf,death]是否返回inf进一步校验

发送'AAAA'*olen,canary,death]让程序挂(why?),

通过find_writable()查找到写内存区域:

add从rip开始,循环检测,每次+0x10000,发送

['AAAA'*olen,canary,pop rdi,addr,(time = VSYSCALL + 0x400),inf,inf,death],判断是否为inf来验证是否可写,

vsyscall + 400 time函数定义:time_t time(time_t *tloc)

通过vsyscall把返回的时间存到addr(pop rdi,addr实现参数)

通过check_rdi()做进一步校验,发送

['AAAA'*olen,canary,brop,6个death,inf,inf,death],判断是否为inf校验。

通过paranoia_checks()做进一步检验,发送

['AAAA'*olen,canary,pop rdi,0,pop rsi,0,0,inf,inf,death] 判断是否为inf校验。

6.find_strcmp ,获取strcmp函数,设置rdx.

声明:int strcmp(const char *s1, const char *s2);

利用good/bad参数组合来校验,good参数:rip,俩bad参数 300和500,原理:

strcmp(bad,bad)/strcmp(bad,good)/strcmp(good,bad)都报错,

strcmp(good,good)成功返回。

另外还利用了vsyscall page最后一个字节:

strcmp(VSYSCALL + 0x1000 - 1,good),超vsyscall page时正常返回,不会报错。

并把good地址存为strcmp_addr地址。

7.find_write(),查找write()函数

plt num 0至300循环探测,

7.1 max_fd上设置初始调用参数,write(fd,addr,len)相关参数

len参数通过strcmp设置:[@strcmp,@strcmp_addr,@strcmp_addr]

设置函数和第一二个参数:[pop rdi,max_fd,pop rsi,@strcmp_addr,0,@plt + 0xb,@write_plt_num]

注:plt+0xb进入plt第三条指令,直接jmp到plt start,后栈上存在@write_plt_num

7.2 chain多个socket write, for fd in [0,max_fd-1]:

设置rdi:[fd]。

设置调用函数[@plt + 0xb,@write_plt_num]

最后的发送rop为[@strcmp,@strcmp_addr,@strcmp_addr,pop rdi,max_fd,pop rsi,@strcmp_addr,0,@plt + 0xb,@write_plt_num, max_fd-1个 (pop rdi ,fd,@plt + 0xb,@write_plt_num),DEATH]

表示max_fd个write函数串起来向客户端写东西,总有一个会成功。

后面这个connection用一个select(conns,nil,nil)来做返回测试。

只要客户端接收到服务端的数据,即探测到write函数。这块牛B在探测多个fds。

8.find_fd,获取打开的句柄

循环10次,构造{fd:count(fd)} map,可用fd通过下面的do_find_fd实现:

do_find_fd  从20往下至0循环探测,发送

[@strcmp,@strcmp_addr,@strcmp_addr,pop rdi,fd,pop rsi,@strcmp_addr,0,@plt + 0xb,@write_plt_num,death],即write(fd,strcmp_addr,len),通过检测是否返回值来验证可用的fd。

最后保存被验证次数最多的fd,及最小、最大的fd。

另外设置@max_fd = 最大fd + 3

9.find_good_rdx,在比较内容小于16或者没找到strcmp \0地址的情况下,查找相对好的strcmp_addr,即cmp的地址内容足够长。

探测addr从strcmp_addr开始,循环探测,当探测到 \0时,addr + 8(64位数)继续,未找到比16大的场景时,addr +(探测到的长度+1) +1即跳过\0

[@strcmp,@strcmp_addr,@strcmp_addr,pop rdi,max_fd,pop rsi,@strcmp_addr,0,@plt + 0xb,@write_plt_num, max_fd-1个 (pop rdi ,fd,@plt + 0xb,@write_plt_num),DEATH]

根据read响应来判断,当read响应为0或者响应长度>16时,分别更新strcmp_zero地址和strcmp_addr。更长的响应代表找到更好的rdx。

10,dump_bin()方法,从服务端内存dump bin

addr 从0x400000循环,每次addr + dump_len

10.1 dump bin

fd in [0,max_fd] chain write,即write(fd0,addr,strcmp_len)|write(fd1,addr,strcmp_len)|...|write(max_fd,addr,strcmp_len)

注:本地没有在rop尾加death。

dump内容写本地文件。

10.2 analyze_bin,分析bin

10.2.1分析更长的字符串,更新strcmp_addr,以找到更大的rdx。

10.2.2分析dynamic string section,二进制正则

/[[:alnum:]]{4,}\0[[:alnum:]]{4,}/ 即00([数据或者字符]{4,}0[数据或者字符]{4,})

string以\0结束,查找多个string.

10.2.3 find_sym分析查找dynamic symbol section,正则@bin.rindex(/\0{24}/, @dynstr)

@dynstr为10.2.2分析结果,ELF文件 dyn sym section在dyn str section正上面。

上术正则表示dyn str 上面第一个24个0区域


Elf64_Sym第一项全0,每项长度24bytes。

10.2.4 dump_sym,解析dynamic symbol table

while 循环,循环起始地址是dynamic sym,结束条件是地址<dynamic str

数据解析成Elf64_sym结构数组。

10.2.5 dump_rel,解析relocation 结构

rela section 在dynstr之下

idx = @bin.index(/(.{8}\07\0\0\0.{4}\0{8}){3}/, @dynstr)

.{8}代表Elf64_Rela结构的r_offset成员,r_info由dynsym 索引和type组成

#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type))

故\07\0\0\0表示 type为7即R_X86_64_JUMP_SLOT类型,.{4}表示sym ,即dynsym索引

\0{8}表示r_addend,R_X86_64_JUMP_SLOT relocation修正值为symbol value,无须r_addend。


num 即为dynsym num,这里需要提的是relocation table,做为桥梁把symbol定义和symbol引用结合在一块。symbol引用即为r_offset,也就是got地址(Dynamic通过got写引用)。symbol定义在dynsym中,通过 rela section的sh_link指向对应dynsym table,见https://www.intezer.com/executable-and-linkable-format-101-part-3-relocations/

最后构造对应的pltf map,这里relocation函数对应plt num。

10.2.6 find_gadgets,查找dump bin中的gadgets

即syscall、pop rax,pop rdx,pop rsi

10.3 build_exp_rop()构造 exp

用到如下函数


a

10.3.1 构造sleep函数调用 sleep(delay) or usleep(1000 * 1000 * delay)

10.3.2   构造read(expfd,writable) & write(expfd,writable)调用

注:writable为@got_end + 100,进入data section

10.3.3 dup fd to 0,1,2

dup2存在时构造set_plt(rop, dup2, expfd, fd)或者 close and fcntl存在时

即把网络fd map到std_in、std_out、std_err上,从而完成read和write操作。

10.3.4,执行execve('/bin/sh',0,0),通过execve plt或者syscall execve

最终的rop+death。

11.正式exp。

11.1通过10.3生成exp rop

11.2生成50个链接

11.3发送11.1生成的exp,发送链接放入11.2中,共51个链接

11.4往51个链接发送'/bin/sh'字串,以便写入服务端data section中

11.5 find_sock()找到有影响的链接,这时候服务端已经read数据到data section,同时会write 同一区域数据到客户端,需判断是否包括'/bin/sh'字串。

11.6到此可以输入shell command。

.............................................................DONE...................................................……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容

  • brop:一种不给你二进制文件的骚操作攻击前提条件: 源程序必须存在栈溢出,让攻击者可以控制程序流 服务器端的进程...
    zs0zrc阅读 4,310评论 1 3
  • 0. 引言 如果你学的第一门程序语言是C语言,那么下面这段程序很可能是你写出来的第一个有完整的 “输入---处理-...
    pandolia阅读 14,038评论 13 27
  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,319评论 0 10
  • 文/罗甜梓 秋天的落叶随风而动终究还是回到故土,而你是否在游玩过后回归故乡。 放不下那片故土,我给家乡修“天路” ...
    罗甜梓阅读 769评论 5 7
  • 和孩子们平起平坐,心灵会拉得更近,这个是在学习的过程中我很深的体会,自己会装傻,孩子会更聪明,更能干 用爱去做,你...
    MOANN阅读 242评论 0 1