首页
论坛
课程
招聘
[原创]信息安全铁人三项陕西赛区个人赛第一题WriteUp
2018-6-17 21:35 2505

[原创]信息安全铁人三项陕西赛区个人赛第一题WriteUp

2018-6-17 21:35
2505

1. 写在前面

首先这是一道ret2dlresolve的题目,但是困难比较多;其次做这道题的前置知识我会做一个简单的介绍,更详细的内容我会在最后列一些文章以供大家参考;最后,我希望和大家分享的是我的解题思路,所以我会尽量详细的描述我碰到问题的时候如何分析定位原因并且如何去解决这个问题。

2. 前置知识

2.1 .dynamic section

.dynamic节包含了很多有用的信息,以本题为例。

只需要关注其中的几个item,SYMTAB的值为.dynsym节的开始,PLTGOT的值为.got.plt节的开始,PLTREL指出重定位项是rela类型,JMPREL的值为.rela.plt节的开始,VERSYM对应着.gun.version节的开始。

2.2 .got,.plt,.rela.plt,.dynsym以及.dynstr

.got和.plt的关系我就不再过多赘述,我们会用到的主要就是common plt(PLT[0]),他是跳转到_dl_runtime_resolve的核心;然后需要知道的就是GOT[0]代表.dynamic节的开始,GOT[1]的值为linkmap的首地址和GOT[2]是_dl_runtime_resolve的入口。
然后说一下_dl_runtime_resolve的大致过程。这个函数只有在某个需要被延迟绑定的函数的第一次执行时才会被调用,函数会根据.plt,.rela.plt(或者rel.plt),.dynsym以及.symstr这几个节中提供的信息来对函数的真实地址进行解析,然后将其写回.got.plt(或者.got)节中的对应位置,我用图来对这个过程做一简单说明。

注:32位和64位的rela结构和sym结构略有不同,需要按实际情况处理;
注:32位的offset是真正的offset而64位的offset是一个index

我隐藏了_dl_runtime_resolve和_dl_fixup函数的一些细节,感兴趣的可以自己去翻阅源码,不过我需要着重提一个细节。_dl_fixup函数会根据r_info>>32的值在.gun.version节查找一个值,这个是一个坑点。

3. exploit过程

3.1 题目概述

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buffer[1024]; // [rsp+0h] [rbp-400h]
  fgets(buffer, 1337, stdin);
  return 0;
}

代码很简单,1024字节的缓冲区但是读入了1337个字节导致了缓冲区溢出;

 

没有保护。
看到这里我最开始的思路是找一个jmp esp或者能跳到栈里面的跳板来执行shellcode,但是结果是没有这样的跳板存在,这时候就基本上确定是ret2dlresolve了。

3.2 问题一:无法构造函数参数

按照正常流程计算偏移,覆盖RIP,然后在这个时候需要第二次调用fgets函数来写入第二阶段的payload,ROPgadget可以找到控制rdi的pop rdi指令,也可以找到控制rsi的pop rsi指令,但是,找不到控制rdx的指令。
这时候我选择了放弃ROPgadget这样的工具,回归程序中的全部反汇编,寻找gadget,(objdump -D ./aleph1)然后我就看到了main函数,第一次调用fgets就是主函数调用的,并且写入地址是由rbp来控制的,写入的地址是rbp-0x400,那就好办了,只要控制了rbp就等于控制了写入地址,选择.bss+0x800作为payload的地址利用pop rbp;ret序列控制rbp并跳入主函数执行fgets函数,payload会从.bss+0x400开始写。
可以得到第一阶段的payload为


接下来继续调试,主函数以leave ret指令序列结束,也就是说函数接下来的执行地点取决于rbp的值,我们的目的是让程序跳到我们第二次构造的payload里面 ,rbp指向.bss+0x800,但是payload从.bss+0x400开始写,那么就需要给第二次的payload在开始位置具有0x400 bytes的padding。
解决了返回地址问题,然后就需要跳转至PLT[0],八字节填充,八字节PLT[0]地址再加八字节offset,将控制流成功劫持到了_dl_runtime_resolve里面去。
然后需要伪造offset使对rela的查找落到我们的地址上去,这里伪造offset时需要注意64位下的offset需要对偏移除以sizeof(Elf64_Rela)来得到index,32位在这里就不需要。然后伪造Elf64_Rela项目,一共三个项目:第一个r_offset,填GOT[‘fgets’];第二个r_info高32位是sym的index需要伪造,低32位填0x7就好了,代表这是一个R_X86_64_JUMP_SLO;第三个是r_addend填0就好,这样一个rela项就伪造好了。
接下来需要控制r_info的高32位指向伪造的Elf64_Sym,这里同样,需要对计算出的偏移除以sizeof(Elf64_Sym),来算出index。然后伪造Elf64_Sym项目:第一个是st_name,是函数名相对于.dynstr的偏移,剩下的照搬原来的就好。
然后就出现问题了,内存访问异常了!

3.3 问题二:访问.gun.version节遇到非法内存

