--> --> --> Computing101 collections
Featured image of post Computing101 collections

Computing101 collections

pwncollege计算机探究模块,进行一个速通顺便学学网络编程

computing 101

hello hackers

writing output

1
2
3
4
5
mov rdi,1
mov rsi,1337000
mov rdx,1
mov rax,1
syscall

chaining syscalls

同时设置两个系统调用,要做的就是分成两部分去设置就行

1
2
3
4
5
6
7
8
9
mov rdi,1
mov rsi,1337000
mov rdx,1
mov rax,1
syscall

mov rdi,42
mov rax,60
syscall

assemble crash course

调试:由于环境中是用python进行的模拟,因此无法gdb调试,但如果我们在汇编中加入int 3,那么模拟器会打印出现在的寄存器和内存情况

重复多的我直接写汇编,或者直接跳过了(

4 linear-equatation-registers

image

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


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
imul rdi,rsi 
add rdi,rdx 
mov rax,rdi 
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

5 interger-division

寄存器进行整除操作

x86除法指令的特殊性

div 指令用于128位被除数 ÷ 64位除数:

  • 被除数rdx:rax(128位,高64位在rdx,低64位在rax)

  • 除数:指定的寄存器

  • 结果

    • rax = 商(quotient)
    • rdx = 余数(remainder)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from pwn import *
context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
mov rax,rdi 
xor rdx,rdx ;由于高64bit存储了rdx,防止出错
div rsi
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

6 modulo-operation

将mod的余数放入rax,前面除法的时候高64bit放了rdx,低64bit放了rax

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


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
mov rax,rdi 
xor rdx,rdx 
div rsi 
mov rax,rdx
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

7 set upper byte

image

x86的寄存器结构图,x86的好处在于通过名字我们可以轻松控制寄存器中不同长度的数字从64bit到32bit到16bit再到8bit

mov ah,0x42

8 efficient modulo

x % y,如果y为$2^n$那么我们就可以得到x=n

要求

  • rax = rdi % 256
  • rbx = rsi % 65536
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from pwn import *
context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
mov al,dil 
mov bx,si
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

9 byte-extraction

对二进制进行操作,我们需要用到and or xor not等逻辑操作

shlshr,shift once to the left和shift once to the right

shl reg1,reg2(reg2可以替换成地址)

1
2
rdi = | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
Set rax to the value of B4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20


from pwn import *
context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
mov rax,rdi 
shr rax,32 
shl rax,56
shr rax,56
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

10 bitwise and

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


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
and rdi,rsi 
xor rax,rax
add rax,rdi           
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

11 check even

1
2
3
4
if x is even then
  y = 1
else
  y = 0

可以用xor 1来进行反转判断

1
2
3
4
xor rax,rax 
or rax,rdi ;rax=rdi
and rax,1  ; 1的话->0,后面反转奇数判断
xor rax,1

12 memory read-14 memory increment

mov rax,[0x404000]

mov [0x404000],rax

关于解析指针所在位置字节

  • byte ptr - 8位
  • word ptr - 16位
  • dword ptr - 32位
  • qword ptr - 64位

我们在移动数据到指定寄存器或地址的时候要注意不会清除高位字节

mov al, [0x404000]

15 memory size access

1
2
3
4
mov al,[0x404000]
mov bx,[0x404000]
mov ecx,[0x404000]
mov rdx,[0x404000]

16 little-endian-write

注意,不能直接将大常数移动到内存地址,但可以通过寄存器间接传递

1
2
3
4
5
mov rax,0xdeadbeef00001337
mov [rdi],rax 
mov rax,0xc0ffee0000 
mov [rsi],rax 
int3

17 memory-sum

通过将地址or寄存器指向地址+offset可以解析到不同的值,这题需要两个qword

而64位寄存器本身存储就是qword,因此直接用偏移

1
2
3
4
mov rcx,[rdi] 
mov rdx,[rdi+8]
add rdx,rcx 
mov [rsi],rdx

18 stack-subtraction

1
2
3
pop rdx 
sub rdx,rdi
push rdx

19 swap-stack-value

1
2
3
4
5
6
push rdi 
pop rdx 
push rsi 
pop rdi 
push rdx 
pop rsi

20 average-stack-values

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mov rax,[rsp]
mov rbx,[rsp+8]
mov rcx,[rsp+16]
mov rdx,[rsp+24]

add rax,rbx 
add rax,rcx 
add rax,rdx 
shr rax,2 
push rax 

21 absolute jump

There are two major ways to manipulate control flow:

  • Through a jump

  • Through a call

  • 相对跳转jmp +0x10jmp -0x5(相对于当前指令)

  • 绝对跳转:通过寄存器间接跳转,如 jmp rax

  • 间接跳转jmp [rax](跳转到 rax 指向的内存地址中的值)

1
2
mov rax,0x403000
jmp rax

22 relative jump

使用nop进行填充

并使用段落进行repeat

GNU assembler archive

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


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
jmp target
.rept 0x51 
nop 
.endr 
target: 
mov rax,0x1
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

23 jump-trampoline

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


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
jmp target
.rept 0x51 
nop 
.endr 
target: 
pop rdi 
mov rax,0x403000 
jmp rax
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

24 conditional-jump

利用jneje指令(jmp if not equal;和jmp if euqal)

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


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
cmp dword ptr [edi],0x7f454c46 
jne check_second

mov eax,[edi+4] 
add eax,[edi+8] 
add eax,[edi+12]
jmp end           

check_second:           
cmp dword ptr [edi],0x00005A4D 
jne done

mov eax,[edi+4]
sub eax,[edi+8]
sub eax,[edi+12]
jmp end           

done:
mov eax,[edi+4] 
imul eax,[edi+8] 
imul eax,[edi+12]

end:
nop           
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

end分支来防止执行多余指令

25 indirect jump

题目会给出随机测试数据,根据测试数据来进行对应跳转(case)

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


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
cmp rdi,3 
ja default
jmp [rsi+rdi*8]
default:
jmp [rsi+32]
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

26 average-loop

用rcx作为计数器,rsi作为除数,div的时候为了防止出现rdx干扰要置0

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


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
xor rax,rax
mov rcx,rsi 
test rcx,rcx
jz done 

sum_loop:
add rax,[rdi] 
add rdi,8
dec rcx           
jnz sum_loop           

mov rdx,0 
div rsi 

done:
nop
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

27 count no-zero

 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 *
context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
.intel_syntax noprefix
mov rax,0 
mov rsi,0 
test rdi,rdi 
jz .done 

.loop:
  cmp byte ptr[rdi],0 
  je .done 
  add rax,1 
  add rdi,1 
  jmp .loop
.done:
nop
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()
 

28 string-lower

1
2
3
0x1021 mov rax, 0x400000
0x1028 call rax
0x102a mov [rsi], rax
  1. call pushes 0x102a, the address of the next instruction, onto the stack.
  2. call jumps to 0x400000, the value stored in rax.

ret pops the top value off of the stack and jumps to it.

 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(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
str_lower:
    mov rbx,0x403000 
    xor rcx,rcx 
    test rdi,rdi 
    jz done 
string_read:
    mov al,byte ptr [rdi] 
    test al,al 
    jz done ;如果是NULL直接结束
    cmp al,0x5A ;大于'Z'不是大写字母
    ja next_char 
    mov rsi,rdi ;保存字符串地址到rsi,稍后恢复 
    mov dil,al ;rsi和dil作为foo的参数
    call rbx 
    mov rdi,rsi 
    mov byte ptr [rdi],al ;转换后的字节放入[rdi]
    inc rcx 
next_char:
    inc rdi ;src_addr存储在rdi,[rdi]为字节的值
    jmp string_read 
done:
    mov rax,rcx 
    ret 
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

29 most-common-byte

最终挑战

rbp确认函数的栈基地址

我们要完成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
most_common_byte(src_addr, size):
  i = 0
  while i <= size-1:
    curr_byte = [src_addr + i]
    [stack_base - curr_byte * 2] += 1
    i += 1

  b = 0
  max_freq = 0
  max_freq_byte = 0
  while b <= 0xff:
    if [stack_base - b * 2] > max_freq:
      max_freq = [stack_base - b * 2]
      max_freq_byte = b
    b += 1

  return max_freq_byte

限制:

  • counting_list放在栈上
  • You must restore the stack like in a normal function
  • 不能修改放在src_addr的数据
 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
from pwn import *
context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
most_common_byte:
    push rbp 
    mov rbp,rsp 
    sub rsp,0x100 ;开拓栈空间
    xor rcx,rcx ;rcx作为个数计数器置0
initial:
    mov byte ptr [rbp-256+rcx],0 ;将rcx作为索引进行存储
    inc rcx ;byte ptr rcx移动到下一位
    cmp rcx,0x100 ;是否移动到输入字符尾部
    jl initial ;如果没有则继续输入
    xor rcx,rcx ;重新置0计数器
count_bytes:
    movzx rax,byte ptr [rdi+rcx]  ;将输入的字符放入rax
    inc byte ptr [rbp-256+rax]  ;新增该字符的计数
    inc rcx 
    cmp rcx,rsi  ;rsi为字符串长度
    jl count_bytes
initial_maxfreq_and_byte:
    xor rcx,rcx ;索引同时最低位为字节   
    xor rdx,rdx ;频率
    xor rbx,rbx ;存储字节
find_most_common_byte:
    movzx rax,byte ptr [rbp-256+rcx]
    cmp al,dl 
    jle next_byte 
new_max:
    mov dl,al ;更新最大频率
    mov bl,cl ;更新字节
next_byte:
    inc rcx 
    cmp rcx,0x100 
    jl find_most_common_byte 
return:
    mov al,bl ;返回结果,最终结果写入al
restore:
    mov rsp,rbp 
    pop rbp 
    ret ;恢复前一个栈帧
""")
print(VIO_TEXT(code))
p = process('/challenge/run')
p.send(code)
p.interactive()

gdb

level 4

image

程序在main+626处进行比较

而我们

image

在这之前取出的$rbp-0x18是比较的值,我们打好断点在输入前然后取出进行输入即可

  • 随机值存储[rbp-0x18]
  • 用户输入存储[rbp-0x10]
  • 循环计数器[rbp-0x1c](0-3)

level5

shit题

image

我们可能要用到这些文档

我们把challenge文件dump下来,IDA分析

根据题目的提示,我们需要将断点下载合适的地方,并设置需要的值后继续操作

image

level8

image

如果+12的地方执行了的话,那么rax=0,从而之后会触发segment fault

因此需要跳过

1
2
3
4
r
x/40i win
set $rip=win+35
c

webserver

使用方法

image

exit

我们运行的时候会给出提示

image

可以根据要求创建可执行文件,也可以直接使用pwntools

 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
from pwn import *
import os

context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
    mov rdi, 0
    mov rax, 60
    syscall
""")

print(VIO_TEXT("生成的机器码:"))
print(code)

# 创建可执行文件
elf = make_elf(code)

# 保存为server文件
with open('server', 'wb') as f:
    f.write(elf)

# 给执行权限
os.chmod('server', 0o755) #0o=八进制

p = process(['/challenge/run', './server'])
p.interactive()

socket

简单理解的话socket就是一个为了让用户可以与内核层进行交互的中间层,将套接字sock得以迁入文件系统,用户在空间中使用文件句柄socket_fd来操作内核sock,实现网络传输功能

其结构为socket(AF_INET, SOCK_STREAM, 0)

我们要在/usr/include下找到对应文件中常量的值

1
2
3
4
5
# 查找 AF_INET 的值
grep -r "AF_INET" /usr/include/ | grep "#define"  # 2

# 查找 socket 系统调用号
grep -r "__NR_socket" /usr/include/

typeSOCK_STREAM定义为TCP

image

image

 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 os

context(arch='amd64', os='linux')

# 创建socket的汇编代码
assembly = """
    /* socket(AF_INET, SOCK_STREAM, 0) */
    mov rdi, 2      /* AF_INET = 2 */
    mov rsi, 1      /* SOCK_STREAM = 1 */
    mov rdx, 0      /* protocol = 0 */
    mov rax, 41     /* SYS_socket = 41 */
    syscall
    
    /* exit(0) */
    mov rdi, 0
    mov rax, 60
    syscall
"""

# 编译并创建可执行文件
shellcode = asm(assembly)
elf = make_elf(shellcode)

with open('server', 'wb') as f:
    f.write(elf)

os.chmod('server', 0o755)

print("Socket服务器已创建,运行挑战...")
p = process(['/challenge/run', './server'])
p.interactive()

bind

创建好socket层之后,下一步通过bind 系统调用去将socket绑定到特定的IP地址和端口,我们需要提供

image

1
2
3
4
5
6
7
8
9
/* Get the definition of the macro to define the common sockaddr members.  */
#include <bits/sockaddr.h>

/* Structure describing a generic socket address.  */
struct __attribute_struct_may_alias__ sockaddr
  {
    __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
    char sa_data[14];		/* Address data.  */
  };

初始代码

 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 *
import os

context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
    /* socket */
    mov rdi,2 
    mov rsi,1 
    mov rdx,0 
    mov rax,41
    syscall  
        
    /* socket fd */
    mov r12,rax 

    /*struct sockaddr_in 8080=0x1f90,*/
    sub rsp,16 

    mov word ptr [rsp],2 #AF_INET
    mov word ptr [rsp+2],0x901f
    mov dword ptr [rsp+4],0 
    mov qword ptr [rsp+8],0
    
    /* bind function */
    mov rdi,r12 
    mov rsi,rsp 
    mov rdx,16 
    mov rax,49 
    syscall


    /*exit*/
    mov rdi,0 
    mov rax,60 
    syscall 
""")

print(VIO_TEXT("生成的机器码:"))
print(code)

# 创建可执行文件
elf = make_elf(code)

# 保存为server文件
with open('server', 'wb') as f:
    f.write(elf)

# 给执行权限
os.chmod('server', 0o755)

p = process(['/challenge/run', './server'])
p.interactive()

image

发现绑定的端口出现了错误

image

修改

mov word ptr [rsp+2],0x901fmov word ptr [rsp+2],0x5000即可(网络字节序为大端)

Listen

1
int listen(int sockfd, int backlog);

sockfd为socket文件句柄,backlog为等待连接队列的最大长度

 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
code = asm("""
    /* socket */
    mov rdi,2 
    mov rsi,1 
    mov rdx,0 
    mov rax,41
    syscall  
        
    /* socket fd */
    mov r12,rax 

    /*struct sockaddr_in 80=0x5000,*/
    sub rsp,16 

    mov word ptr [rsp],2 #AF_INET
    mov word ptr [rsp+2],0x5000
    mov dword ptr [rsp+4],0 
    mov qword ptr [rsp+8],0
    
    /* bind */
    mov rdi,r12 
    mov rsi,rsp 
    mov rdx,16 
    mov rax,49 
    syscall

    /* listen */
    mov rdi,r12 /*sockfd*/ 
    mov rsi,0 /*backlog=0*/ 
    mov rax,50 
    syscall

    /*exit*/
    mov rdi,0 
    mov rax,60 
    syscall 
""")

其他部分不变,添加listen

accept

1
2
 int accept(int sockfd, struct sockaddr *_Nullable restrict addr,
  socklen_t *_Nullable restrict addrlen);

grep -r "__NR_accept" /usr/include/-43

添加

1
2
3
4
5
6
7
    /* accept */
    /*accpet(sockfd, &client_addr, &addr_len)*/
    mov rdi,r12 
    mov rsi,0
    mov rdx,0
    mov rax,43 
    syscall 

static response

本来尝试加入了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    /* accept */
    /*accpet(sockfd, &client_addr, &addr_len)*/
    mov rdi,r12 
    mov rsi,0
    mov rdx,0
    mov rax,43 
    syscall 
    
    mov r13,rax /*socket fd from client*/ 

    /* ready for http response */ 
    sub rsp,32 
    
    /* read for bytes to write */ 
    mov qword ptr [rsp],0x0a0d0a0d4b4f2030 
    mov qword ptr [rsp+8],0x312e302f50545448 
    mov byte ptr [rsp+16],0 

    /* send http */ 
    mov rdi,r13  /* client fd */ 
    mov rsi,rsp  /* pointer to buf*/ 
    mov rdx,17 
    mov rax,1 
    syscall 

结果发现解析失败了??

不允许mov qword ptr 8字节数据的用法

于是修改,注意还要加入read(fd,buffer,len)

 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
    /* read */ 
    sub rsp, 0x100
    mov rdi, r13
    mov rsi, rsp
    mov rdx, 0x100
    xor rax, rax
    syscall

    /* bytes to write */ 
    sub rsp, 32
    mov dword ptr [rsp],     0x50545448
    mov dword ptr [rsp+4],   0x302e312f
    mov dword ptr [rsp+8],   0x30303220
    mov dword ptr [rsp+12],  0x0d4b4f20
    mov word  ptr [rsp+16],  0x0d0a
    mov byte  ptr [rsp+18],  0x0a
    
    /* send http */ 
    mov rdi,r13  /* client fd */ 
    mov rsi,rsp  /* pointer to buf*/ 
    mov rdx,19 
    mov rax,1 
    syscall 

    /*close*/ 
    mov rdi,r13 
    mov rax,3 
    syscall

dynamic response

会稍微复杂一些

  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
from pwn import *
import os

context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
    /* socket */
    mov rdi,2 
    mov rsi,1 
    mov rdx,0 
    mov rax,41
    syscall  
        
    /* socket fd */
    mov r12,rax 

    /*struct sockaddr_in 80=0x5000,*/
    sub rsp,16 

    mov word ptr [rsp],2 #AF_INET
    mov word ptr [rsp+2],0x5000
    mov dword ptr [rsp+4],0 
    mov qword ptr [rsp+8],0
    
    /* bind */
    mov rdi,r12 
    mov rsi,rsp 
    mov rdx,16 /*addrlen*/
    mov rax,49 
    syscall

    /* listen */
    mov rdi,r12 /*sockfd*/ 
    mov rsi,0 /* backlog=0*/ 
    mov rax,50 
    syscall

    /* accept */
    /*accpet(sockfd, &client_addr, &addr_len)*/
    mov rdi,r12 
    mov rsi,0
    mov rdx,0
    mov rax,43 
    syscall 
    
    mov r13,rax /*socket fd from client*/ 

    /* read buffer */ 
    sub rsp, 0x100
    mov rdi, r13
    mov rsi, rsp
    mov rdx, 0x100
    xor rax, rax
    syscall

    /* open file and read*/ 
    lea rbx,[rsp+4]
    find_space:
      cmp byte ptr [rbx],0x20
      je found_space 
      inc rbx 
      jmp find_space 
    found_space:
      mov byte ptr [rbx],0
    lea rdi,[rsp+4] 
    xor rsi,rsi 
    mov rax,2 
    syscall 
    mov r8,rax 

    sub rsp,0x200 
    mov rbx,rsp 

    mov rdi,r8
    mov rsi,rbx
    mov rdx,0x200 
    xor rax,rax 
    syscall 
    mov r15,rax  /*save the length*/

    /*close file*/ 
    mov rdi,r8
    mov rax,3 
    syscall 

    /* bytes to write's head of http */ 
    sub rsp, 32
    mov dword ptr [rsp],     0x50545448
    mov dword ptr [rsp+4],   0x302e312f
    mov dword ptr [rsp+8],   0x30303220
    mov dword ptr [rsp+12],  0x0d4b4f20
    mov word  ptr [rsp+16],  0x0d0a
    mov byte  ptr [rsp+18],  0x0a

    /* send http */ 
    mov rdi,r13  /* client fd */ 
    mov rsi,rsp  /* pointer to buf*/ 
    mov rdx,19 
    mov rax,1 
    syscall 

    /* write file's content to client*/ 
    mov rdi,r13 
    mov rsi,rbx /*上面mov rsi,rbx将内容留存在了rbx当时的地址中*/
    mov rdx,r15 
    mov rax,1 
    syscall

    /*close client */ 
    mov rdi,r13 
    mov rax,3 
    syscall
    
    /* exit */
    mov rdi,0 
    mov rax,60 
    syscall 
""")

