Android 安全测试
文章Android Security Tools对1~4的使用有介绍,下面主要分析其源码实现。
public void onCreate(Bundle savedInstanceState) {
...
Map<String, PackageInfo> packages = new HashMap<String, PackageInfo>();
PackageManager pm = getPackageManager();
List<PackageInfo> l = pm.getInstalledPackages(0xFFFFFFFF);
...
函数getInstalledPackages(int)参数有如下取值:
flags | Value |
GET_ACTIVITIES | 0x00000001 |
GET_GIDS | 0x00000100 |
GET_CONFIGURATIONS | 0x00004000 |
GET_INSTRUMENTATION | 0x00000010 |
GET_PERMISSIONS | 0x00001000 |
GET_PROVIDERS | 0x00000008 |
GET_RECEIVERS | 0x00000002 |
GET_SERVICES | 0x00000004 |
GET_SIGNATURES | 0x00000040 |
GET_UNINSTALLED_PACKAGES | 0x00002000 |
为oxFFFFFFFF是由于Android此函数实现对flags取“&”,不能包括GET_META_DATA,可取512或14111。详见ApplicationPackageManager.java
@SuppressWarnings("unchecked")
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
return getInstalledPackages(flags, mContext.getUserId());
} /** @hide */
@Override
public List<PackageInfo> getInstalledPackages(int flags, int userId) {
try {
ParceledListSlice<PackageInfo> slice = mPM.getInstalledPackages(flags, userId);
return slice.getList();
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
@Override
public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) {
final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "get installed packages"); // writer
synchronized (mPackages) {
ArrayList<PackageInfo> list;
if (listUninstalled) {
list = new ArrayList<PackageInfo>(mSettings.mPackages.size());
for (PackageSetting ps : mSettings.mPackages.values()) {
PackageInfo pi;
if (ps.pkg != null) {
pi = generatePackageInfo(ps.pkg, flags, userId);
} else {
pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
}
if (pi != null) {
list.add(pi);
}
}
} else {
list = new ArrayList<PackageInfo>(mPackages.size());
for (PackageParser.Package p : mPackages.values()) {
PackageInfo pi = generatePackageInfo(p, flags, userId);
if (pi != null) {
list.add(pi);
}
}
} return new ParceledListSlice<PackageInfo>(list);
}
} PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
PackageInfo pi;
final PackageSetting ps = (PackageSetting) p.mExtras;
if (ps == null) {
return null;
}
final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
final PackageUserState state = ps.readUserState(userId);
return PackageParser.generatePackageInfo(p, gp.gids, flags,
ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions,
state, userId);
} private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags,
int userId) {
if (!sUserManager.exists(userId)) return null;
PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
PackageParser.Package pkg = ps.pkg;
if (pkg == null) {
if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) {
return null;
}
pkg = new PackageParser.Package(packageName);
pkg.applicationInfo.packageName = packageName;
pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
pkg.applicationInfo.sourceDir = ps.codePathString;
pkg.applicationInfo.dataDir =
getDataPathForPackage(packageName, 0).getPath();
pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
}
// pkg.mSetEnabled = ps.getEnabled(userId);
// pkg.mSetStopped = ps.getStopped(userId);
return generatePackageInfo(pkg, flags, userId);
}
return null;
}
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
HashSet<String> grantedPermissions, PackageUserState state, int userId) { if (!checkUseInstalled(flags, state)) {
return null;
}
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
pi.versionCode = p.mVersionCode;
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0
|| (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
pi.requiredForAllUsers = p.mRequiredForAllUsers;
}
pi.restrictedAccountType = p.mRestrictedAccountType;
pi.requiredAccountType = p.mRequiredAccountType;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
pi.gids = gids;
}
if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
int N = p.configPreferences.size();
if (N > 0) {
pi.configPreferences = new ConfigurationInfo[N];
p.configPreferences.toArray(pi.configPreferences);
}
N = p.reqFeatures != null ? p.reqFeatures.size() : 0;
if (N > 0) {
pi.reqFeatures = new FeatureInfo[N];
p.reqFeatures.toArray(pi.reqFeatures);
}
}
if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
int N = p.activities.size();
if (N > 0) {
if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.activities = new ActivityInfo[N];
} else {
int num = 0;
for (int i=0; i<N; i++) {
if (p.activities.get(i).info.enabled) num++;
}
pi.activities = new ActivityInfo[num];
}
for (int i=0, j=0; i<N; i++) {
final Activity activity = p.activities.get(i);
if (activity.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags,
state, userId);
}
}
}
}
if ((flags&PackageManager.GET_RECEIVERS) != 0) {
int N = p.receivers.size();
if (N > 0) {
if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.receivers = new ActivityInfo[N];
} else {
int num = 0;
for (int i=0; i<N; i++) {
if (p.receivers.get(i).info.enabled) num++;
}
pi.receivers = new ActivityInfo[num];
}
for (int i=0, j=0; i<N; i++) {
final Activity activity = p.receivers.get(i);
if (activity.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags,
state, userId);
}
}
}
}
if ((flags&PackageManager.GET_SERVICES) != 0) {
int N = p.services.size();
if (N > 0) {
if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.services = new ServiceInfo[N];
} else {
int num = 0;
for (int i=0; i<N; i++) {
if (p.services.get(i).info.enabled) num++;
}
pi.services = new ServiceInfo[num];
}
for (int i=0, j=0; i<N; i++) {
final Service service = p.services.get(i);
if (service.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.services[j++] = generateServiceInfo(p.services.get(i), flags,
state, userId);
}
}
}
}
if ((flags&PackageManager.GET_PROVIDERS) != 0) {
int N = p.providers.size();
if (N > 0) {
if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.providers = new ProviderInfo[N];
} else {
int num = 0;
for (int i=0; i<N; i++) {
if (p.providers.get(i).info.enabled) num++;
}
pi.providers = new ProviderInfo[num];
}
for (int i=0, j=0; i<N; i++) {
final Provider provider = p.providers.get(i);
if (provider.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags,
state, userId);
}
}
}
}
if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) {
int N = p.instrumentation.size();
if (N > 0) {
pi.instrumentation = new InstrumentationInfo[N];
for (int i=0; i<N; i++) {
pi.instrumentation[i] = generateInstrumentationInfo(
p.instrumentation.get(i), flags);
}
}
}
if ((flags&PackageManager.GET_PERMISSIONS) != 0) {
int N = p.permissions.size();
if (N > 0) {
pi.permissions = new PermissionInfo[N];
for (int i=0; i<N; i++) {
pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags);
}
}
N = p.requestedPermissions.size();
if (N > 0) {
pi.requestedPermissions = new String[N];
pi.requestedPermissionsFlags = new int[N];
for (int i=0; i<N; i++) {
final String perm = p.requestedPermissions.get(i);
pi.requestedPermissions[i] = perm;
if (p.requestedPermissionsRequired.get(i)) {
pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED;
}
if (grantedPermissions != null && grantedPermissions.contains(perm)) {
pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
}
}
}
}
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
int N = (p.mSignatures != null) ? p.mSignatures.length : 0;
if (N > 0) {
pi.signatures = new Signature[N];
System.arraycopy(p.mSignatures, 0, pi.signatures, 0, N);
}
}
return pi;
}
回到此apk的源文件ViewPackage.java
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Intent startingIntent = this.getIntent(); try {
ArrayList<String> toShow = startingIntent.getExtras()
.getStringArrayList("pkgs");
/*
* Allow starting of activities when we are in single package view
* mode, and give package count when not exactly one.
*/
if (toShow.size() != 1)
mOut.append("Received " + toShow.size() + " package names\n");
else
setupActionStuff(pm.getPackageInfo(toShow.get(0), 0xFFFFFFFF)); for (String cur : toShow) {
mOut.append(describePackage(packages.get(cur)));
}
... void initControls() {
...
mStartButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
startCurrentlySelected();
}
});
... /**
* Populates the activity selection spinner and activates start button if
* needed.
*/
public void setupActionStuff(PackageInfo p) {
showActivityUI(false);
mActivitiesList.clear();
mPkgName = null; // enable system and manifest views as this is an individual package
mSystemViewButton.setVisibility(View.VISIBLE);
try {
getPackageManager().getPackageGids(
"com.isecpartners.android.manifestexplorer");
mManifestButton.setVisibility(View.VISIBLE);
} catch (NameNotFoundException e) {
mManifestButton.setVisibility(View.GONE);
} if (null == p || null == p.activities)
return; mPkgName = p.packageName; for (ActivityInfo ai : p.activities)
if (ai.exported)
mActivitiesList.add(ai.name); ArrayAdapter<String> AA = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_dropdown_item, mActivitiesList);
mActivities.setAdapter(AA); if (mActivities.getCount() > 0)
showActivityUI(true);
} /**
* Looks at the spinner for the currently selected Activity, then starts it.
*/
public void startCurrentlySelected() {
Intent i = new Intent();
i.setComponent(new ComponentName(mPkgName, mActivities
.getSelectedItem().toString()));
startActivity(i);
}
IntentSniffer.java
// classes that have broadcast actions, this list is incomplete (a start).
// These classes are not all visible to SDK, so loading them at runtime for
// reflection on the actual device.
public String[] actionHarboringClasses = {
"android.content.Intent",
"android.bluetooth.BluetoothIntent",
"android.bluetooth.BluetoothA2dp",
// Application specific, and would be dangerous to load (as their
// static initializer might run etc.)
// "com.android.mms.transaction.MessageStatusReceiver",
// "com.android.mms.transaction.SmsReceiverService",
// "com.android.internal.telephony.gsm.stk.AppInterface",
"com.android.internal.location.GpsLocationProvider",
"com.android.internal.telephony.TelephonyIntents",
"android.provider.Telephony.Intents",
"android.proivder.Contacts.Intents",
"com.android.mms.util.RateController",
"android.media.AudioManager", "android.net.wifi.WifiManager",
"android.telephony.TelephonyManager",
"android.appwidget.AppWidgetManager",
"android.net.ConnectivityManager" }; /*
* Grabs the recent tasks from the ActivityManager.
*/
protected void updateWithRecents() {
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<RecentTaskInfo> rti = am.getRecentTasks(1000,
ActivityManager.RECENT_WITH_EXCLUDED);
StringBuffer log = new StringBuffer();
int count = 0;
for (RecentTaskInfo c : rti) {
count++;
Intent cur = c.baseIntent;
log.append("received: " + rti.toString() + "\n");
receiveIntent(RECENT_SOURCE, cur);
Log.d(TAG, "recent intent added: " + cur.toString()
+ cur.hashCode());
} } protected void updateKnownCategories() {
Set<String> l = new HashSet<String>();
// reflect across whatever Intent class is installed on the user's
// system to grab all static "CATEGORY" fields
for (Field f : Intent.class.getFields()) {
// the Intent class has a bunch of constants named "CATEGORY_*",
// grabbing em...cheap eh.
if (f.getName().startsWith("CATEGORY")
&& f.getType() == String.class) {
try {
l.add((String) f.get(null));
} catch (IllegalAccessException e) {
// should "adjust" protection level of field and retry.
// but I believe there is no need right now. Maybe a
// future constant will be private on some platform.
Log.d(TAG, "Access error on: " + f.getName());
}
}
} mKnownCategories = l.toArray(new String[l.size()]);
saveKnownCategories();
} protected void updateKnownActions() {
Set<String> l = new HashSet<String>();
List<Class> classes = new ArrayList<Class>(); for (String cur : actionHarboringClasses)
try {
classes.add(Class.forName(cur));
} catch (ClassNotFoundException cne) {
Log.e(TAG, "missing class " + cne.getMessage());
} for (Class c : classes)
for (Field f : c.getFields()) {
// actions constants tend to start or end with ACTION
if ((f.getName().startsWith("ACTION") || f.getName().endsWith(
"ACTION"))
&& f.getType() == String.class) {
try {
l.add((String) f.get(null));
} catch (Exception e) {
// should "adjust" protection level of field and retry.
Log.d(TAG, "Access error on: " + c.getName() + " : "
+ f.getName() + "" + e.getMessage());
}
}
}
this.mNumReflected = l.size();
findMoreActions(l);
mKnownBroadcastActions = l.toArray(new String[l.size()]);
saveKnownActions();
} protected void findMoreActions(Set<String> l) {
// walk the XML registrations for every package on the system looking
// for places where broadcast receivers are registered.
for (PackageInfo pi : getPackageManager().getInstalledPackages(
PackageManager.GET_DISABLED_COMPONENTS)) {
try {
XmlResourceParser x = createPackageContext(pi.packageName, 0)
.getAssets().openXmlResourceParser(
"AndroidManifest.xml");
int eventType = x.getEventType();
// looking for receiver tag, containing action tag(s), contains
// a name attribute has a value that is the known action.
int state = 0; // 0 out, 1 in receiver
String n;
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
n = x.getName();
n = (null == n) ? "" : n.toLowerCase();
if (n.equals("receiver"))
state = 1;
else if (1 == state && n.equals("action"))
for (int i = 0; i < x.getAttributeCount(); i++)
if (x.getAttributeName(i).equalsIgnoreCase(
"name"))
l.add(x.getAttributeValue(i));
break;
case XmlPullParser.END_TAG:
n = x.getName();
n = (null == n) ? "" : n.toLowerCase();
if (n.equals("receiver"))
state = 0;
break;
}
eventType = x.nextToken();
}
} catch (IOException ioe) {
Log.e(TAG, "IOException opening package: " + pi.packageName);
} catch (NameNotFoundException name) {
Log.e(TAG, "NameNotFoundException opening package: "
+ pi.packageName);
} catch (XmlPullParserException e) {
Log.e(TAG, "Exception reading manifest XML for package: "
+ pi.packageName);
}
}
mNumDug = l.size() - mNumReflected;
}
IntentFuzzer.java
/**
* For any type, provide the registered instances based on what the package
* manager has on file. Only provide exported components.
*
* @param type
* IPC requested, activity, broadcast, etc.
* @return
*/
protected ArrayList<ComponentName> getExportedComponents(IPCType type) {
ArrayList<ComponentName> found = new ArrayList<ComponentName>();
PackageManager pm = getPackageManager();
for (PackageInfo pi : pm
.getInstalledPackages(PackageManager.GET_DISABLED_COMPONENTS
| PackageManager.GET_ACTIVITIES
| PackageManager.GET_RECEIVERS
| PackageManager.GET_INSTRUMENTATION
| PackageManager.GET_PROVIDERS
| PackageManager.GET_SERVICES)) {
PackageItemInfo items[] = null; switch (type) {
case ACTIVITIES:
items = pi.activities;
break;
case BROADCASTS:
items = pi.receivers;
break;
case SERVICES:
items = pi.services;
break;
case PROVIDERS:
items = pi.providers;
break;
case INSTRUMENTATIONS:
items = pi.instrumentation;
} if (items != null)
for (PackageItemInfo pii : items)
found.add(new ComponentName(pi.packageName, pii.name));
} return found;
} protected int fuzzAllBroadcasts(List<ComponentName> comps) {
int count = 0;
for (int i = 0; i < comps.size(); i++) {
Intent in = new Intent();
in.setComponent(comps.get(i));
sendBroadcast(in);
count++;
}
return count;
} protected int fuzzAllServices(List<ComponentName> comps) {
int count = 0;
for (int i = 0; i < comps.size(); i++) {
Intent in = new Intent();
in.setComponent(comps.get(i));
try {
startService(in);
} catch (Exception e) {
mOut.append("Can't launch " + comps.get(i) + ""
+ e.getMessage() + "\n");
}
count++;
}
return count;
} protected boolean sendIntentByType(Intent i, IPCType t) {
try {
switch (t) {
case ACTIVITIES:
startActivity(i);
return true;
case BROADCASTS:
sendBroadcast(i);
return true;
case SERVICES:
startService(i); // stopping these might be nice too
return true;
case PROVIDERS:
// uh - providers don't use Intents...what am I doing...
Toast.makeText(this,
"Proivders don't use Intents, ignore this setting.",
Toast.LENGTH_SHORT).show();
return false;
case INSTRUMENTATIONS:
Toast
.makeText(
this,
"Instrumentations aren't Intent based... starting Instrumentation.",
Toast.LENGTH_SHORT).show();
startInstrumentation(i.getComponent(), null, null); // not
// intent based you could fuzz these params, if anyone cared.
return true;
}
} catch (Exception e) {
return false;
}
return false;
} protected Intent fuzzBroadcast(ComponentName toTest) {
Intent i = new Intent();
i.setComponent(toTest);
sendBroadcast(i);
return i;
}
5.drozer(原名Mercury)用作Android平台上动态分析的安全测试框架,其源码由Python编写,它能安装在Windows & Linux上,详见其用户指南,其研发公司MWR InfoSecurity还提供了Android 安全研究。
6.androguard用Python编写,目前只能在Linux上安装、使用,它安装时需要较多第三方库的支持,幻灯片Android-OEM-applications-insecurity-and-backdoors-without-permission用此工具作了分析。
7.DroidBox用Python编写,目前只能运行在Linux和Mac上,它用作对Android应用动态分析。
8.查看apk签名:
jarsigner -verify -verbose -certs apk_name.apk
9.Apk相关文件中如congfig、未加密的db中无工号、用户个人数据和系统数据、万能码之类内容、账户密码和敏感数据不要明文存储
10.Apk网络传输时不能泄露数据、不能有影响安全的开发端口,测试工具参考文章Android工具tcpdump和Nmap
11.Apk运行时Logcat输出不必要的信息
12.APK相关文件权限other组权限为---或--x,比如不含w权限:
-rw-rw--w- :data/data/com.snda.cloudary/shared_prefs/UserInfo.xml …该文件的这个权限不安全
13.Santoku
14.用至少两种杀毒软件对发布产品进行查杀
15.禁用私有加密算法和业界已知不安全加密算法,需解决兼容或标准协议规定的除外。目前较安全的加密算法为:
3DES 128及以上
AES 128及以上
RSA 1024及以上
SHA2 256及以上
HMAC-SHA2 128及以上
其它可见幻灯片Android软件安全攻防研究现状,Android漏洞信息库,Security Tips。
16.APK禁止申请不必要权限
Android 安全测试的更多相关文章
- Android开源测试框架学习
近期因工作需要,分析了一些Android的测试框架,在这也分享下整理完的资料. Android测试大致分三大块: 代码层测试 用户操作模拟,功能测试 安装部署及稳定性测试 代码层测试 对于一般java ...
- Android ui 测试课堂笔记
开始接触Android ui测试了,笔记如下 模拟器 Genemotion , the fastest android simulator in the world Android ui 测试工具 S ...
- Android压力测试工具——Monkey
Android压力测试工具——Monkey Monkey是运行在模拟器上和真机设备上的一段程序,它会产生用户事件的一系列伪随机流,比如点击.触摸.手势,还有很多系统级别的事件.Monkey通常是用来做 ...
- Android渗透测试Android渗透测试入门教程大学霸
Android渗透测试Android渗透测试入门教程大学霸 第1章 Android渗透测试 Android是一种基于Linux的自由及开放源代码的操作系统,主要用于移动设备,如智能手机.平板等.目前 ...
- 监听Android CTS测试项解决方案(一)
前言: 首先这里需要详细叙述一下标题中"监听Android CTS测试项解决方案"的需求.这里的需求是指我们需要精确的监听到当前CTS测试正在测试的测试项. 因为我们知道CTS认证 ...
- Android测试(一):在Android中测试App
原文:https://developer.android.com/training/testing/index.html 测试你的App是开发过程中的重要组成部分.通过对应用程序持续的运行测试,你可以 ...
- Google+ 团队的 Android UI 测试
https://github.com/bboyfeiyu/android-tech-frontier/tree/master/android-blog/Google%2B%20%E5%9B%A2%E9 ...
- [转]Android开源测试框架学习
近期因工作需要,分析了一些Android的测试框架,在这也分享下整理完的资料. Android测试大致分三大块: 代码层测试 用户操作模拟,功能测试 安装部署及稳定性测试 代码层测试 对于一般java ...
- 20162311 编写Android程序测试查找排序算法
20162311 编写Android程序测试查找排序算法 一.设置图形界面 因为是测试查找和排序算法,所以先要有一个目标数组.为了得到一个目标数组,我设置一个EditText和一个Button来添加数 ...
- 移动性能测试 | 持续集成中的 Android 稳定性测试
前言 谈到Android稳定测试,大多数会联想到使用monkey工具来做测试.google官方提供了monkey工具,可以很快速点击被应用,之前我有一篇帖子提到了monkey工具的使用,详见: htt ...
随机推荐
- python-集合(第二篇(七):集合)
第二篇(七):集合 python 集合 集合标准操作 摘要: 说明: ·类似于数学中学的集合,Python中的集合可以实现去重的功能,通过set()函数来实现: ·sets支持x in set, ...
- BFC与IFC
在我们做的网页上通常最重要的其中一点就是美观度,bfc他是一个块级格式化上下文,它是一个独立的渲染区域,只有Block-level box参与, 它规定了内部的Block-level Box如何布局, ...
- node基础再现--module.exports 和exports
实际上,最最基础的方法,最最原始的方法是module.exports,至于exports,是为了方便书写才出来的,应该说,module.exports 包含exports,所工作的范围更加的广泛! m ...
- Quartz.NET配置(Log4net)
最近有个任务关于服务调度,想起以前看过Quartz.NET调度任务非常棒. 今天小试Quartz.NET,前面配置Quartz.NET很轻松,控制台也输出了.但是想配合Log4net来做日志文件,怎么 ...
- LinkedIn第三方登录
官方开发文档网址:https://developer.linkedin.com angularjs LinkedIn初始化 var apiKey='77n7z65hd7azmb';$(function ...
- MSSQL批量替换网址字符串语句
1.如何批量替换ntext字段里面的数据 问题描述: 我想把数据库中News表中的字段content中的一些字符批量替换. 我的content字段是ntext类型的. 我想替换的字段是content字 ...
- 序列化- 使用BinaryFormatter进行序列化
可以使用属性(Attribute)将类的元素标为可序列化的(Serializable)和不可被序列化的(NonSerialized)..NET中有两个类实现了IFormatter借口的类中的Seria ...
- 不能错过的XCode插件
以便自己记忆网上找的! 以下是搜集的一些有力的XCode插件. 全能搜索家CodePilot 2.0 你要找的是文件?是文件夹?是代码?Never Mind,CMD+SHIFT+X调出CodePilo ...
- OA系统权限管理设计方案学习
学习之:http://www.cnblogs.com/kivenhou/archive/2009/10/19/1586106.html 此为模型图: 据此写了sql语句: drop table if ...
- Excel等外部程序点击链接会带上IE信息的bug
今天碰到一个问题,在Excel内点击链接到默认浏览器Chrome打开,奇怪的是服务端收到的Session一直对不上. 查了很久发现这个Excel到Chrome的跳转竟然带上了IE的Cookie 和 U ...