作者 | 绿盟科技格物实验室 李东宏

前言

电缆调制解调器和数字电视调谐器从根本上说做了同样的事情—接收和解调QAM信号,因此萌生了一种想法,是否有可能将其变成一个SDR(软件定义无线电)?电缆调制解调器支持5到1794 MHz的频率范围(具有特定的发送和接收频率范围,并取决于DOCSIS的产生),使用二次振幅调制(QAM)和二次相移键控(QPSK),默认通道大小为6 MHz。将电缆调制解调器转换为SDR可能会涉及到一些深层的硬件修改,以便为每种模式使用适当的调制和带宽,并绕过大多数数字信号转换内容。本文将以Motorola MB7220为例进行介绍。

获得访问权限

拿到设备的第一个目标就是获得访问权限,寻找访问媒介或与设备进行通信的方式,通过对WEB的分析,没有发现什么可用的内容,并且telnet也被禁用了。看来只能从硬件接口(UART)着手,从塑料外壳上卸下几颗螺钉,打开后即可看到电路板。通过观察,在确定了由四个通孔组成的两个候选接口后可以通过如下方法判定各个引脚的接口定义。1. 将万用表功能开关定位到二极管(蜂鸣)档位,关闭设备电源,用黑色探头接到硬件电路板的接地引脚(这个引脚一般于较大的铜箔面相连)。2. 用红色探头分别放在可能是UART接口的每个焊盘或者针脚上,直到听到蜂鸣器 (BEEP)的声音。3. 听到蜂鸣器声音时即可判定设备接口的接地引脚,这种测试方法也称为通断测试。4. 将万用表功能开关定位20V直流电压档位置,黑色探头保持接口的接地引脚(GND),用红色探头移到接口的其他引脚(GND除外)上。接通设备电源,如果看到恒定电压(3.3V或者5V)的引脚就是VCC引脚。5. 再次重启设备并测量剩余焊盘和GND之间的电压(除了前面步骤中确定的VCC和GND)。由于在启动期间最初进行的大量数据传输,可以看到在最初的10-15秒内其中一个引脚上的电压值发生了巨大波动。这个引脚将是TXD引脚。6. RXD可以通过在整个过程中具有最低电压波动和最低总值的引脚来确定。第一个UART接口似乎没有发送任何东西,而另一个则会进行数据发送,将Tx连接到树莓派上的UART Rx GPIO引脚,Rx连接到树莓派上的UART Tx GPIO引脚并连接好接地引脚。(注意:由于两个系统均为3.3V才可以这样做,如果两个系统的电压不同,则需要使用相关的适配器)。根据经验,115200的波特率在设备中比较常见,于是使用这个波特率进行连接(如果波特率错误,则会在屏幕上显示乱码,需要重新调整波特率,然后再进行链接),在树莓派上执行命令并将设备上电,终端显示如下信息:

