【从零开始学CTF】9、Pwn从入门到放弃或改行

【从零开始学CTF】9、Pwn从入门到放弃或改行

呃呃呃,貌似很久没更新了,咸鱼了一个寒假,就学了点PWN的入门,新的学期开学了,继续恢复更新233。这学期将由社团其他师傅讲课,我只负责PPT搬运了。


Pwn从入门到放弃或改行

Author:CancerGary


0x00:什么是Pwn - 二进制漏洞利用

Pwn 题目主要考察二进制漏洞的发掘和利用,需要对计算机操作系统底层有一定的了解。在 CTF 竞赛中,PWN 题目主要出现在 Linux 平台上。


0x01:基本知识

汇编语言:是一种用于电子计算机微处理器微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。一种汇编语言专用于某种计算机系统结构,而不像许多高级语言,可以在不同系统平台之间移植。

使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程

寄存器:是中央处理器内的其中组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。

IP/EIP/RIP:指向当前的命令地址

SP/ESP/RSP:指向栈的顶部

BP/EBP/RBP:指向栈的底部

栈:一种后进先出的数据结构(Last In First Out),用于调用函数过程中恢复寄存器的状态。

常用的汇编指令:

MOV %src %dest :把数据从源地放到目的地

push:从栈中取出一个元素

POP:往栈中压入一个元素

call:将当前的指令指针EIP(该指针指向紧接在call指令后的下条指令)压入堆栈

ret:从栈顶弹出返回地址(之前call指令保存的下条指令地址)到EIP寄存器中,程序转到该地址处继续执行(此时ESP指向进入函数时的第一个参数)

AT&T语法和Intel语法

AT&T
语法要在常数前加
$、在寄存器名前加 % 符号;Intel 语法没有相应的东西要加

AT&T
语法先写源操作数,再写目标操作数;Intel 语法先写目标操作数,再写源操作数

AT&T
语法将操作数的大小表示在指令的后缀中(b、w、l);Intel 语法将操作数的大小表示在操作数的前缀中(BYTE PTR、WORD
PTR、DWORD PTR)

大端模式和小端模式:
•以0x12345678为例(左到右代表地址从低到高)

•小端 \x78\x56\x34\x12

•大端 \x12\x34\x56\x78

•大端模式:PowerPC、IBM、Sun、大部分网络协议

•小端模式:X86,DEC

(其实就是表示一串地址的顺序问题)

0x02:函数调用的过程

  • 保存现场(一会好回来接着做)
  • 传递参数(可选,套公式的时候需要些什么数据)
  • 返回(把计算结果带回来,接着刚才的事)

当发生函数调用的时候,栈空间中存放的数据是这样的:
1、调用者函数把被调函数所需要的参数按照与被调函数的形参顺序相反的顺序压入栈中,即:从右向左依次把被调函数所需要的参数压入栈;

2、调用者函数使用call指令调用被调函数,并把call指令的下一条指令的地址当成返回地址压入栈中(这个压栈操作隐含在call指令中);

3、在被调函数中,被调函数会先保存调用者函数的栈底地址(push ebp)(从高内在地址--》低内存地址),然后再保存调用者函数的栈顶地址,即:当前被调函数的栈底地址(mov ebp,esp);
4、在被调函数中,从ebp的位置处开始存放被调函数中的局部变量和临时变量,并且这些变量的地址按照定义时的顺序依次减小,即:这些变量的地址是按照栈的延伸方向排列的,先定义的变量先入栈,后定义的变量后入栈;
所以,发生函数调用时,入栈的顺序为:
参数N
参数N-1
参数N-2
.....
参数3
参数2
参数1
函数返回地址
上一层调用函数的EBP/BP
局部变量1
局部变量2
....
局部变量N
函数调用栈如下图所示:


函数执行完成后,将栈顶的返回地址弹出,EBP弹出,恢复上一个函数的状态。

0x03 栈溢出

这么久才进入正戏233

栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致栈中与其相邻的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞(比如说,还有向堆中写,向bss段写)。而对于黑客来说,栈溢出漏洞轻则可以使得程序崩溃,重则可以使得攻击者控制程序执行流程。此外,我们也不难发现,发生栈溢出的基本前提是

  • 程序必须向栈上写入数据。
  • 写入的数据大小没有被良好地控制。

我们来看这个程序:

执行效果就是输入一个字符串,然后输出

在IDA中我们按F5试试

变成了这样的伪代码。

我们通过

read(0, &buf, 0xFFuLL);

读取0xff长度的字符串到buf里,然后再输出buf。

然而我们的buf有多长呢?我们双击buf看看。

buf位于ebp-10的位置,0x10-0x08只有两2个字节的空间。那么当我们输入一个超长的字符串(大于2字节)会发生什么呢?

很显然,会覆盖到上面栈的内容(ebp-08以上的栈空间)

这时候我们就能对栈里其他的变量进行修改了,比如我们的函数返回值。

我们看下0x08-(-0x10)=0x18,也就说这两个变量在栈里差18个字节,那我们只要输入

18个字符,那么接下来我们就能覆盖返回地址的内容了。

当然不只是函数返回地址,我们还可以覆盖栈里的其他元素,比如var_8,我们只需要输入2个字符,接下来的内容就能覆盖var_8了。

那么能覆盖返回地址有什么用呢?

我们来回顾下前面说的函数调用过程,当子函数执行完毕,就会把主函数下一个命令的地址出栈并赋值给EIP寄存器,随后EIP寄存器就会去此地址执行,那我们是不是可以通过覆盖返回地址的内容,达到使得程序执行我们想执行函数的目的呢?

我们再来看看程序的函数:

这里有个bingo点进去看看:

调用了系统函数,执行了shell。那我们只要执行这个函数就能执行shell了,那我们该如何执行这个函数呢?就通过上面说的覆盖的思路。

查看得bingo函数的地址0x040068D,那我们就可以构造利用的payload了

"A"*0x18+"\x8D\x06\x40\x00"    //0x040068D

也可以利用pwntools
apt-get update
apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev
build-essential

pip install --upgrade pip

pip install --upgrade pwntools

(抛错可能是默认pip3 改用pip2

from pwn import *
sh=process('./first')
bingo_address=0x040068D
payload='a'*0x18+p64(bingo_address)
sh.sendline(payload)
sh.interactive()

执行结果

成功getshell。

引用:

zh.wikipedia.org/wiki/%

CTF 竞赛内容 - CTF Wiki

zh.wikipedia.org/wiki/%

汇编语言---函数调用栈 - taek - 博客园

编辑于 2018-03-18

文章被以下专栏收录

    大家好,我是H1TerHub的Rman,一直以来对网络安全十分感兴趣,今年在学校里成立H1TerHub战队,主打CTF的赛事。由于很多同学都是刚刚接触网络安全和CTF,于是就开了一些课,帮助他们入门和接触这个赛事。 于是乎就有把上课用的PPT编辑下分享给大家的想法。争取按照我们上课的进度,每周一更,希望能让更多的人了解这个比赛,也希望能跟大家共同交流学习。