pwnable_tw笔记
本文最后更新于:2023年11月8日 中午
Pwnable_tw笔记
pwnable开坑了,希望自己能把这个大坑补完,近期和学长交流了一下,受益颇多,也深深感受到了自己水平上的差距,再次清醒认识到自己是蒟蒻(,所以遵从勤能补拙的思路,希望通过多积累和见识题目真正在System方面有所深入
start
拿到手先赶紧进行一个checksec
这里推荐最好用多个工具检查一番,因为看网上有人说peda检测此程序会提示开了NX,实则没有,这样会让做题人徒增烦恼 :thinking:
RELRO(Relocation Read Only):尽量使存储区域只读
接着进行一个IDA的分析
这里可以看到使用了int80方式的系统函数调用方法
INT 80h 系统调用方法
系统调用的过程可以总结如下:
1. 执行用户程序(如:fork)
2. 根据glibc中的函数实现,取得系统调用号并执行int $0x80产生中断。
3. 进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进行内核模式)
4. 进行中断处理,根据系统调用表调用内核函数。
5. 执行内核函数。
6. 执行RESTORE_ALL并返回用户模式
Linux 32位的系统调用时通过int 80h来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数。
其中int80调用的系统函数编号可以在网络上找到
其中第一个系统调用为将esp(赋给ecx执行)开始的0x14H字节数据写入标准输出,即最终输出Let's start the CTF.
name | eax | ebx | ecx | edx |
---|---|---|---|---|
sys_write | 0x04 | unsigned int fd = 1 | const char __user *buf = esp | size_t count=14H |
第二个系统调用则是从标准输入读取0x3cH字节到栈空间,这里义眼顶针,鉴定为可能可以溢出
name | eax | ebx | ecx | edx |
---|---|---|---|---|
sys_read | 0x03 | unsigned int fd=1 | char __user *buf=esp | size_t count=3ch |
现在我们知道了读入应当使用的是read(1,esp,0x3c)
,那么下一步就是要找到esp在哪了,通过最后一部分的add esp,14h
我们推断start函数使用的是内平栈,几esp距离ret有0x14个字节。我们可以通过gdb来证明猜想。
在这里我们看到了buf的地址,再看ret
果然就是0x14,所以padding就是'a'*0x14
,接下来就是要考虑如何劫持控制流,这里因为保护全没开,所以可以试试ret2shellcode,所以就必须要知道栈的地址,要想泄露栈地址,那么就要考虑使用一下write函数了,当我们在ret的地址填充0x8048089就可以打印出栈的值
1 |
|
read有60B,我们可以使用的有60B-24B=36B,所以我们还得去找一个断点的payload
1 |
|
查看一下其具体作用
这就是在做系统调用,所以只要我们把对应获取shell的系统调用的参数放在对应寄存器中,然后执行int80,就可以执行对应的系统调用,此处我们利用如下方式来getshell
1 |
|
其中,该程序是 32 位,所以我们需要使得
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。为了4字节对齐,我们用//bin/sh
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
这里我们使用xor ecx,ecx
来代替原shellcode中的mov ecx,0
1 |
|
所以就可以写出如下exp
1 |
|
成功getshell,最后find一下flag提交即可,大功告成
orw
拿到手这次吸取上个题的经验,先进行一个checksec
彳亍,看来pwnable确实对新手最友好,那就继续拖入IDA进行分析
看到了orw_seccmop()
函数,进入查看是关于系统内部的调用prctl
的进程创建和管理内容,不太理解含义,遂Google之,发现是关于沙箱保护的函数,通过这一函数可以划定程序准许用户态调用的系统函数,相当于划定白名单,即题目所言仅开启了open、write、read。这里我们可以用seccomp查看其保护规则。
通过分析可知,本题是直接执行了用户remote传来的shellcode,但是通过沙箱机制只授予了write, read, open权限,所以我们需要通过这三个操作获取到flag的值,而flag具体位置在题目里面已经给出了。结合题目意思,可以使用open函数打开flag文件,然后read读出文件内容,最后write输出到控制台。
后面的思路就很简单了,我们只需要调用其open-read-write过程读取出/home/orw/flag
下的内容并输出即可,这里有两种方式解决。
0x01. shellcraft一把梭
shellcraft是一种自动化生成shellcode的工具,只需要你了解pwn的过程和方法,可以大部分避开手搓汇编的过程,放到这个题目里面个人猜测出题人也是想要通过这个方式来解决,因为本题flag路径以及getshell方式已经很明确了(o-r-w),所以我们废话少说,立马开始写shell
1 |
|
这样就可以直接getshell
0x02. 手搓汇编
鉴于MTCTF因为汇编不熟造成的惨痛经历,个人认为汇编基础还是要牢牢把握,刚好看到也有师傅手搓的WP,遂计划照猫画虎,自己也学习一下。
我们选择x86 syscall构造出 open
read
write
open(file='esp', oflag='O_RDONLY', mode=0)
- `eax` 是 0x3 - `fd` 存在 `open()` 的回传結果 (`eax`) - `buf` 在 `esp` - `length` 为 60,任意数值,能够覆盖flag即可1
2
3
4
5
- 这里可以直接用pwntools给出的写法
- ```
read(fd, buf, length)```
write(stdout, buf, length)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- `eax` 是 0x4
- `stdout` 是 1
- `buf` 是刚刚读取的 `esp`
- `length` 是 60,参照read的length
可以得出的汇编代码如下
```assembly
/* push '/home/orw/flag\x00' */
push 0x1010101
xor dword ptr [esp], 0x1016660
push 0x6c662f77
push 0x726f2f65
push 0x6d6f682f
/* open(file='esp', oflag='O_RDONLY', mode=0) */
mov ebx, esp
xor ecx, ecx
xor edx, edx
/* call open() */
xor eax,eax;
mov al,0x5;
int 0x80;
/*call read()*/
mov ebx,eax;
xor eax,eax;
mov al,0x3;
mov ecx,esp;
mov dl,0x30;
int 0x80;
/*call write()*/
mov al,0x4;
mov bl,1;
mov dl,0x30;
int 0x80;
1 |
|
也可得出最终结果