pi@raspberrypi:~/modem $ cu -l /dev/serial0 -s 115200Connected.B3312inim S C 84(9 mose_VS 8STesldlo rh 83 rs 10STesldhi: _h 8, _s 13Sync: 0 MemSize:            128 MChip ID:     BCM3383D-B0BootLoader Version: 2.4.0 fyl spiboot reduced DDR drive avsBuild Date: Nov 12 2015Build Time: 14:31:43SPI flash ID 0xef4016, size 4MB, block size 64KB, write buffer 256, flags 0x0Cust key size 128Signature/PID: 3383Image 1 Program Header:   Signature: 3383     Control: 0005   Major Rev: 0003   Minor Rev: 0000  Build Time: 2015/11/26 08:47:57 Z File Length: 1692841 bytesLoad Address: 80004000    Filename: ecram_sto.bin         HCS: e749         CRC: 175b753fFound image 1 at offset 20000Enter '1', '2', or 'p' within 2 seconds or take default...Performing CRC on Image 1...CRC time = 282177012Detected LZMA compressed image... decompressing... Target Address: 0x80004000decompressSpace is 0x8000000Elapsed time 736066500Decompressed length: 8091524Executing Image 1... eCos - hal_diag_initEcos memory map:BLOCK    OWNER        MIPS      SIZE      MEMBlock 0: Owner: 0 - 0x00000000 0x07e00000 0x00000000Block 0: Owner: 0 - 0 MB 126 MB 0 MBBlock 1: Owner: 3 - 0x07e00000 0x00200000 0x07e00000Block 1: Owner: 3 - 126 MB 2 MB 126 MB126MB (129024KB) remaining for eCosInit device '/dev/BrcmTelnetIoDriver'Init device '/dev/ttydiag'Init tty channel: 807bb020Init device '/dev/tty0'Init tty channel: 807bb040Init device '/dev/haldiag'HAL/diag SERIAL initInit device '/dev/ser0'BCM 33XX SERIAL init - dev: b4e00500.2Set output buffer - buf: 0x80852408 len: 4096Set input buffer - buf: 0x80853408 len: 4096BCM 33XX SERIAL configInit device '/dev/ser1'BCM 33XX SERIAL init - dev: b4e00520.3Set output buffer - buf: 0x80854408 len: 4096Set input buffer - buf: 0x80855408 len: 4096BCM 33XX SERIAL configInit device '/dev/ser2'InitBoard: MIPS frequency 637200000...Reading Permanent settings from non-vol...Checksum for permanent settings:  0xe9d88f65Setting downstream calibration signature to '5.7.1mp1|die temperature:70.775degC'Settings were read and verified.Reading Dynamic settings from non-vol...Checksum for dynamic settings:  0x6e4a329Settings were read and verified.Console input has been disabled in non-vol.Console output has been disabled in non-vol!  Goodbye...[00:00:00 01/01/1970] [Reset/Standby Switch Thread] BcmResetStandbySwitchThread::ProcessResetSwitchEvent:  (Reset/Standby Switch Thread) Reset switch released; resetting...[00:00:00 01/01/1970] [Reset/Standby Switch Thread] BcmResetStandbySwitchThread::ProcessResetSwitchEvent:  (Reset/Standby Switch Thread) Cant Reset pfCmDocsisCtlThread==NULL...

此输出包含大量信息,从输出的信息发现设备运行了两个MIPS处理器,其中一个是博通BCM3383的SoC,运行的系统为eCos系统,而另一个未在调制解调器上使用。在某些设备上,第二个处理器将运行Linux以获得其他功能。由于操作系统在启动不久后便会禁用串行控制台,在bootloader模式下,除了可以通过tftp下载新OS镜像以及用于读取和写入内存地址的实用程序外,并不能做其他多余的操作。

从芯片中获取固件

当前的目标是启用串行控制台,这部分的参数很可能存储在引导加载程序、操作系统或配置中,这些配置数据一般是放在一个外置的存储芯片中,通过对板卡电路的分析发现了如下芯片(winbond 25Q32JV):

通过相关芯片资料可以知道芯片采用了SPI接口以及相关的管脚定义,主要的SPI管脚为VCC,片选(CS),时钟(CLK),数据输出(DO),数据输入(DI)和地。由于树莓派也存在一个SPI的控制端口,可以从芯片中读取数据,于是将导线焊接到其引脚上,并将它们连接到树莓派。地线接地(也可以使用更早的UART地线),VCC到树莓派的3.3v引脚,DO引脚连接到树莓派的SPI MISO(主机输入从机输出)引脚,DI引脚连接到MOSI引脚(主机输出从机输入)。最后,时钟连接到SCLK GPIO引脚,芯片选择连接到该CE0引脚。

要真正读取芯片,有一个很棒的工具叫做 flashrom,它支持大量芯片,并存在于树莓派的发行版本中。

通过如下命令可验证是否已检测到正确的接线。

flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=2000 --chip W25Q32.V

如果成功了则可以进行固件转储。

flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=2000 --chip W25Q32.V --read modem.bin

固件数据分析

快速浏览十六进制转储,可以看到大多数数据已压缩或加密,但是在最后,配置是可见的。

...003f00c0: ffff ffff ffff ffff ffff 0000 07b0 369a  ..............6.003f00d0: 6336 0010 434d 4170 0002 0000 0002 0000  c6..CMAp........003f00e0: 0000 0057 4d4c 6f67 0005 0004 7573 6572  ...WMLog....user003f00f0: 0004 7573 6572 0005 6164 6d69 6e00 086d  ..user..admin..m003f0100: 6f74 6f72 6f6c 6102 7465 6368 6e69 6369  otorola.technici...

Web界面凭据以及许多其他编码的配置值清晰可见。经过一番搜索,找到了一个名为bcm2-utils的伟大项目 ,其中包含用于转储,解析和修改Broadcom电缆调制解调器上配置的实用程序。配置的开始位置在设备上的0x003f0000处,包括202 0xff字节,提取配置后,我可以使用bcm2cfg进行读写配置。使用如下命令开启与telnet类似的串行控制台并设置密码。

$ ./bcm2-utils/bcm2cfg set bfc.serial_console_mode "rw"bfc.serial_console_mode = rw$ ./bcm2-utils/bcm2cfg set userif.remote_acc_methods 0x3userif.remote_acc_methods = http | telnet$ ./bcm2-utils/bcm2cfg set userif.remote_acc_pass abcduserif.remote_acc_pass = abcd

将修改后的文件前端用零填充,使配置信息位于0x003f0000处,然后用flashrom将配置写回到芯片上。为了避免重写整个芯片,需要创建一个布局文件,内容如下:

00000000:003effff fw003f0000:003fffff cfg

使用如下命令进行配置更新。

flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=2000 --chip W25Q32.V --layout ./layout  --image cfg  --write modem-modified.bin

打开串行控制台并再次启动后,显示和以前同样的信息,检查固件发现在初始配置之后有许多重复的配置副本。有部分内容是不同的,其中最主要的就是错误日志消息。为简化起见,通过Web界面进行了出厂重置,以消除所有错误日志消息。然后,再次转储闪存,并重复了之前的过程来修改干净的配置,只是这次把配置截断,仅包含第一个副本。然后使用dd命令,重新构造了整个配置部分,将修改后的config附加到配置副本开始的偏移处。重新刷新镜像并再次引导后终于能够查看整个引导日志,然后可以访问控制台。

...Reading Permanent settings from non-vol...Checksum for permanent settings:  0xe9d88f65Setting downstream calibration signature to '5.7.1mp1|die temperature:70.775degC^@^@^@^@^@'Settings were read and verified.Reading Dynamic settings from non-vol...Checksum for dynamic settings:  0x2630e508Settings were read and verified.[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Setting FPM Buffer size to: 256 Base Address: 0x87566600[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) fFpmLargestBufferSize: 2048 fFpmSizeShiftBits: 0x8[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Pool index: 0  pool size: 2048[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Pool index: 1  pool size: 1024[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Pool index: 2  pool size: 512[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Pool index: 3  pool size: 256[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Lookup table index: 0  pool size: 3...[00:00:18 01/01/1970] [Scan Downstream Thread] BcmGenericCmDownstreamScanThread::ThreadMain:  (Scan Downstream Thread) Scanning for a Downstream Channel...[00:00:18 01/01/1970] [Scan Downstream Thread] BcmGenericCmDownstreamScanThread::ScanStarting:  (Scan Downstream Thread) Scanning STD & HRC Annex B channel plan frequenciesResetting EnergyDetected to false.Forgetting energy frequency.Executing fast scan algorithm...Type 'help' or '?' for a list of commands...CM> Scanned 489000000 Hz...Scanned 495000000 Hz...Scanned 501000000 Hz...Scanned 507000000 Hz...Scanned 513000000 Hz...

eCos控制台

eCos控制台包含许多配置和调试命令。可以使用以下命令停止输出的过程:

cd cm_halscan_stop

附带说明一下,这些“目录”不是真正的文件系统,它们只是组织命令组的一种方式。使用telnet工具登录192.168.100.1,用户名为“ technical”,密码为配置修改时设定的密码。登陆以后发现进入的是一个受限的shell环境,但是可以使用su命令和密码来获得一个完整的shell环境。

逆向分析固件

根据系统启动日志可以知道操作系统位于0x20000并经过LZMA压缩。bcm2-utils工具中引用了一个外部工具库ProgramStore,用于提取操作系统镜像。通过如下命令可以提取并解压缩镜像。

./ProgramStore -f ./ecram_sto.bin -o decompressed_fw.bin -c 4 -x

现在可以使用启动日志中的基地址0x80004000,将其扔进Ghidra,并将架构设置为big endian MIPS,在Ghidra自动分析完成后即可进行进一步的工作了。操作系统之上有大量的Broadcom代码,这些代码都是用C++编写的。由于在函数调用和多态性方面添加了大量间接调用,这使得逆向分析变得非常困难。常常会看到类似如下的代码。

case 0x24:uVar23 = (**(code **)(*piParm1_00 + 0x1c))(piParm1_00);

针对此种情况,可以通过eCos控制台中的write_memory命令来完成。该call命令可用于调用包含未知对象的函数,然后read_memory可用于从已知位置检索指针。尝试调用某些函数时,会导致设备崩溃,经过仔细的分析发现它们接收了4个以上的参数,并使用t0,t1,t2和t3寄存器作为附加参数。调用约定是由ABI确定的,并且MIPS具有许多不同的ABI,Ghidra似乎不支持MIPS EABI,但在几个函数上手动设置参数寄存器并不太方便,参数似乎就是唯一的区别了。经过一段时间的研究后,将目光投向了频谱分析仪,发现了所有其他有用的功能,例如用于设置下行(接收)通道频率的功能、套接字/绑定/监听/发送/接收功能、线程创建功能以及用于读取和写入调谐器和LNA寄存器的功能。

突破

经过不断的实验分析,发现了一个控制台命令,可用来执行给定频率范围内的带宽测量。开始密切关注执行过程,以查看它对频率范围参数做了什么,发现它调用了一个非常熟悉的函数,这个函数与用于调整下行通道的函数几乎相同,但是设置频率的内存映射寄存器地址高于普通频道的地址。测量功能将目标缓冲区的物理地址写入一个内存映射的寄存器,然后在另一个寄存器中设置一个位并循环,直到再次将其取消设置为止。之后调用传入缓冲区地址的函数,该函数可能会计算FFT。计算完成后,另一个函数对缓冲区进行了一些处理,但其他方面则保持不变。将跳线插入同轴连接器以充当天线后,调用了bandpower函数,然后在目标缓冲区上执行read_memory操作。

CM> read_memory -n256 0x86fb3e8086fb3e80: 00 00 06 8c  00 3f fe 48  00 00 06 41  00 20 00 3d | .....?.H...A. .=86fb3e90: 00 00 08 56  00 20 02 11  00 00 0a b3  00 20 03 f2 | ...V. ....... ..86fb3ea0: 00 00 0a 50  00 20 04 84  00 00 06 61  00 20 03 d7 | ...P. .....a. ..86fb3eb0: 00 00 01 1d  00 20 02 da  00 1f fd f4  00 20 00 4d | ..... ....... .M86fb3ec0: 00 1f fd 11  00 3f fc 20  00 1f fb 95  00 3f fa ad | .....?. .....?..86fb3ed0: 00 1f fa 32  00 3f fd fc  00 1f fc a3  00 20 00 cb | ...2.?....... ..86fb3ee0: 00 00 01 97  00 3f fe b5  00 00 04 0f  00 3f fb 6a | .....?.......?.j86fb3ef0: 00 00 03 9f  00 3f fb d6  00 00 03 1d  00 3f fe 55 | .....?.......?.U86fb3f00: 00 00 02 f8  00 3f ff a9  00 00 02 ee  00 20 01 49 | .....?....... .I86fb3f10: 00 00 03 8f  00 20 04 87  00 00 03 94  00 20 05 09 | ..... ....... ..86fb3f20: 00 00 01 81  00 3f ff bb  00 1f ff 14  00 3f fa 97 | .....?.......?..86fb3f30: 00 1f fe 8d  00 3f fc 9d  00 1f ff 89  00 20 01 82 | .....?....... ..86fb3f40: 00 00 00 be  00 20 00 09  00 00 01 8f  00 3f fa 3a | ..... .......?.:86fb3f50: 00 00 01 78  00 3f fa 66  00 00 00 7b  00 20 01 35 | ...x.?.f...{. .586fb3f60: 00 1f ff 79  00 20 04 f6  00 1f fe e2  00 20 02 62 | ...y. ....... .b86fb3f70: 00 1f fd 93  00 3f ff 4d  00 1f fa ee  00 3f fe 16 | .....?.M.....?..

FFT之后处理数据的函数检查第一个32位word的0x00200000位是否为零,如果是,则丢弃数据的第一个字和最后一个字。假设该位表示样本是I或者Q值,并且如果第一个样本是Q,则它将从开头删除不匹配的Q,从结尾删除不匹配的I。

Case 1:                Case 2:Q IQ IQ IQ I           IQ IQ IQ IQ     |                      | do nothing     v                      v  IQ IQ IQ             IQ IQ IQ IQ

通过此种方法可以获得更多的数据进行分析。

实践分析

为了使分析变得更加容易,应该编写一个程序,在调制解调器上运行,以调用tune和bandpower功能,然后打开侦听套接字,并通过TCP连接将缓冲区的内容发送回去。为了使程序在加载到预定的存储位置时正常工作并确保入口点位于该地址,使用的段映射与功能函数映射如下:

memset = 0x80522d7c;memcpy = 0x80004f30;malloc = 0x80596998;printf = 0x8052b178;socket = 0x80332fd0;bind = 0x800ae7bc;listen = 0x80412ed4;accept = 0x80413118;send = 0x80413240;recv = 0x804134bc;tune_aux_channel = 0x80082108;SECTIONS{  . = 0x80810000;  .start : { *(.start) }  .text : { *(.text) }  .data : { *(.data) }  .rodata : { *(.rodata) }}

使用如下命令进行编译:

mips-linux-gcc measure.c \    -march=mips32 \    -mabi=eabi \    -msoft-float \    -mno-abicalls \    -fno-builtin     -nostdlib \    -nodefaultlibs \    -nostartfiles \    -T ./script.ld

MIPS CPU没有FPU,因此使用-msoft-float。当使用-mabi=eabi选项时需要配合使用-mno-abicalls选项。使用-fno-builtin选项防止编译器通过添加对函数的调用来优化某些部分,例如memcpy将导致未定义符号的调用 。-nostdlib和-nostartfiles防止编译器使用标准c库。使用objcopy可以从编译的elf中提取到需要关心的部分。

mips-linux-objcopy -O binary \    -j .start \    -j .text \    -j .data \    -j .rodata \    a.out bin

最后,编写了一个Python脚本,利用pexpect远程登录到调制解调器,使用write_memory命令将二进制文件写入目标地址。程序是通过call命令执行的。

为了查看是否可以接收FM广播,可以将其调谐到100MHz并获取数据。

使用numpy,scipy和matplotlib Python库,能够将数据解释为一个复杂的有价值的样本,计算FFT并将其绘制成图表,以查看具有明显峰值的带通滤波后的频谱。

将频谱移动到其中一个尖峰的中心,抽取频谱以隔离频率范围,并使用在网上找到的一种非常简单的复数值调频解调技术,可以清楚地看到广播的不同部分,包括19kHz导频。

优化

以每秒1500万个样本,每个样本占用8个字节的速度,不到一秒钟的数据可以存储在大约100MB的可用RAM中。一种明显的改进是在填充缓冲区后发送数据,然后捕获更多数据。根据处理时间和网络吞吐量计算得知两次捕获之间的间隔大约为11秒。通过实现一个新功能设置寄存器并启动捕获,从而将时间缩短至约5秒钟,从而消除了FFT计算和其他处理。在对未知寄存器值进行了一些实验之后,希望能找到一个会影响采样率的寄存器,将I和Q值限制为14个有效位的位。尽管它们每个样本仍占据8个字节,但这意味着可以将其中两个打包成一个32位字(需要注意ADC采样的位数)。编写另一个函数来确定它是否以I或Q值开头,然后遍历缓冲区,将每个I / Q对打包为单个整数并将其写入缓冲区中的下一个位置。

仅此一项并不能改善性能,但是仅通过获取第N个样本,就可以降低有效采样率,缩短处理时间并减少必须发回的字节数,从而大大提高了延迟。

使用双线程进行优化处理,一个线程将数据连续捕获到下一个可用缓冲区中,然后向另一个线程发出信号,表明已完成写入。第二个线程对数据进行打包,通过网络发送数据,然后发出可再次写入缓冲区的信号。

结论

本文为通过逆向分析将电缆调制解调器改装为SDR的一个初步实践,并不打算制作一个功能强悍的SDR,仅是对技术的一次挑战,希望能给后续有兴趣进行深入研究的同行一点思路上的引导,并用如下的内容结束本文:

“With so fewfirsts available in life, take those that present themselves and have a crack”

参考文章:https://stdw.github.io/cm-sdr/

原文来源:关键基础设施安全应急响应中心

为甚serve 修改dev不能跑_初探逆向将电缆调制解调器改装为SDR相关推荐

  1. mount修改/dev/shm的大小

    1.关于 /dev/shm 目录 目录路径 /dev/shm 目录位于 linux 系统的内存中,而不在磁盘里,所以它的效率非常高,其上级目录 /dev 主要是一些设备管理文件,例如磁盘.内存等. 容 ...

  2. 修改/dev/shm大小

    如何修改/dev/shm大小? /dev/shm在/etc/fstab中挂载,对应tmpfs,实际使用的是内存的空间.默认情况下,/dev/shm为物理内存大小的一半.因而,调整/dev/shm大小有 ...

  3. 命运神界服务器维护,开服不到30天就关服?命运神界主美发文 游戏正在修改不会跑路...

    原标题:开服不到30天就关服?命运神界主美发文 游戏正在修改不会跑路 就在不少玩家在为新活动"异国的幻梦"肝体力时,<命运神界:梦境链接>官方却在最近突然宣布了&quo ...

  4. 20145307陈俊达_安卓逆向分析_Xposed的hook技术研究

    20145307陈俊达_安卓逆向分析_Xposed的hook技术研究 引言 其实这份我早就想写了,xposed这个东西我在安卓SDK 4.4.4的时候就在玩了,root后安装架构,起初是为了实现一些屌 ...

  5. mysql修改字段占用磁盘_给数据减肥 让MySQL数据库跑的更快

    [IT168 专稿]在数据库优化工作中,使数据尽可能的小,使表在硬盘上占据的空间尽可能的小,这是最常用.也是最有效的手段之一.因为缩小数据,相对来说可以提高硬盘的读写速度,并且在查询过程中小表的内容处 ...

  6. ubuntu修改u盘权限_手把手教你使用U盘安装Ubuntu系统

    Linux一直以来都是比较小众的系统,特别是在国内,用户相对Windows来说,更是少,甚至给人一种高端,复杂的印象,不过这些年来,使用linux的人越来越多了,而Ubuntu作为Linux系统中用户 ...

  7. oracle 修改默认日期格式_查看MySQL查询计划的方法和格式

    查看MySQL的查询计划是分析查询的重要方法,可以通过使用EXPLAIN语句来确认优化器将采取哪种查询计划,是否与你的预期一致. 如何使用EXPLIAN?使用它有两种方式: 直接在查询语句之前直接加上 ...

  8. linux下修改/dev/shm tmpfs文件系统大小

    默认系统就会加载/dev/shm ,它就是所谓的tmpfs,有人说跟ramdisk(虚拟磁盘),但不一样.象虚拟磁盘一 样,tmpfs 可以使用您的 RAM,但它也可以使用您的交换分区来存储.而且传统 ...

  9. ubuntu修改登陆用户名称_修改ubuntu的用户名(注意用户名和主机名的区别)

    1.用户名是user,一个主机可以有多个主机; 主机名是 hostname,要修改,就去 /etc/hostname目录修改. 2.修改用户名: 比如我想把 用户名"sanshanxiash ...

最新文章

  1. 科学计算机怎么调亮度,LED显示器背光很刺眼怎么办?显示器刺眼如何设置?
  2. 可以改动的option组件_uni-app WebView 组件通信
  3. 关于内表数据汇总的一些算法
  4. Android游戏开发系统控件-CheckBox
  5. 石家庄计算机专接本学校有哪些,河北省内的专接本学校都有哪些?
  6. [032] 微信公众帐号开发教程第8篇-文本消息中使用网页超链接(转)
  7. PHP内核通用网站后台权限管理系统源码
  8. 常用网站URL规划分析
  9. android 调用系统自带文件管理器_编写使用Android 系统自带的文字转语音代码
  10. html软件dr,了解HTML锚点 - osc_mbqdr3w5的个人空间 - OSCHINA - 中文开源技术交流社区...
  11. hssfwork 导出excel 文件已损坏_C# NPOI 操作EXCEL文件的读取和导出
  12. python打印文档添加条码_12行代码教会你用python读excel文件,提取数据,生成条形码...
  13. Android+usb+spi,Android设备如何使用USB的硬件接口
  14. pandas - AttributeError: Series object has no attribute reshape
  15. Erlang_ets冷门函数fun2ms
  16. Win7 VNC远程连接Centos桌面
  17. word 添加批注 标题向右移动 解决方法
  18. 运行新项目的时候 出现 The type javax.servlet.http.HttpServletRequest cannot be resolved.
  19. 常用第三方包汇总(持续更新)
  20. NBOOT、EBOOT、UBOOT介绍

热门文章

  1. python逐行读取文本
  2. Git_学习_06_ 放弃本地修改
  3. CentOS之——CentOS7安装iptables防火墙
  4. 关于QQ群共享一百多K以上的文件上传失败的问题
  5. 通过windows系统封杀IP与端口
  6. php 开发 比 java 快_PHP 比 Java 的开发效率高在哪?
  7. ctypes python3_聊聊Python ctypes 模块
  8. Python爬无止境,获得王者荣耀全部高清皮肤
  9. 鸿蒙os电视是安卓,适配鸿蒙OS系统的机型又增加了!和安卓系统相比鸿蒙有哪些特点?...
  10. java phantomjd linux_linux安装phantomjs