print(VIO_TEXT("生成的机器码:"))
print(code)

# 创建可执行文件
elf = make_elf(code)

# 保存为server文件
with open('server', 'wb') as f:
    f.write(elf)

# 给执行权限
os.chmod('server', 0o755)

p = process(['/challenge/run', './server'])
p.interactive()

可迭代的(Iterative)GET Server

添加循环处理GET逻辑

  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
    /* socket */
    mov rdi,2 
    mov rsi,1 
    mov rdx,0 
    mov rax,41
    syscall  
        
    /* socket fd */
    mov r12,rax 

    /*struct sockaddr_in 80=0x5000,*/
    sub rsp,16 

    mov word ptr [rsp],2 #AF_INET
    mov word ptr [rsp+2],0x5000
    mov dword ptr [rsp+4],0 
    mov qword ptr [rsp+8],0
    
    /* bind */
    mov rdi,r12 
    mov rsi,rsp 
    mov rdx,16 /*addrlen*/
    mov rax,49 
    syscall

    /* listen */
    mov rdi,r12 /*sockfd*/ 
    mov rsi,0 /* backlog=0*/ 
    mov rax,50 
    syscall

.server_loop:
    /* accept */
    /*accpet(sockfd, &client_addr, &addr_len)*/
    mov rdi,r12 
    mov rsi,0
    mov rdx,0
    mov rax,43 
    syscall 
    
    mov r13,rax /*socket fd from client*/ 

    /* read buffer */ 
    sub rsp, 0x100
    mov rdi, r13
    mov rsi, rsp
    mov rdx, 0x100
    xor rax, rax
    syscall

    /* open file and read*/ 
    lea rbx,[rsp+4]
    find_space:
      cmp byte ptr [rbx],0x20
      je found_space 
      inc rbx 
      jmp find_space 
    found_space:
      mov byte ptr [rbx],0
    lea rdi,[rsp+4] 
    xor rsi,rsi 
    mov rax,2 
    syscall 
    mov r8,rax 

    sub rsp,0x200 
    mov rbx,rsp 

    mov rdi,r8
    mov rsi,rbx
    mov rdx,0x200 
    xor rax,rax 
    syscall 
    mov r15,rax  /*save the length*/

    /*close file*/ 
    mov rdi,r8
    mov rax,3 
    syscall 

    /* bytes to write's head of http */ 
    sub rsp, 32
    mov dword ptr [rsp],     0x50545448
    mov dword ptr [rsp+4],   0x302e312f
    mov dword ptr [rsp+8],   0x30303220
    mov dword ptr [rsp+12],  0x0d4b4f20
    mov word  ptr [rsp+16],  0x0d0a
    mov byte  ptr [rsp+18],  0x0a

    /* send http */ 
    mov rdi,r13  /* client fd */ 
    mov rsi,rsp  /* pointer to buf*/ 
    mov rdx,19 
    mov rax,1 
    syscall 

    /* write file's content to client*/ 
    mov rdi,r13 
    mov rsi,rbx /*上面mov rsi,rbx将内容留存在了rbx当时的地址中*/
    mov rdx,r15 
    mov rax,1 
    syscall

    /*close client */ 
    mov rdi,r13 
    mov rax,3 
    syscall    
    
    jmp .server_loop
