11~20
11.bjdctf_2020_babystack
很简单的ret2text
(1)分析
main
buf前的nbytes可以控制read函数的读入字节数,造成溢出点
backdoor
则
binsh_addr=0x4006e6
溢出长度在IDA中看一下就好
(2)payload
1 |
|
12.get_started_3dsctf_2016
(1)分析
main
有gets溢出漏洞
看到提示函数
进入内存看a1和a2在栈上的参数位置,它们的位置在返回地址之后,没法利用溢出覆盖来控制程序
查看打开flag的地方
可以看到在此位置程序打开flag文件,我们控制程序跳转到此处执行理论上就可以拿到flag,但是发现只能在本地打通。
方法一
查看网上师傅的wp发现无法拿到的原因是因为我们没有维持栈的平衡,导致程序异常退出,但为啥本地异常退出可以回显文本中的字符串而远程不可以,这就不太清楚,有可能远端做了限制。
通过后门函数进行回显flag
针对上面远端无法拿到结果的问题,这里给出新的解法,也就是维护好栈,使得执行完get_flag()后门函数让程序正常退出。打远程时,如果程序是异常退出了,最后是不给你回显的。所以我们得想办法让程序正常退出。C语言有个函数是exit,只要执行这个只要我们把get_flag的返回地址写成exit的地址,程序就可以正常结束并且有回显了。(==静态编译文件==)
通过GDB或IDA,我们可以拿到exit函数的地址
那么思路就有了
我们先利用gets溢出控制程序打开flag文件,再用exit函数让程序可以正常退出,最后将返回地址溢出成==带参数的==,就能拿到flag
方法二
这题比较巧妙的做法
程序里有一个mprotect函数(==以后静态编译文件可以试着找找此类库函数==),它的作用是能够修改内存的权限为可读可写可执行,然后我们就可以往栈上写入shellcode,执行即可获取shell
首先学习一下这个函数
int mprotect(const void *start, size_t len, int prot);
第一个参数填的是一个地址,是指需要进行操作的地址。
第二个参数是地址往后多大的长度。
第三个参数的是要赋予的权限。
mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:
1)PROT_READ:表示内存段内的内容可写;
2)PROT_WRITE:表示内存段内的内容可读;
3)PROT_EXEC:表示内存段中的内容可执行;
4)PROT_NONE:表示内存段中的内容根本没法访问。
prot=7 是可读可写可执行
需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
(通过看源码,可以发现几个参数都是整型数据,就不奇怪了)
就这样,我们就可以将一段地址弄成可以执行的了。因为程序本身也是==静态编译==,所以地址是不会变的。
首先利用了mprotect函数修改内存权限,然后利用read读取shellcode到该内存区域。由于开启了NX保护,所以不断利用retn返回地址来达到调用函数,注入shellcode的目的。
那么我们需要做的是
- 第一个参数为需要修改的内存起始地址,这个地址也就是我们接下来shellcode将要写入的地方;
- ==第二个参数为修改的内存大小,一般取内存的整数页==;
- 第三个参数为0x7,表示该内存拥有可读可写可执行权限。
read函数第一个参数是打开的文件描述符,0表示输入;第二参数指明读取的数据存放的内存起始位置;第三个参数指明最大读取的字节数。而每次调用函数后都是返回到pop_3_ret中执行,是用该段汇编代码弹出压入栈中的三个参数来达到栈平衡。该代码的地址不唯一,在程序中找到相应弹出三个栈的操作再加retn的汇编代码就行。
==随便找就行==
具体方法见exp2
(2)payload
exp1
1 |
|
exp2
1 |
|
(3)总结
13.jarvisoj_level2_x64
(1)分析
main
vulnerable_function
read函数漏洞
system和/bin/sh字符串都有
没啥好说的,ret2text,注意一下64位ROP链构造
payload=B’a’*offset+p64(pop_rdi_ret)+p64(binsh_addr)+p64(system_addr)
(2)payload
1 |
|
14.[OGeek2019]babyrop
(1)分析
main
/dev/urandom是Linux的一个生成随机数文件,0代表只读,但是open函数还是返回一个大于0的数,read函数将fd赋给buf,因此buf处的数是随机的。
将buf作为参数传给sub_804871F,返回值赋值给v2,将v2作为参数传给 sub_80487D0。
进入sub_804871F函数
memset函数:将某一块区域内容设置为指定内容(0),多用于数组的初始化。
memset函数会将s和buf中元素都初始化为0.
sprintf函数,将a1打印在s上。
read函数从键盘读取0x20字节内容写入buf,并将返回值赋值给v5,read函数返回值为读取的字节数。
接下来判断buf和s大小,相等返回0,不相等返回非0的数程序就会终止。因此第一个任务就是使比较结果为0,不让程序退出,我们可以采用’\0’截断绕过,让v1为0,这样strncmp函数会立即返回0。
sub_80487D0函数
把v5的值作为参数传入,进行条件判断运算。在第2个read中,只要a1足够大,就能发生栈溢出,并且能够溢出到ret位置。
根据ASCLL码最大值为255,用‘\xff’表示,我们将buf[7]改为‘\xff’即可造成溢出
在sub_804871F函数中,为防止程序退出,就要使strncmp(buf, s, v1)结果为0
可以看到v1 = strlen(buf);用\0截断,在输入时设置第一个元素为\x00
最后返回的时buf[7],看sub_80487D0函数,我们需要将传入的参数尽可能大了写,这样才能栈溢出并进行想要的操作
所以在输入时就要将buf[7]也一起修改了。
另外,sendline函数的结束符也是算一个字节的,所以输入的时候只要*7就可以了(当然保险起见, *8也是可以的)
(2)payload
1 |
|
(3)总结
- strlen的特性是遇到’\0’就停止记录字符串长度,这里可以考虑\0截断绕过strncmp函数判定。
- 在LibcSearcher找不到匹配的libc库的时候,要会使用题目中给的libc版本
- 32位程序函数参数直接压入栈中,即函数地址->函数的返回地址->参数n->参数n-1>···>参数1
- python3取消了binsh_libc=libc.search(‘/bin/sh’).next()的用法。
15.[HarekazeCTF2019]baby_rop
(1)分析
main
看字符串
system和/bin/sh字符串都有
binsh_addr=0x601048
system_addr=0x400490(找_system函数地址)
接下来就是ROPgadget找gadgets
这时发现ROP链打不通
排查原因也不是payload的问题,考虑栈对齐问题
关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题
64位ubuntu18以上系统调用system函数时是需要栈对齐的。再具体一点就是64位下system函数有个movaps指令,这个指令要求内存地址必须16字节对齐,如果你到system函数执行的时候,si单步进去就会发现,如果没对齐的话,最后就会卡在这里(如下图)。
程序会卡在这里不动了,无法继续执行
我们可以看一下栈内的状态
因为64位程序的地址是8字节的,而十六进制又是满16就会进位,因此我们看到的栈地址末尾要么是0要么是8。
只有当地址的末尾是0的时候,才算是与16字节对齐了,如果末尾是8的话,那就是没有对齐。而我们想要在ubuntu18以上的64位程序中执行system函数,必须要在执行system地址末尾是0。
所以上图是没对齐的情况,所以我们需要加一个凑数用的gadget
于是找到上面的ret地址(对ROP链无影响)
下面是打通的栈内状态
可以看到首地址末尾是0了
然后我们又发现打通后flag不在当前目录
使用这条指令就能找到flag所在文件目录
find / -name flag
cat ./xxx/xxx/flag
那么本题完结
(2)payload
1 |
|
(3)总结
- Ubuntu18以上版本注意栈对齐问题
- flag不在当前目录要会找
16.ciscn_2019_n_5
(1)分析
基本没有保护,程序也没有system和/bin/sh字符串,可以试试ret2shellcode
name是bss段的,在name上写shellcode,然后gets溢出到name的地址执行shellcode
(2)payload
1 |
|
17.others_shellcode
直接nc过去就通了。。。
18.ciscn_2019_en_2
同第8题,给payload再稍微解释一下
(1)payload
1 |
|
19.not_the_same_3dsctf_2016
(1)分析
进入IDA发现,函数非常多,基本是静态编译,所以一些C语言的库函数可以拿来用了
main
get_secret
发现程序将flag存在fl4g中,我们可以利用write函数(==这里需要积累常用C语言库函数==)从这里读取flag
那么思路基本明确,先利用get_secret函数让程序将flag存在fl4g中,然后返回main函数,再利用write函数读取fl4g的内容,也就是flag。
(2)payload
1 |
|
20.ciscn_2019_ne_5
(1)分析
main
GetFlag函数
strcpy函数复制src到dest时,只要src够长,就能让dest溢出
Print函数
给了system函数
AddLog函数
那么我们可以先让V4的值为1,在src写入payload,再设置V4的值为4,使得dest溢出
需要注意的是,不仅/bin/sh字符串可以拿权限,sh有时也可以,类似的还有$0
sh字符串查找
ROPgadget –binary 20 –string ‘sh’
(2)payload
1 |
|
(3)总结
system函数可以在IDA中这么找
双击跟进
也可以在pwndbg中打断点找
还能直接在终端找
objdump -d -j .plt ./program’s_name | grep system
偏移地址可以这么找