通过调试以及查询资料,发现问题出现在r_info>>32也会用于在.gun.version节中查询信息,这就导致了一个内存访问异常。具体原因不好说明,希望各位可以把东西下下来自己调试一下。
然后我问了论坛的大表哥,并且也查了查资料,发现把linkmap+0x1c8处的一个8字内存设置为0就可以。关于linkmap的资料我没找到,想翻源码又不知道在哪,于是这部分的问题我就略过了,还希望大表哥们能给个明路。
然后我就在想,反正第二阶段的payload前面有0x400字节的padding,那就先把控制流劫持到payload2的开始处,并在这里通过自己写的汇编代码把那个内存置零(反正没有PIE,可以写位置相关代码),然后在返回到PLT[0]处就好了。用pwnlib的asm进行汇编,然后问题又来了,拿不到汇编代码,总是提示我mov引用过多的内存。不会用人家的工具,这真尴尬,然后我就索性抛弃工具,既然机器汇编不成,那我就脑力汇编,真正的程序员怎么可以被工具束缚。
然后就有了如下二进制序列

注:简单解释一下第一个指令,\x48是64位指令的REX Prefix,一方面指明指令使用64位地址,一方面还可以扩展指定寄存器的数量,\x8b是操作码,\x04是ModR/M,指明使用的寄存器,\x25是SIB指明寻址信息,最后四个字节是Displacement,值为GOT[1],也就是linkmap的地址,注意是小端方式,要倒着写。
脑力汇编之后,在把控制流导回.bss+0x800的地方去,继续调用PLT[0],这样就可以防止内存访问异常。
下来的任务就是在payload后面加上system字符串,然后把st_name的偏移计算出来,这样就可以调用system函数了。
但是,还缺少一点东西,那就是system函数的参数,需要在rdi里面。又要在payload2里面插一个gadget了,用pop rdi;ret序列把/bin/sh字串的地址存进去。然后就可以成功getshell了。
第二阶段的payload有点复杂我就不放截图了,想看的下脚本看吧。

4. 写在后面

比赛当时我并没有做出来这道题,我花了两个小时在这道题上。而且我总觉得我把这道题想复杂了,应该会有更简单的办法去解决这个问题。
然后构造shellcode十分不易,需要精心布置内存,多次转移控制流,这个繁琐的过程需要的一定的耐心以及大胆的想法。
最后希望我以及选择了这条路的朋友可以一直坚持下去。
(求个职,本菜鸡还没找到实习,哪家收留我)


[公告]《CTF高级解混淆》训练营,国际顶尖CTF战队大牛亲自授课,助你快速成长!

最后于 2018-6-17 21:37 被ninebianbian编辑 ,原因:
上传的附件:
收藏
点赞0
打赏
分享
最新回复 (9)
雪    币: 2945
活跃值: 活跃值 (32)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
V1NKe 活跃值 2 2018-6-17 21:50
2
0
其实你这题有点想太多了,你的第一个问题中其实就完全可以劫持程序流,用不着dl_resolve
最后于 2018-6-17 21:51 被V1NKe编辑 ,原因:
雪    币: 327
活跃值: 活跃值 (14)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
ninebianbian 活跃值 2018-6-17 22:03
3
0
VINKKe 其实你这题有点想太多了,你的第一个问题中其实就完全可以劫持程序流,用不着dl_resolve
dalao,劫持了之后用什么样的代码getshell呢?
雪    币: 2945
活跃值: 活跃值 (32)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
V1NKe 活跃值 2 2018-6-17 22:23
4
0
ninebianbian dalao,劫持了之后用什么样的代码getshell呢?
直接可以用pwntools里的shellcarft.sh()写
雪    币: 2945
活跃值: 活跃值 (32)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
V1NKe 活跃值 2 2018-6-17 22:24
5
0
.
最后于 2018-6-17 22:29 被V1NKe编辑 ,原因:
雪    币: 5468
活跃值: 活跃值 (214)
能力值: ( LV17,RANK:1155 )
在线值:
发帖
回帖
粉丝
holing 活跃值 15 2018-6-18 09:40
6
0
是的
0x602000  rwxp
这块可读可写可执行,直接ROP写这里然后跳过来shellcode就好了
最后于 2018-6-18 09:42 被holing编辑 ,原因:
雪    币: 327
活跃值: 活跃值 (14)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
ninebianbian 活跃值 2018-6-18 10:39
7
0
holing 是的0x602000  rwxp这块可读可写可执行,直接ROP写这里然后跳过来shellcode就好了
我在脑力汇编的时候就已经再考虑了,既然已经能执行位置有关代码了,应该可以直接getshell了,但还是把他当ret2dlresolve做了,就当学习了吧,还是不熟悉,不太懂Linux的shellcode原理,不过还是感谢大佬
雪    币: 5468
活跃值: 活跃值 (214)
能力值: ( LV17,RANK:1155 )
在线值:
发帖
回帖
粉丝
holing 活跃值 15 2018-6-18 11:03
8
0
ninebianbian 我在脑力汇编的时候就已经再考虑了,既然已经能执行位置有关代码了,应该可以直接getshell了,但还是把他当ret2dlresolve做了,就当学习了吧,还是不熟悉,不太懂Linux的shellcod ...
Linux  shellcode直接用中断调execve,pwntools里面有内置的
雪    币: 2945
活跃值: 活跃值 (32)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
V1NKe 活跃值 2 2018-6-26 14:04
9
0
请问指到bss段的时候为什么要在bss基址的基础上再加上一段偏移地址呢
雪    币: 327
活跃值: 活跃值 (14)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
ninebianbian 活跃值 2018-6-28 09:30
10
0
VINKKe 请问指到bss段的时候为什么要在bss基址的基础上再加上一段偏移地址呢
emmm,你看看我问题一的前三段,然后结合代码分析一下RBP,RSP以及返回地址的关系;简单点就是因为找不到合适的构造函数参数的跳板,所以用了主函数中的代码作为跳板。
游客
登录 | 注册 方可回帖
返回