.exit:
    /* exit */
    mov rdi,0 
    mov rax,60 
    syscall

concurrent GET Server

在原本的基础上使用fork子进程来实现执行GET操作,批注写在代码里了

将前一段的代码进行分段修改后,如果成功fork了进程则进入进行操作,否则直接server进行close

  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
    /* socket */
    mov rdi,2 
    mov rsi,1 
    mov rdx,0 
    mov rax,41
    syscall  
        
    /* socket fd */
    mov r12,rax 

    /*struct sockaddr_in 80=0x5000,*/
    sub rsp,16 

    mov word ptr [rsp],2 #AF_INET
    mov word ptr [rsp+2],0x5000
    mov dword ptr [rsp+4],0 
    mov qword ptr [rsp+8],0
    
    /* bind */
    mov rdi,r12 
    mov rsi,rsp 
    mov rdx,16 /*addrlen*/
    mov rax,49 
    syscall

    /* listen */
    mov rdi,r12 /*sockfd*/ 
    mov rsi,0 /* backlog=0*/ 
    mov rax,50 
    syscall

server_loop:
    /* accept */
    /*accpet(sockfd, &client_addr, &addr_len)*/
    mov rdi,r12 
    mov rsi,0
    mov rdx,0
    mov rax,43 
    syscall 
    mov r13,rax /*socket fd from client*/ 

    /*fork*/
    mov rax,57 
    syscall 

    test rax,rax 
    jz child_process 
    jmp parent_process

child_process:
    mov rdi,r12 
    mov rax,3 
    syscall 

    /* read buffer */ 
    sub rsp, 0x100
    mov rdi, r13
    mov rsi, rsp
    mov rdx, 0x100
    xor rax, rax
    syscall

    /* open file and read*/ 
    lea rbx,[rsp+4]
    find_space:
      cmp byte ptr [rbx],0x20
      je found_space 
      inc rbx 
      jmp find_space 
    found_space:
      mov byte ptr [rbx],0
    lea rdi,[rsp+4] 
    xor rsi,rsi 
    mov rax,2 
    syscall 
    mov r8,rax 

    sub rsp,0x200 
    mov rbx,rsp 

    mov rdi,r8
    mov rsi,rbx
    mov rdx,0x200 
    xor rax,rax 
    syscall 
    mov r15,rax  /*save the length*/

    /*close file*/ 
    mov rdi,r8
    mov rax,3 
    syscall 

    /* bytes to write head of http */ 
    sub rsp, 32
    mov dword ptr [rsp],     0x50545448
    mov dword ptr [rsp+4],   0x302e312f
    mov dword ptr [rsp+8],   0x30303220
    mov dword ptr [rsp+12],  0x0d4b4f20
    mov word  ptr [rsp+16],  0x0d0a
    mov byte  ptr [rsp+18],  0x0a

    /* send http */ 
    mov rdi,r13  /* client fd */ 
    mov rsi,rsp  /* pointer to buf*/ 
    mov rdx,19 
    mov rax,1 
    syscall 

    /* write file content to client*/ 
    mov rdi,r13 
    mov rsi,rbx /*上面mov rsi,rbx将内容留存在了rbx当时的地址中*/
    mov rdx,r15 
    mov rax,1 
    syscall

    /*close client */ 
    mov rdi,r13 
    mov rax,3 
    syscall    
    
    /* exit child_porcess*/ 
    mov rdi,0 
    mov rax,60 
    syscall 
parent_process:
    mov rdi,r13 
    mov rax,3 
    syscall 

    jmp server_loop 

检测的时候分为对parent process和child process的检测(卧槽了这挑战检测的代码是怎么用python写的,好牛逼)

image

image

concurrent POST Server

but this time instead of reading from that file, you will instead write to it with the incoming POST data. In order to do so, you must determine the length of the incoming POST data.

emm,原来POST的数据长度一定要写入请求是这个原因

image

首次测试,可以正常读入数据,但是传给open的句柄出错了

  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
code = asm("""
    /* socket */
    mov rdi,2 
    mov rsi,1 
    mov rdx,0 
    mov rax,41
    syscall  
        
    /* socket fd */
    mov r12,rax 

    /*struct sockaddr_in 80=0x5000,*/
    sub rsp,16 

    mov word ptr [rsp],2 #AF_INET
    mov word ptr [rsp+2],0x5000
    mov dword ptr [rsp+4],0 
    mov qword ptr [rsp+8],0
    
    /* bind */
    mov rdi,r12 
    mov rsi,rsp 
    mov rdx,16 /*addrlen*/
    mov rax,49 
    syscall

    /* listen */
    mov rdi,r12 /*sockfd*/ 
    mov rsi,0 /* backlog=0*/ 
    mov rax,50 
    syscall

server_loop:
    /* accept */
    /*accpet(sockfd, &client_addr, &addr_len)*/
    mov rdi,r12 
    mov rsi,0
    mov rdx,0
    mov rax,43 
    syscall 
    mov r13,rax /*socket fd from client*/ 

    /*fork*/
    mov rax,57 
    syscall 

    test rax,rax 
    jz child_process 
    jmp parent_process

child_process:
    /* close socket */
    mov rdi,r12 
    mov rax,3 
    syscall 
    
    /* read buffer*/
    sub rsp,0x400
    mov rdi,r13 
    mov rsi,rsp 
    mov rdx,0x400 /*1024 most*/ 
    xor rax,rax
    syscall 

    /* read bytes length */ 
    mov r14,rax 
    
    /* find the path */ 
    lea rbx,[rsp+5] /*skip POST*/ 
    mov r15,rbx

find_space: 
    cmp byte ptr [rbx],0x20 
    je found_space 
    inc rbx 
    jmp find_space 

found_space: 
    /* if found,then analysis what is the text */ 
    mov byte ptr [rbx],0 

    /* open it as writing mode */ 
    mov rdi,r15
    mov rsi,0x41 /* O_WRONLY|O_CREAT*/ 
    mov rdx,0x1ff /*0777*/ 
    mov rax,2 
    syscall 
    /* save file fd to r8*/ 
    mov r8,rax 

    /* find the body after the \r\n\r\n */
    lea rdi,[rsp] 
    mov rcx,r14 
    mov eax,0x0a0d0a0d 
    

find_body: 
    cmp dword ptr [rdi],eax 
    je found_body 
    inc rdi 
    loop find_body 
    jmp close_file  

found_body: 
    add rdi,4 
    mov r15,rdi 

    lea rax,[rsp] 
    add rax,r14 
    sub rax,r15 /*sub length of request body*/ 
    mov rcx,rax /*save length*/

   /* write the POST data in */ 
    mov rdi,r8
    mov rsi,r15 
    mov rdx,rcx 
    mov rax,1 
    syscall 

close_file:
    mov rdi,r8
    mov rax,3 
    syscall

send_200:

    /* bytes to write's head of http */ 
    lea rsi,[rsp+0x40] 
    mov dword ptr [rsi],     0x50545448
    mov dword ptr [rsi+4],   0x302e312f
    mov dword ptr [rsi+8],   0x30303220
    mov dword ptr [rsi+12],  0x0d4b4f20
    mov word  ptr [rsi+16],  0x0d0a
    mov byte  ptr [rsi+18],  0x0a

    /* send http */ 
    mov rdi,r13  /* client fd */ 
    mov rdx,19 
    mov rax,1 
    syscall 

    /*close client */ 
    mov rdi,r13 
    mov rax,3 
    syscall    
    
    /* exit child_porcess*/ 
    mov rdi,0 
    mov rax,60 
    syscall 

parent_process:
    mov rdi,r13 
    mov rax,3 
    syscall 

    jmp server_loop 
""")

Web Server

为何一定要用汇编写,我思考了很久这个问题,得出的结果是牛魔的pwncollege想累死我,处理成两个分支,再加上了一些补充的点,这里的汇编全都是用的rsp缓冲区,不涉及栈空间

  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
262
263
from pwn import *
import os

context(log_level="debug", arch='amd64')


def VIO_TEXT(x, code=95):
    return f"\x1b[{code}m{x}\x1b[0m"


code = asm("""
    /* socket */
    mov rdi,2 
    mov rsi,1 
    mov rdx,0 
    mov rax,41
    syscall  
        
    /* socket fd */
    mov r12,rax 

    /*struct sockaddr_in 80=0x5000,*/
    sub rsp,16 

    mov word ptr [rsp],2 #AF_INET
    mov word ptr [rsp+2],0x5000
    mov dword ptr [rsp+4],0 
    mov qword ptr [rsp+8],0
    
    /* bind */
    mov rdi,r12 
    mov rsi,rsp 
    mov rdx,16 /*addrlen*/
    mov rax,49 
    syscall

    /* listen */
    mov rdi,r12 /*sockfd*/ 
    mov rsi,0 /* backlog=0*/ 
    mov rax,50 
    syscall

server_loop:
    /* accept */
    /*accpet(sockfd, &client_addr, &addr_len)*/
    mov rdi,r12 
    mov rsi,0
    mov rdx,0
    mov rax,43 
    syscall 
    mov r13,rax /*socket fd from client*/ 

    /*fork*/
    mov rax,57 
    syscall 

    test rax,rax 
    jz child_process 
    jmp parent_process

child_process:
    /* close server socket in child */
    mov rdi,r12 
    mov rax,3 
    syscall 
    
    /* read buffer*/
    sub rsp,0x400
    mov rdi,r13 
    mov rsi,rsp 
    mov rdx,0x400 /*1024 most*/ 
    xor rax,rax
    syscall 

    /* read bytes length */ 
    mov r14,rax 
    
    /* Check request type: GET or POST */
    mov al, byte ptr [rsp]  /* First character */
    cmp al, 'G'
    je handle_get
    cmp al, 'P'  
    je handle_post
    jmp send_404  /* Unknown method */

handle_get:
    /* GET request processing */
    /* find the path */ 
    lea rbx,[rsp+4] /*skip "GET "*/ 
    mov r15,rbx

find_space_get: 
    cmp byte ptr [rbx],0x20 
    je found_space_get 
    inc rbx 
    jmp find_space_get 

found_space_get: 
    mov byte ptr [rbx],0

    /* open file for reading */ 
    mov rdi,r15
    xor rsi,rsi  /* O_RDONLY */
    mov rax,2 
    syscall 
    
    cmp rax, 0
    jl send_404  /* File not found */
    
    mov r8,rax   /* save file fd */

    /* read file content */
    sub rsp,0x200 
    mov rbx,rsp 

    mov rdi,r8
    mov rsi,rbx
    mov rdx,0x200 
    xor rax,rax 
    syscall 
    mov r15,rax  /* save the length */

    /* close file */ 
    mov rdi,r8
    mov rax,3 
    syscall 

    jmp send_response

handle_post:
    /* POST request processing */
    /* find the path */ 
    lea rbx,[rsp+5] /*skip "POST "*/ 
    mov r15,rbx

find_space_post: 
    cmp byte ptr [rbx],0x20 
    je found_space_post 
    inc rbx 
    jmp find_space_post 

found_space_post: 
    mov byte ptr [rbx],0

    /* open file for writing (create if not exists) */ 
    mov rdi,r15
    mov rsi,0x41 /* O_WRONLY|O_CREAT */ 
    mov rdx,0x1ff /* 0777 */ 
    mov rax,2 
    syscall 
    mov r8,rax   /* save file fd */

    /* find the body after \r\n\r\n */
    lea rdi,[rsp] 
    mov rcx,r14 
    mov eax,0x0a0d0a0d  /* \r\n\r\n */

find_body: 
    cmp dword ptr [rdi],eax 
    je found_body 
    inc rdi 
    loop find_body 
    jmp close_file_post  /* No body found */

found_body: 
    add rdi,4 
    mov r15,rdi 

    lea rax,[rsp] 
    add rax,r14 
    sub rax,r15 /* body length */
    mov rcx,rax 

    /* write POST data to file */ 
    mov rdi,r8
    mov rsi,r15 
    mov rdx,rcx 
    mov rax,1 
    syscall 

close_file_post:
    mov rdi,r8
    mov rax,3 
    syscall
    
    /* For POST, we'll send success response but no file content */
    mov r15, 0  /* No content to send after headers */
    jmp send_response

send_404:
    /* Prepare 404 Not Found response */
    sub rsp, 32
    mov dword ptr [rsp],     0x50545448 
    mov dword ptr [rsp+4],   0x302e312f  
    mov dword ptr [rsp+8],   0x34303420 
    mov dword ptr [rsp+12],  0x0d464e20 
    mov word ptr [rsp+16],   0x0a0d      
    mov byte ptr [rsp+18],   0x0a        
    
    mov rdi, r13
    mov rsi, rsp
    mov rdx, 19
    mov rax, 1
    syscall
    
    jmp cleanup_child

send_response:
    /* Send HTTP 200 OK headers */
    sub rsp, 32
    mov dword ptr [rsp],     0x50545448  
    mov dword ptr [rsp+4],   0x302e312f  
    mov dword ptr [rsp+8],   0x30303220  
    mov dword ptr [rsp+12],  0x0d4b4f20 
    mov word ptr [rsp+16],   0x0a0d     
    mov byte ptr [rsp+18],   0x0a       

    /* Send headers */
    mov rdi, r13
    mov rsi, rsp
    mov rdx, 19
    mov rax, 1
    syscall

    /* If there's file content (GET request), send it */
    test r15, r15
    jz cleanup_child
    
    mov rdi, r13
    mov rsi, rbx  /* File content buffer */
    mov rdx, r15  /* Content length */
    mov rax, 1
    syscall

cleanup_child:
    /* Close client socket */ 
    mov rdi, r13 
    mov rax, 3 
    syscall    
    
    mov rdi, 0 
    mov rax, 60 
    syscall 

parent_process:
    mov rdi, r13 
    mov rax, 3 
    syscall 
    jmp server_loop 
""")

print(VIO_TEXT("生成的机器码:"))
print(code)

elf = make_elf(code)

with open('server', 'wb') as f:
    f.write(elf)

os.chmod('server', 0o755)

p = process(['/challenge/run', './server'])
p.interactive()

image

emmm,虽然成功了但是为何test都是?

这也是最后有点困惑的地方

本博客已稳定运行
发表了53篇文章 · 总计20万5千字

浙ICP备2024137952号 『网站统计』

𝓌𝒶𝒾𝓉 𝒻ℴ𝓇 𝒶 𝒹ℯ𝓁𝒾𝓋ℯ𝓇𝒶𝓃𝒸ℯ
使用 Hugo 构建
主题 StackJimmy 设计
⬆️该页面访问量Loading...