Coder Social home page Coder Social logo

rozbo / blog Goto Github PK

View Code? Open in Web Editor NEW
148.0 6.0 15.0 1.6 MB

A super blog lite -- just one page. use vue with github api !

Home Page: https://blog.6h.work

License: Do What The F*ck You Want To Public License

HTML 6.51% Vue 19.67% JavaScript 26.73% Shell 1.00% CSS 10.61% TypeScript 15.10% SCSS 20.38%
vue blog-lite typescript vuejs

blog's Introduction

A blog based on github issue

usage

fork this project and change the config.js to your own github username and repo.

Then build it and push the static pages to your github pages.

If you want to use the github action for CI/CD, you also should change the repo url in .github/.github/workflows/github_page.yaml

History

If you want use to old version, checkout it in https://github.com/rozbo/blog/releases and touch the doc with

Sponsor

The project is developing by JetBrains Ide

blog's People

Contributors

dependabot[bot] avatar rozbo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

Sublime Text 3全平台破解思路,源码

前因

由于上次无聊,在mac平台上分析了下当时刚刚release的sublime text3,然后写出了个补丁,
开源在https://github.com/rozbo/sublimetext-mac-anti上,大家反映都还可以,但是有mac的人还是比较少的,都想要个windows版的。
但当时我手里没有windows设备,总不能因为这个事,搞个虚拟机吧。我8g的mbp可整不起。
今天,手里刚好有台windows 10,可以来试试windows下破解。

原理

sublime text的验证以前一直是本地验证,得益于此,很早之前甚至可以使用注册机来自行算号,自己注册,但后来的版本中,加入了在线验证,所以就必须给他动手术了。

正常来讲,我们只需要在判断是否是正版的时候,让他永远返回1就行了,那么,如何找到判断是否是正版的函数呢?那就是通过字符串引用的方式,找到使用版的弹窗上面的文字,往上面翻一点就到了,这种方案完全不需要key的存在,当然,点击关于的时候,显示的一片空白,完全体现不了我们装逼的气质。我想在点击关于的时候显示我的名字。
那该怎么办呢???

首先我们观察sublimte text的注册码

—– BEGIN LICENSE —–
Michael Barnes
Single User License
EA7E-821385
8A353C41 872A0D5C DF9B2950 AFF6F667
C458EA6D 8EA3C286 98D1D650 131A97AB
AA919AEC EF20E143 B361B1E7 4C8B7F04
B085E65E 2F5F5360 8489D422 FB8FC1AA
93F6323C FD7F7544 3F39C318 D95E6480
FCCC7561 8A4A1741 68FA4223 ADCEDE07
200C25BE DBBC4855 C4CFB774 C5EC138C
0FEC1CEF D9DCECEC D3A5DAD1 01316C36
—— END LICENSE ——

其中前面的—– BEGIN LICENSE —–和后面的—— END LICENSE ——,只是一个标记,没有什么特殊作用,在验证的第一步就会被过滤掉。
Michael Barnes则是用户名,Single User License则是类型,EA7E-821385则是授权码的id。下面的,就是授权码,其中包含了校验。

知道了这个原理,我们很自然的想到了一个简单的破解办法,那就是让校验永远返回校验通过,然后我们随便输入下面的校验,上面的名字和类型都可以随便改,而本地认为这是一个正版用户。
然后我们在拦截掉联网验证即可。

mac版

mac版非常简单,开源在
https://github.com/rozbo/sublimetext-mac-anti
思路如上,不再赘述。

windows版

搞了半天,突然发现

—– BEGIN LICENSE —– 
TwitterInc 
200 User License 
EA7E-890007 
1D77F72E 390CDD93 4DCBA022 FAF60790 
61AA12C0 A37081C5 D0316412 4584D136 
94D7F7D4 95BC8C1C 527DA828 560BB037 
D1EDDD8C AE7B379F 50C9D69D B35179EF 
2FE898C4 8E4277A8 555CE714 E1FB0E43 
D5D52613 C3D12E98 BC49967F 7652EED2 
9D2D2E61 67610860 6D338B72 5CF95C69 
E36B85CC 84991F19 7575D828 470A92AB 
—— END LICENSE ——

这个key是可以激活的,并且支持在线验证的。。。。。
本文到此终结。

更简单的方法

在文章的最后,给你们一个直接通关的办法!!!
其实网络上流传的各种验证码都是可以通过本地校验这一关键步骤的,但是本地有个数组,存放了被ban掉的key,我们可以在本地清空这个数组来实现任意共享key激活。
例如我们可以使用CE无脑替换。

ce22222

ios modfiy code & data with running

#include <substrate.h>
#import <mach/mach.h>
#import <unistd.h>
extern "C" kern_return_t mach_vm_region
(
 vm_map_t target_task,
 vm_address_t *address,
 vm_size_t *size,
 vm_region_flavor_t flavor,
 vm_region_info_t info,
 mach_msg_type_number_t *infoCnt,
 mach_port_t *object_name
 );
 extern "C" kern_return_t mach_vm_protect(
	 vm_map_t target_task,
	 mach_vm_address_t address,
	 mach_vm_size_t size,
	 boolean_t set_maximum,
	 vm_prot_t new_protection
 );
typedef unsigned long zsize;
typedef unsigned long zaddr;
%ctor
{
	void* la=MSFindSymbol(NULL, "_NSFoundationVersionNumber");
// 	mach_port_t task;
// #if defined(_MAC64) || defined(__LP64__)
// 	NSLog(@"xxxxxxxfffff");
// 	vm_region_basic_info_data_64_t info;
// 	mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
// 	vm_region_flavor_t flavor = VM_REGION_BASIC_INFO_64;
// 	if (mach_vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info, (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task) != KERN_SUCCESS)
// 	{
// 		NSLog(@"errorrrrrrrr2");
// 	}
// #else
// 	vm_region_basic_info_data_t info;
// 	mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
// 	vm_region_flavor_t flavor = VM_REGION_BASIC_INFO;
// 	if (vm_region(mach_task_self(), &region, &region_size, flavor, (vm_region_info_t)&info, (mach_msg_type_number_t*)&info_count, (mach_port_t*)&task) != KERN_SUCCESS)
// 	{
// 		NSLog(@"errorrrrrrrr1");
// 	}
// #endif
	kern_return_t kr;
	// zaddr address=(zaddr)la;
	// zsize size=sizeof(double);
    // zsize page_size;
    // zaddr aligned_addr;
    // zsize aligned_size;

    // page_size = getpagesize();
    // aligned_addr = (zaddr) address & ~(page_size - 1);
    // aligned_size =
    //         (1 + ((address + size - 1 - aligned_addr) / page_size)) * page_size;

	kr = vm_protect(mach_task_self(), (vm_address_t) la,
                         sizeof(double), false, (VM_PROT_ALL | VM_PROT_COPY));
	*(double*)la=100.0;
	//mach_vm_protect(mach_task_self(), aligned_addr, region_size, false, VM_PROT_READ);

	//NSLog(@"2222222:%f",*l);

	// *l=100.0;
}

about

About Me

  • normal man .
  • like blue margarita .

用css画小猪佩奇

今天无聊,看到一篇文章,觉得特别秀,于是给转载过来,啥时候也学习一哈。

<!DOCTYPE html>
<html>
<head>
	<title></title>
	<style type="text/css">

	div {
		position: absolute;
		transform-origin: left top;
	}
.pig_container {
	width: 800px;
	height: 800px;
	top: 0;
	left: 50px;
}

.pig_head {
	width: 300px;
    height: 200px;
    top: 100px;
    left: 100px;
    border-radius: 95% 50% 50% 50%/ 87% 80% 68% 50%;
    border: 6px solid #ef96c2;
    background-color: #ffb3da;
    transform: rotate(30deg);
    z-index: 100;
    box-sizing: border-box;
}
.pig_head_white_left_bottom {
    width: 200px;
    height: 154px;
    bottom: -7px;
    left: -38px;
    background-color: #fff;
    box-sizing: border-box;
}
.pig_head_white_left_top {
/*	width: 200px;
    height: 64px;
    bottom: 84px;
    left: 52px;
    background-color: #ffb3da;
    box-sizing: border-box;*/

    width: 200px;
    height: 66px;
    bottom: 84px;
    background-color: #ffb3da;
    box-sizing: border-box;
    top: 166px;
    left: 134px;
    transform: rotate(34deg);
    z-index: 103;
}
.left_eye, .right_eye, .face, .mouth {
    z-index: 104;
}
.pig_nose {
	width: 51px;
    height: 70px;
    top: 147px;
    left: 107px;
    border-radius: 72% 72% 72% 72%/ 72% 72% 72% 72%;
    border: 6px solid #ef96c2;
    background-color: #ffb3da;
    transform: rotate(36deg);
    z-index: 103;
    box-sizing: border-box;
}
.pig_nose_bottom {
	width: 88px;
    height: 13px;
    top: 209px;
    left: 84px;
    border-radius: 50% 50% 50% 50%/ 0% 0% 100% 100%;
    border: 6px solid #ef96c2;
    background-color: #ffb3da;
    transform: rotate(35deg);
    z-index: 102;
    box-sizing: border-box;
    border-top-color: #ffb3da;
}
.pig_jaw {
	width: 97px;
    height: 104px;
    top: 249px;
    left: 141px;
    border-radius: 0% 0% 0% 76%/ 0% 0% 0% 74%;
    border: 6px solid #ef96c2;
    background-color: #ffb3da;
    transform: rotate(22deg);
    z-index: 100;
    box-sizing: border-box;
    border-top-color: #ffb3da;
    border-right-color: #ffb3da;
}
.pig_jaw_right {
	width: 13px;
    height: 6px;
    background-color: #ef96c2;
    top: 373px;
    left: 186px;
    transform: rotate(19deg);
    z-index: 100;
}
.left_eye_bg {
	width: 29px;
    height: 29px;
    top: 177px;
    left: 170px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    border: 6px solid #fff;
    background-color: #fff;
    z-index: 101;
    box-sizing: border-box;
}
.left_eye_ball {
	width: 10px;
    height: 10px;
    top: 181px;
    left: 171px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    border: 6px solid #000;
    background-color: #000;
    z-index: 101;
    box-sizing: border-box;
}
.left_eye_border {
	width: 34px;
    height: 34px;
    top: 174px;
    left: 166px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    border: 6px solid #ef96c2;
    background-color: transparent;
    z-index: 101;
    box-sizing: border-box;
}

.right_eye_bg {
	width: 28px;
    height: 28px;
    top: 194px;
    left: 205px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    border: 6px solid #fff;
    background-color: #fff;
    z-index: 101;
    box-sizing: border-box;
}
.right_eye_ball {
	width: 10px;
    height: 10px;
    top: 199px;
    left: 208px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    border: 6px solid #000;
    background-color: #000;
    z-index: 101;
    box-sizing: border-box;
}
.right_eye_border {
	width: 35px;
    height: 37px;
    top: 191px;
    left: 202px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    border: 6px solid #ef96c2;
    background-color: transparent;
    z-index: 101;
    box-sizing: border-box;
}

.mouth_bottom {
	width: 97px;
    height: 45px;
    top: 273px;
    left: 154px;
    border-radius: 50% 50% 50% 50%/ 0% 0% 100% 100%;
    border: 6px solid #d44b81;
    background-color: #000;
    z-index: 101;
    box-sizing: border-box;
    transform: rotate(19deg);
}
.mouth_middle {
	width: 98px;
    height: 27px;
    top: 272px;
    left: 154px;
    border-radius: 0% 0% 50% 50%/ 0% 0% 100% 100%;
    border: 6px solid #d44b81;
    background-color: #ffb3da;
    z-index: 101;
    box-sizing: border-box;
    transform: rotate(19deg);
    border-top-color: #ffb3da;
}
.mouth_top {
        width: 135px;
    height: 66px;
    top: 231px;
    left: 149px;
    border-radius: 50% 50% 50% 50%/ 0% 0% 100% 100%;
    background-color: #ffb3da;
    z-index: 101;
    transform: rotate(13deg);

/*	width: 131px;
    height: 55px;
    top: 231px;
    left: 152px;
    border-radius: 50% 50% 50% 50%/ 0% 0% 100% 100%;
    background-color: #ffb3da;
    z-index: 101;
    transform: rotate(19deg);*/
}
.face {
	width: 49px;
    height: 59px;
    top: 243px;
    left: 269px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    background-color: #ff96ce;
    transform: rotate(26deg);
}

.nose_kong_left {
	width: 12px;
    height: 12px;
    top: 179px;
    left: 93px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    background-color: #da6c9b;
    z-index: 104;
}
.nose_kong_right {
	width: 12px;
    height: 12px;
    top: 182px;
    left: 109px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    background-color: #da6c9b;
    z-index: 104;
}

.ear_left {
    width: 24px;
    height: 52px;
    top: 126px;
    left: 226px;
    border: 6px solid #ef96c2;
    border-radius: 50% 50% 50% 50%/ 35% 40% 50% 50%;
    background-color: #ffb3da;
    z-index: 99;
    transform: rotate(18deg);
}
.ear_right {
	width: 24px;
    height: 52px;
    top: 150px;
    left: 280px;
    border: 6px solid #ef96c2;
    border-radius: 50% 50% 50% 50%/ 35% 40% 50% 50%;
    background-color: #ffb3da;
    z-index: 99;
    transform: rotate(36deg);
}

.pig_body_bottom {
	width: 215px;
    height: 197px;
    top: 305px;
    left: 108px;
    border: 6px solid #e33b32;
    border-radius: 50% 50% 50% 50%/ 100% 100% 0% 0%;
    background-color: #eb5b50;
    z-index: 99;
}

.hand_left_middle {
	width: 78px;
    height: 12px;
    top: 432px;
    left: 63px;
    border-radius: 100% 100% 100% 17%/ 100% 90% 16% 90%;
    background-color: #ffbadf;
    z-index: 99;
    transform: rotate(-35deg);
}
.hand_left_top {
	width: 28px;
    height: 9px;
    top: 415px;
    left: 63px;
    border-radius: 100% 100% 100% 35%/ 100% 90% 16% 90%;
    background-color: #ffbadf;
    z-index: 99;
}
.hand_left_bottom {
	    width: 20px;
    height: 9px;
    top: 420px;
    left: 93px;
    border-radius: 60% 59% 65% 90%/ 100% 90% 89% 90%;
    background-color: #ffbadf;
    z-index: 99;
    transform: rotate(98deg);
}

.hand_right_middle {
	    width: 79px;
    height: 11px;
    top: 374px;
    left: 309px;
    border-radius: 100% 100% 15% 17%/ 99% 92% 90% 90%;
    background-color: #ffbadf;
    z-index: 99;
    transform: rotate(28deg);
}
.hand_right_top {
	width: 28px;
    height: 10px;
    top: 397px;
    left: 350px;
    border-radius: 100% 100% 15% 17%/ 99% 92% 90% 90%;
    background-color: #ffbadf;
    z-index: 99;
    transform: rotate(-7deg);
}
.hand_right_bottom {
	width: 28px;
    height: 11px;
    top: 395px;
    left: 356px;
    border-radius: 100% 100% 62% 17%/ 99% 92% 90% 90%;
    background-color: #ffbadf;
    z-index: 99;
    transform: rotate(69deg);
}
.left_foot {
	    width: 11px;
    height: 52px;
    top: 507px;
    left: 175px;
    border-radius: 100% 100% 100% 100%/ 50% 50% 21% 20%;
    background-color: #ffbadf;
    z-index: 99;
}
.left_shoes {
	    width: 51px;
    height: 14px;
    top: 553px;
    left: 138px;
    border-radius: 58% 187% 180% 50%/ 130% 123% 113% 100%;
    background-color: #000;
    z-index: 99;
    transform: rotate(0deg);
}
.right_foot {
	left: 268px;
}
.right_shoes {
	left: 230px;
}
.pig_shadow {
	width: 240px;
    height: 47px;
    top: 535px;
    left: 101px;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    background-color: rgba(171, 171, 171, 0.7);
    transform: rotate(-1deg);
}
.tail_left {
	width: 19px;
    height: 8px;
    top: 472px;
    left: 330px;
    border-radius: 50% 50% 50% 50%/ 0% 0% 100% 100%;
    transform: rotate(-9deg);
    background-color: #ffbadf;
    z-index: 99;
}
.tail_left_blank {
	width: 30px;
    height: 15px;
    top: 466px;
    left: 332px;
    border-radius: 50% 50% 50% 50%/ 0% 0% 100% 100%;
    transform: rotate(-36deg);
    background-color: #fff;
    z-index: 99;
}
.tail_right {
	width: 21px;
    height: 5px;
    top: 451px;
    left: 343px;
    border-radius: 0% 0% 51% 50%/ 0% 0% 100% 100%;
    transform: rotate(31deg);
    background-color: #fff;
    z-index: 99;
    border: 8px solid #ffbadf;
    border-top-color: #fff;
}
.tail_blank {
	width: 36px;
    height: 21px;
    top: 437px;
    left: 351px;
    transform: rotate(34deg);
    background-color: #fff;
    z-index: 99;
}
.tail_middle {
	width: 7px;
    height: 11px;
    top: 450px;
    left: 336px;
    border: 8px solid #ffbadf;
    border-radius: 50% 50% 50% 50%/ 50% 50% 50% 50%;
    background-color: #fff;
    z-index: 99;
}
.tail_circle {
	width: 17px;
    height: 8px;
    top: 475px;
    left: 358px;
    border-radius: 36% 37% 62% 63%/ 99% 92% 90% 90%;
    background-color: #ffbadf;
    z-index: 99;
    transform: rotate(-40deg);
}

	</style>
</head>
<body>

<div class="pig_container">
	<!-- 尾巴 -->
	<div class="tail_left"></div>
	<div class="tail_right"></div>
	<div class="tail_blank"></div>
	<div class="tail_middle"></div>
	<div class="tail_circle"></div>
	<!-- 底部阴影 -->
	<div class="pig_shadow"></div>
	<!-- 左脚 -->
	<div class="left_foot"></div>
	<div class="left_foot right_foot"></div>
	<!-- 左鞋 -->
	<div class="left_shoes"></div>
	<div class="left_shoes right_shoes"></div>
	<!-- 左手 -->
	<div>
		<div class="hand_left_top"></div>
		<div class="hand_left_bottom"></div>
		<div class="hand_left_middle"></div>
	</div>
	<!-- 身体 -->
	<div class="pig_body_bottom"></div>
	<!-- 右手 -->
	<div>
		<div class="hand_right_top"></div>
		<div class="hand_right_bottom"></div>
		<div class="hand_right_middle"></div>
	</div>

    <!-- 猪头 -->
	<div>
		<!-- 耳朵 -->
		<div class="ear_left"></div>
		<div class="ear_right"></div>
		<div class="pig_head">
			<div class="pig_head_white_left_bottom"></div>
        </div>
			<div class="pig_head_white_left_top"></div>
		<!-- 鼻子 -->
		<div class="pig_nose"></div>
		<!-- 下巴 -->
		<div class="pig_jaw"></div>
		<div class="pig_jaw_right"></div>
		<div class="pig_nose_bottom"></div>
		<!-- 鼻孔 -->
		<div class="nose_kong_left"></div>
		<div class="nose_kong_right"></div>
		<!-- 左眼 -->
		<div class="left_eye">
			<div class="left_eye_bg"></div>
			<div class="left_eye_ball"></div>
			<div class="left_eye_border"></div>
		</div>
		<!-- 右眼 -->
		<div class="right_eye">
			<div class="right_eye_bg"></div>
			<div class="right_eye_ball"></div>
			<div class="right_eye_border"></div>
		</div>
		<!-- 嘴巴 -->
		<div class="mouth">
			<div class="mouth_bottom"></div>
			<div class="mouth_middle"></div>
			<div class="mouth_top"></div>
		</div>
		<!-- 脸颊 -->
		<div class="face"></div>
	</div>
</div>

</body>
</html>

最终效果:

https://www.doverr.com/peppa.html

原文地址

用 CSS 画小猪佩奇,你就是下一个社会人!

xunit.net 一个简单的忽略测试都做不好

需求

这是一个非常符合人类的逻辑的操作:忽略某些测试,除非我手动运行。
非常合理对吧?
对应当你测试一些有昂贵操作的行为时,这非常有用。比如你不能每次测试都请求某个收费的api吧,这样不光是效率问题,经费问题,还有可能因为网络的波动而导致测试失败。又比如当你在测试里写一些概念验证,比如往临时往数据库里插入某条数据,你总不可能每次测试都要运行吧。
这么一个简单的需求,xunit.net竟然选择了一个匪夷所思的实现方法,即彻底隐藏,无论如何也不能执行了。并且从2015年到现在(2021年)也坚持不改,大有一幅你打死我我也不改的架势。
这是一个多么愚蠢的设计,如果我真的需要永久的禁止这个测试,我直接删除掉不就行了,或者直接简单注释掉声明测试的注解,也要比这个方便的多。

        [Fact]
        public void TestS() {
            1.Should().Be(1);
        }

这样的一个测试,是改成

        [Fact(Skip = "!2313")]
        public void TestS() {
            1.Should().Be(1);
        }

容易,还是改成

        //[Fact]
        public void TestS() {
            1.Should().Be(1);
        }

容易? 我想答案显然易见。
而以前的Nunit则可以通过Explicit 注解完美实现。‘

怎么办

万万没想到,最后我还是用一个非常曲折的方式实现了这一需求

    public class RunnableInDebugOnlyAttribute : FactAttribute {
        public RunnableInDebugOnlyAttribute() {
            if (!Debugger.IsAttached) {
                Skip = "Only running in interactive mode.";
            }
        }
    }

    /// <summary>
    /// Apply this attribute to your test method to specify a category.
    /// </summary>
    [TraitDiscoverer("Test.CategoryDiscoverer", "Test")]
    [AttributeUsage(AttributeTargets.Method)]
    class IgnoredAttribute : Attribute, ITraitAttribute {
        public IgnoredAttribute() {
        }
    }

    /// <summary>
    /// Apply this attribute to your test method to specify a category.
    /// </summary>
    [TraitDiscoverer("Test.CategoryDiscoverer", "Test")]
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    class CategoryAttribute : Attribute, ITraitAttribute {
        public CategoryAttribute(string category) {
        }
    }

    /// <summary>
    /// This class discovers all of the tests and test classes that have
    /// applied the Category attribute
    /// </summary>
    public class CategoryDiscoverer : ITraitDiscoverer {
        /// <summary>
        /// Gets the trait values from the Category attribute.
        /// </summary>
        /// <param name="traitAttribute">The trait attribute containing the trait values.</param>
        /// <returns>The trait values.</returns>
        public IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute) {
            var ctorArgs = traitAttribute.GetConstructorArguments().ToList();
            var category = "";
            if (ctorArgs.Count == 0) {
                var name = traitAttribute.ToString()?.Split(".").Last();
                if (name.EndsWith("Attribute")) {
                    name = name.Remove(name.LastIndexOf("Attribute", StringComparison.Ordinal));
                }

                category = name;
            }
            else if (ctorArgs.Count == 1) {
                category = ctorArgs[0].ToString();
            }
            else {
                throw new Exception("unknown trait");
            }

            yield return new KeyValuePair<string, string>("Category", category);
        }
    }

尽管这种方式非常的麻烦

        [RunnableInDebugOnly, Ignored]
        public void TestS() {
            1.Should().Be(1);
        }

他需要配合Rider使用,在编辑器中设置Skip tests from categories 的值为 Ignored,即可在Rider中实现忽略掉指定的分类。
但是命令行怎么办?则使用 RunnableInDebugOnly注解来声明仅在Debug模式下执行。(当然也可以修改逻辑为某个环境变量存在时执行等)

结论

有些人吧,就是犟。臭毛病。

ref

xunit/xunit#701
https://youtrack.jetbrains.com/issue/RIDER-49097

c#常量和枚举之坑

现在假设你有两个程序集,他们分别是A,B
其中 A 是一个类库

public class A
{
   public const string ConstValue = "A1";
}

而 B 是一个 console app,或者asp.net,或者无论什么可执行的程序,然后它依赖了B。

class B
{
   static void Main(string[] args)
   {
      Console.WriteLine(A.ConstValue);
      Console.ReadKey();
   }
}

这个程序非常简单,以致于刚入门的新手都能轻易看懂,正如你想的那样,它输出了 A1
然而,当你想把A1改成A2的时候,问题出现了。
你以为它会输出 A2,但事实是它还是会输出A1,惊不惊喜,意不意外?
同样的,枚举也类似。
这是因为 编译器对待常量的方式是提取,它会A程序集的常量的值,编译到自身的程序集里。也就是说,再编译后, A1这个字符串位于程序集B,而不是程序集A。
除非你修改一下程序集B,然后触发B的重新编译。
因此习惯性思维如同一座大山,任凭你如何努力,你也休想改变B的输出结果,你会以为是别的地方的问题,于是你会疯狂找bug,一直找,一直找,从白天到黑夜,从黑夜到凌晨。
你猜我是怎么发现的。

php一个诡异的加法算法的研究

前言

之前我在面试的时候,遇到许多年轻人都声称自己精通php,有过许多项目经验等等。然而,当真正笔试的时候,我问到

$result=1;
if(-1){
 $result=2;
}
echo $result;

中,$result最终结果的时候,许多人信誓旦旦的告诉我是1。 试想,这样一个连基本算法都搞不清楚的人,即使有过再多的项目经验,你敢用吗?

引申

对于算法的一些问题,我个人一向是非常较真的,我招人的时候也是非常侧重此方面,因此我对php的关注也是在这方面多些。故事得从一个知乎上的问题开始。

//第一题
<?php
function test(){
 $a=1;
 $b=&$a;
 echo (++$a)+(++$a);
}
test();
//执行的值为6
?>
//第二题
<?php
function test(){
 $a=1;
 $b=&$a;
 echo (++$a)+(++$a)+(++$a);
}
test();
//执行的值为10
?>

这个问题非常的有意思,也是一个大坑,许多人都算错了,包括很多我认识的大牛。在这里就不点名字,以免其羞愧。 然而你以为我一开始算对了吗?我算对了第一题,第二题却是百思不得其解。最后用调试工具一番调试才算理清头绪。索性发出来与大家一起分享这个有意思的问题。

分析

第一题

php语言解释

这个其实非常简单,++a这种单目运算符的运算结果还是自身。 所以

$a=1;
$b=&$a;
echo (++$a)+(++$a);
//换种写法就等同于
$a=1;
$a=++$a; //2
$a=++$a; //3
$a=$a+$a;//3+3=6

哈,很多人肯定以为是等于5,然而这个是操作的同一个变量,等同于改变了两次$a的值,最后相加的时候,自然就是改变后的值相加,所以等于6。

正常情况

然而在php中,为了照顾人类的逻辑,默认情况下,即使名字相同的基本类型的变量,也不会使用同一个变量地址,因此,以上代码会被解析为

$a=1;
echo (++$a)+(++$a);
//换种写法就等同于
$a=1;
$a=++$a; //2
$b=++$a; //3
$a=$a+$b;//2+3=5

但是由于

$b=&$a;

的存在,使得下面的第一个$a的计算方式变成了传统的c语言计算方式,所以输出了_看起来错误的结果_。
然而实际上,这个结果反而是正确的。PHP中的糖语法宠坏了那些基础本来不扎实的孩子,对这种加法做了特别的运算处理而已。

深入思考

为什么说等于6才是正确结果呢?我们知道现行高级语言大多来自c语言,php也不例外,我们这里用c语言来写一遍上述代码,然后通过反汇编来看看机器到底是怎么执行的。 其实无论是否注释下面的取地址,结果都是6。 我们看汇编代码 这里更清晰的看到a的值的变化。 即是

mov edx,dword ptr [a] ;a的值为3
add edx,dword ptr [a] ; 3+3

问题二

问题发现

由问题一的结论来分析问题二,反而陷入了一个更大的舞曲,为什么呢?

echo (++$a)+(++$a)+(++$a);  //10
/*
按照问题一的分析,此处的结果应为
a=1+1  //2
a=a+1  //3
a=a+1  //4
a=a+a+a //12
*/

然而输出的结果却是10.

动手实验

这里我们用一个工具phpdebug.exe来调试下看看。 可以看到的a的值的变化:

1->2->3->4

然后我们再来调试一下注释掉

$b=&$a;

的结果。 这里在第二步的时候重新给了变量$a一个地址,实际上同是叫$a,其实他们已经不是同一个变量了。 所以他输出的结果为9.

实验结果

但是为什么上面的结果为10呢? 这其实是因为

$b=&$a;

这个取地址运算只起效了一句运算指令,就是只管事了第一回合,对于以后的运算,php还是用了平常的算法。 即:

$a=1;
$b=&$a;
echo (++$a)+(++$a)+(++$a);
/*
这段实际上是
$a=++$a; //2
$a=++$a; //3
//注意了,前两个已经得到结果了,第三个我们用一个新的变量$c。
$c=++$a; //4
$a=$a+$a; //3+3=6
$a=$a+$c; //6+4=10
*/

结论

我认为出现这种诡异的结果应该算是php的bug,同时,这也说明了此种问题不太容易被发现和暴露,这要求我们平常写代码的时候尽量使用常用的语法,和精干的语句,让代码和逻辑达到最佳的平衡点。 此BUG我已经反馈到php官方。

后续

最新的php7中已经修复了此bug。

修订记录

  • 初稿 2015-09-11
  • 修订 2016-05-27
  • 修订 2016-05-30

ubuntu dev iphone.

安装交叉编译工具模版

docker images

There is a docker image (1.22GB) based on ubuntu-x86:10.04 for this toolchain4 plus Telesphoreo

Download from here https://mega.nz/#!jgJ23bRL!IC0hw8QDgJEH3RvfPGHK4Fij8dEV3MlX_Bn78WU8Zxs
import to docker using

cat toolchain4.tar.gz | docker import - toolchain4:ubuntu

or

docker import toolchain4.tar.gz toolchain4:ubuntu

run docker using

docker run -t -i toolchain4:ubuntu bash

Ubuntu 10.10 releases are here http://releases.ubuntu.com/10.10/
I used ubuntu-10.10-desktop-i386.iso (32 bit) in VMWare Fusion to test this toolchain

sdk

The pkg files should be put in /toolchain4/sdks/

If you want to ./toolchain.sh buildsys
    Download iPhoneSDK4_2.pkg here or here

If you want to ./toolchain.sh buildsys50
     Download iPhoneSDK5_0.pkg here or here

If you want to ./toolchain.sh buildsys43
     Download iPhoneSDK4_3.pkg here or here

If you want to ./toolchain.sh build313
     Download iPhoneSDKHeadersAndLibs.pkg (that is iPhoneSDK3.1.3) here or here
    Download MacOSX10.5.pkg here or here

If you want to ./toolchain.sh build32
    Download iPhoneSDKHeadersAndLibs_32.pkg (that is iPhoneSDK3.2) here or here
    and MacOSX10.5.pkg

Old iPhone SDKs (requires developer account login)
iPhone SDK 3.1.3 with XCode 3.2.1 for Snow Leopard (requires 10.6.0)
iPhone SDK 3.2 Final with Xcode 3.2.2 for Snow Leopard (requires 10.6.0)

Xcode 3.2.3 and iPhone SDK 4 Final for Snow Leopard (requires 10.6.2)

Xcode 3.2.4 and iOS SDK 4.1 for Snow Leopard (requires 10.6.4)

Xcode 3.2.5 and iOS SDK 4.2 for Snow Leopard (requires 10.6.4)

Xcode 3.2.6 and iOS SDK 4.3.1 for Snow Leopard (requires 10.6.6)

If you use Ubuntu 11.04/11.10, please make sure you use gcc-4.4 and g++-4.4 to compile cctools

sudo apt-get install gcc-4.4 g++-4.4 gobjc-4.4
sudo update-alternatives --remove-all gcc
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.4 44  \
 --slave /usr/bin/g++ g++ /usr/bin/g++-4.4 \
 --slave /usr/bin/gcov gcov /usr/bin/gcov-4.4

If you have problem installing libssl0.9.8, try this

wget http://us.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl0.9.8_0.9.8o-1ubuntu4_i386.deb
sudo dpkg -i libssl0.9.8_0.9.8o-1ubuntu4_i386.deb 

if you get the print_objc.c for error objc/objc_runtime.h not found add these to disable compilation in /toolchain4/src/cctools/otool/print_objc.c

#if 0
..
..#endif

There are lots of ld warning: bad symbol version: .... when compiling
To disable this, edit the source code of /toolchain4/src/cctools/ld64/src/MachOReaderDylib.hpp
and comment out this line

warning("bad symbol version: %s in dylib %s ...

Then rebuild cctools without downloading source again.

设置交叉编译工具

Ubuntu 10.10 iPhone Toolchain 4

If you use Ubuntu 11.04, please see wiki on how to use gcc-4.4 to compile

. 用apt依赖

    sudo apt-get update
    sudo apt-get install bison flex build-essential wget patch
    sudo apt-get install git autoconf gawk libssl-dev libxml2-dev uuid-dev
    sudo apt-get install clang

Clone Project

cd /
sudo git clone git://github.com/javacom/toolchain4.git
sudo chown -R <yourusername>:<yourusername> /toolchain4

Put iPhoneOS4.2.sdk.tgz to /toolchain4/sdks/

or build xar
sudo apt-get install libxml2-dev
./toolchain.sh xar
then put iPhoneSDK4_2.pkg to /toolchain4/sdks/
./toolchain.sh buildsys
to build sys

Put iPhoneSDK5_0.pkg to /toolchain4/sdks/
./toolchain.sh buildsys50
to build sys50 folder

Put iPhoneSDK4_3.pkg to /toolchain4/sdks/
./toolchain.sh buildsys43
to build sys43 folder

Put iPhoneSDKHeadersAndLibs.pkg to /toolchain4/sdks/
./toolchain.sh build313
to build sys313 folder

Put iPhoneSDKHeadersAndLibs_32.pkg to /toolchain4/sdks/
./toolchain.sh build32
to build sys32 folder
  1. Build headers tools and gcc
    cd /toolchain4
    ./toolchain.sh buildsys
    ./toolchain.sh cctools
    ./toolchain.sh llvmgcc
    ./toolchain.sh ldid

  2. Install as_driver
    cd /toolchain4/as_driver
    make
    cd /usr/bin
    sudo mv as i686-linux-gnu-as
    sudo cp -p /toolchain4/as_driver/as_driver as
    sudo ln -s /toolchain4/pre/bin/arm-apple-darwin9-as .
    sudo mv ld i686-linux-gnu-ld
    sudo cp -p /toolchain4/as_driver/as_driver ld
    sudo ln -s /toolchain4/pre/bin/arm-apple-darwin9-ld .

  3. Test iOS4 sample code
    cd /toolchain4/Projects/LocalPush
    make
    make dist
    (LocalPush.app & LocalPush.ipa will be in build/4.0/ folder)

  4. Checkout the updated UICatalog in svn here
    sudo apt-get install subversion
    cd /toolchain4/Projects
    svn checkout http://apiexplorer.googlecode.com/svn/trunk/UICatalog UICatalog-read-only
    cd UICatalog-read-only
    make -f Makefile.ios4
    make -f Makefile.ios4 dist
    (Then install the build/4.0/UICatalog.ipa to iPhone for testing)

Enjoy.


Instructions to add theos build system to toolchain4

  1. Installation
    cd /toolchain4
    git submodule add git://github.com/DHowett/theos.git theos
    mv /toolchain4/theos/include /toolchain4/theos/include.bak
    git submodule add git://github.com/DHowett/theos-nic-templates.git theos/templates/theos-nic-templates
    git submodule add git://github.com/rpetrich/iphoneheaders.git theos/include
    for FILE in /toolchain4/theos/include.bak/*.h; do mv $FILE /toolchain4/theos/include/; done
    rmdir /toolchain4/theos/include.bak
    cp /toolchain4/Projects/IOSurfaceAPI.h /toolchain4/theos/include/IOSurface/.
    cp /toolchain4/pre/bin/ldid /toolchain4/theos/bin/.

  2. This is how to create theos project
    cd /toolchain4/Projects
    /toolchain4/theos/bin/nic.pl

  3. Test iOS5 sample notification center widget (requires sys50)
    cd /toolchain4/Projects/widgettest/
    make && make package

  4. Test mobilesubstrate extension (requires sys32)
    cd /toolchain4
    git submodule add git://github.com/DHowett/preferenceloader.git Projects/preferenceloader
    cd /toolchain4/Projects/preferenceloader
    cp /toolchain4/Projects/Makefile_for_preferenceloader Makefile
    make && make package

Enjoy.


Instructions to add Telesphoreo to toolchain4


Introduction

Telesphoreo is an APT-based distribution of Unix Software for iOS started by Jay Freeman (saurik).  For details, please refer to http://www.telephoreo.org/

Installation

    # update the toolchain4 git project 
    # install additional required packages
    sudo apt-get install pkg-config realpath texinfo
    sudo apt-get install subversion
    # build headers for sys42 and sys32, please refer to wiki for downloads
    cd /toolchain4
    ./toolchain.sh buildsys
    mv /toolchain4/sys /toolchain4/sys42
    ./toolchain.sh build32
    ln -s /toolchain4/sys32 sys
    # update some header files
    cp /toolchain4/sys42/usr/include/mach/mach_interface.h /toolchain4/sys32/usr/include/mach/.
    cp /toolchain4/sys42/usr/include/mach/mach_host.h /toolchain4/sys32/usr/include/mach/.
    cp /toolchain4/sys42/usr/include/mach/mach_vm.h /toolchain4/sys32/usr/include/mach/.
    cp /toolchain4/sys42/usr/include/mach/mach_init.h /toolchain4/sys32/usr/include/mach/.
    cp /toolchain4/sys42/usr/include/mach/host_info.h /toolchain4/sys32/usr/include/mach/.
    mkdir -p /toolchain4/telesphoreo/debs
    mkdir -p /toolchain4/telesphoreo/data

Test build p7zip

    cd /toolchain4/telesphoreo/data
    svn co http://svn.telesphoreo.org/trunk/data/p7zip
    cd /toolchain4/telesphoreo
    export PATH=$PATH:/toolchain4/pre/bin
    export PKG_ARCH=iphoneos-arm; ./package.sh p7zip

Test build odcctools-782

cd /toolchain4/telesphoreo/data
svn co http://svn.telesphoreo.org/trunk/data/openssl
svn co http://svn.telesphoreo.org/trunk/data/uuid
cd /toolchain4/telesphoreo
export PKG_ARCH=iphoneos-arm; ./package.sh odcctools

Enjoy.

一次解决Redis Timeout 的坑爹之旅

压力测试过程中遇到的一个问题,框架是asp.net core 2.2,redis组件是StackExchange.Redis 2.0.601,在高并发场景下会报错,报错信息:

坑爹之路

怀疑redis

最初怀疑是由于redis单线程,扛不住这么高的并发,但问题的关键是我才200个并发,就必定能挂,试想这个redis也太垃圾了吧,但是仔细想想不太对,之前redis单机能抗过万的并发,这里应该问题不大吧。但实在没招还是搞了redis集群。
使用的是https://github.com/bitnami/bitnami-docker-redis-cluster,16节点8主8从的配置。
但这里显然跳进了一个更深的坑,关于redis集群的坑,比如只能使用0号库(实际上已经没有库了,只有槽),比如集群间互相无法发现(可能是因为动态dokcer ip地址的原因),比如动态扩容后需要重新移动槽等,改天专门记录一下。
好不容易披荆斩棘度过了这个难关,然而发现根本没卵用。

怀疑连接池

我又在想,是不是因为连接被占用了,导致后续无法获得新的连接?又是一顿操作猛如虎,撸了个redis连接池。然而还是一段操作猛如虎,一看战力2.5

怀疑人生

整个人处于一种懵逼状态,虽然好多年不搞web,但web也不能这么难吧。抽颗华子冷静下。

分析

排查服务端

首先排除redis的问题,这里单独对redis进行压测。使用redis自带的redis-benchmark

redis-benchmark  -h 172.20.0.1 -p 7001  -t set,get   -n 100000 -q -d 3000 

结果说明服务端完全没得毛病。

排除所有不可能

那看来只能是客户端的问题了。实际上异常里已经带有说明了,只是之前一把梭完全没在意,认为这个文档完全是在帅锅,没意思。再仔细看,StackExchange.Redis/Timeouts,该页面列举了常见的超时错误的几种情况:

  • 网络 / CPU 带宽瓶颈
  • 运行时间较长的Redis指令(可以通过SlowLog指令在redis服务器上查看
  • 大请求挡在多个小请求之前,大请求超时导致后续请求全部被阻塞,所有请求都超时了
  • 线程中busyIO和busyworker导致的超时(.Net平台线程实现机制相关)

显然我们不是前3种,那么虽然很蛋疼,但是现在锅又甩给了.Net本身。

锅在哪里

IOCP & WORKER

简单来说,IOCP(I/O Completion Port)线程是dotnet框架为了协调超快的CPU处理速度和相对来说非常慢的网络、硬盘IO而搞出来的线程使用方式,广泛用于各种网络IO、磁盘IO和文件IO。

推荐阅读:

  1. MSDN: I/O Completion Ports IOCP基本定义和基本用法
  2. wiki: Input/output completion port IOCP概述
  3. I/O completion port's advantages and disadvantages IOCP的优缺点

相对应的,WORKER线程是在你使用了Task.Run(…)或者ThreadPool.QueueUserWorkItem(…)之后用于处理并发线程而产生的,他们也大量被用于CLR中其他有多线程和后台运行需求的地方。

推荐阅读:

  1. ThreadPool Growth: Some Important Details 这篇必读!!!
  2. How to troubleshoot Azure Cache for Redis 这篇有案例分析

结合log分析

对我们的应用来说,总共有3个线程池,

  1. StackExchange.Redis自带的线程池mgr:(10 / 10)
  2. IOCP: (Busy=0,Free=1000,Min=8,Max=1000)
  3. WORKER: (Busy=77,Free=32690,Min=8,Max=32767)

在我们的应用服务器上的情况是,有一个8核CPU,线程池最小情况下有8个线程,StackExchange.Redis空闲,IO也毫无压力,但是worker线程比较忙,在并发数高的情况下由于默认的最小线程池中的8个线程不够用,框架又给应用产生了69个线程,线程池中总共有77线程

dotnet生成新线程的规则是,当有新任务需要线程时,先在线程池中找空闲线程,如果没有空闲的,等待500ms看是否有线程空闲出来,如果还是没有,产生一个新线程

我们的Redis超时设置为5000ms,只需要有10个任务没有及时处理,就超时报错了

由于应用中大量使用了async/await导致应用对worker线程的需求非常大,并发数较低时线程池自己的调度可以及时处理,所以不会出问题,压测时并发数突然升高,线程池来不及调度,导致Redis的request/response来不及处理,触发5000ms超时报错抛异常

处理方法

根据Recommendation 的建议,调整配置文件中最小线程数到100,然后测试、调整、再测试、再调整循环,找到一个适合应用的大小。

注意:

  1. 这里调整的是worker线程,会被整个应用程序共享,并不只是被redis使用
  2. 线程数也不能调整得特别大,线程切换是有代价的,过大会得不偿失

最终

    environment:
      - COMPlus_ThreadPool_ForceMaxWorkerThreads=10000
      - COMPlus_ThreadPool_ForceMinWorkerThreads=2000

其实这里还有一个小插曲,我设置完了环境变量然而无论如何都不生效。
最后一看,竟然是因为 dotnet/aspnetcore#17090 (comment) ,大小写搞错了。。。
/😂

python创建空对象

背景

对于动态语言来说,我想创建空对象 这个需求应该是非常常见的,在大多数场景下,由于数据复杂度的制约,往往无法提前得出数据格式,或者数据格式有效变动,这个时候,动态的创建一个对象,用以包装是非常有效的做法。

样例

php空对象

以php为例,我们需要给客户端返回一个临时的json,那我们就可以通过创建空对象的方式

  $obj = new StdClass();
  $obj->status =0;
  $obj->msg='你懂的';
  return $obj;

然后,在Response里统一渲染,可以选择为json,xml,yaml等,非常的方便。

javascript空对象

对于js来讲,空对象更是简单的一塌糊涂,而且空对象也是最最常见的代码了。

let user = {}
user.name='xxxx'
user.age=18
user.sex='男'
user.favor='女'
user.jj='18cm'

同样地,可以通过

let user = new Object();
user.name='xxxx'
user.age=18
user.sex='男'
user.favor='女'
user.jj='18cm'

等等方式,相应地,第二种方式更加的直观些。

现场

然而,事故现场开始了。在python中,如果你想做到同样的事情,就有点匪夷所思了。

例子

我所遇见的是一个非常常见的场景,wtforms中,在创建Form时,如果传入一个对象obj,则会将这个对象属性相应的字段设置为默认值。原文是

obj – If formdata is empty or not provided, this object is checked for attributes matching form field names, which will be used for field values.

妈卖批,他要求传入的不是一个map,而是一个对象。而实际上,再它的例子下,一切都显得那么完美。

def edit_profile(request):
    user = User.objects.get(pk=request.session['userid'])
    form = EditProfileForm(request.POST, obj=user)

    if request.POST and form.validate():
        form.populate_obj(user)
        user.save()
        return redirect('/home')
    return render_to_response('edit_profile.html', form=form)

它的user刚好是提前定义好的场景,这自然没有什么问题。我上我也行!

需求

这种情况下对于通常表单自然是ok的,但是,假如,现在某个表单它的字段并非固定的,而是需要从数据库动态获取的,根据设置,可能随时有数量不同的各种表单展示,这个时候又能怎么办?很自然的,我们想到了,python是一门动态语言,balabala的。

尝试

显然,我们照猫画虎,也参考其他语言的,new 个基类

user=object()
user.age=18
user.sex='男'
user.favor='女'
user.jj='18cm'

然而

AttributeError: 'user' object has no attribute 'age'

妈卖批!我当然知道没有。然后我去查了下文档,各种balabala,python2和python3各执一词,完全是懵逼的状态。好吧,只能自己动手

user=object()
stattr(user,'age',18)
AttributeError: 'user' object has no attribute 'age'

okok,继续

user=object()
user.__setattr__('age',18)
AttributeError: 'user' object has no attribute 'age'

mmp,继续

user=object()
user.__dict__['age']=18
AttributeError: 'user' object has no attribute '__dict__'

黑人问号????

然而我尝试了各种方法都不能够设置属性,那这个object存在的意义是什么???此时我的心情是这样的
why

貌似解决了

这时,一些很‘聪明’的家伙出现了,它们甚至在暗暗嘲笑我的智商,只闻键盘啪啦一顿响之后,(插播一条广告,键盘尽量选择hhkb或flico),得到了一个貌似很好的解决方案

class EmptyClass:
    pass
user= EmptyClass()
user.age=18
user.sex='男'
user.favor='女'
user.jj='18cm'

why

python的优雅哪里去了?
python的优雅哪里去了?
python的优雅哪里去了?
那些叫着Life is short, you need Python 的人哪里去了???
那些叫着Life is short, you need Python 的人哪里去了???
那些叫着Life is short, you need Python 的人哪里去了???

稍微好一点的

好吧,看来,这个问题已经非常神奇了,那去社区看看吧,看看别人怎么解决的,最终,我在这里找到了一些哭笑不得的答案:

>>> b = type('', (), {})()
>>> b.this_works = 'cool'
>>> 

谁能给我解析一哈type('', (), {})()是什么???

obj = lambda: None
obj.test = "Hello, world!"

还有这样的

import types

x = types.SimpleNamespace()
x.happy = True

print(x.happy) # True

del x.happy
print(x.happy) # AttributeError. object has no attribute 'happy'

事实证明,以上三个都能解决问题,虽然我完全不能够理解这立在的逻辑关联关系。

后记

有过哪位有更加优雅的方案,请一定不吝赐教。

Life is short, you need mmp

Readme建议写稍微详细点?

cd blog
npm install

    修改源码中issue地址为你自己的
    打包js并上传index.html和static目录

就加这句commit说明也好一些啊 "config.js用以配置github用户名和repo名"

sf双十一解密答案及详解及分析思路

前言

今天上班百无聊赖的在群里发现一个有趣的链接光棍节程序员闯关秀,点开之后浑身颤抖如获至宝啊。
我最喜欢这种挑战了。
花了一个小时的时间,终于全部解密。下面奉上思路和分析以及代码。由于sf良好的前端氛围,这里全部用JavaScript作为工具语言。
喜欢python的朋友可以参见我另一篇用python作为示例语言的解密一个有意思的解密
话不多说,时间宝贵,我们立马开始解密之旅吧:)

第一关

光棍节程序员闯关秀第1关
1a
作为一个web入门的学员,我们自然而然的直接右键查看源文件:

<html>
<head><title>光棍节程序员闯关秀第1关(总共10关)</title></head>
<body style="background: #172024; color: #54BA3E; font: 100%/1.5 Menlo, Consolas, Courier, monospace; text-align: center; padding: 10% 0 0 0">
<h2>光棍节程序员闯关秀第1关(总共10关)</h2>
<u>提示: 从所有信息中找到进入下一关的方法</u>
<p><a style="color: #172024" href="?k=e70030d49158de95087eae6469f5319e">进入下一关</a></p> 
</body>
</html>

而实际上,我们不查看源文件也是可以的,直接在页面上ctrl+a,就可以让进入下一关的链接变蓝,直接点击即可。

第二关

光棍节程序员闯关秀第2关
这一关同上,我们也是先直接右键查看源文件,幸运的是,它写在了注释里。

<html>
<head><title>光棍节程序员闯关秀第2关(总共10关)</title></head>
<body style="background: #172024; color: #54BA3E; font: 100%/1.5 Menlo, Consolas, Courier, monospace; text-align: center; padding: 10% 0 0 0">
<h2>光棍节程序员闯关秀第2关(总共10关)</h2>
<!-- 不错嘛,密码在此:4c29dbaf326fe76232390dac0917e921 -->
<!-- 强插广告: 欢迎访问 http://segmentfault.com 或者 http://sf.gg -->
<p>密码在哪呢?</p>
<form><input autocomplete="off" placeholder="输入密码" name="k" /></form>
<p><a style="color: #172024" href="javascript:alert('你太天真了');">进入下一关</a></p> 
</body>
</html>

我们复制出密码,然后填在上面网址的k=后面,k参数应该是代表key,也就是密码的意思。在以后的几关里我们都是用这种方法来完成跳转。

第三关

光棍节程序员闯关秀第3关

页面上说,这关就没有那么简单了。显而易见的,我们还是查看源代码,然而他们这次没有给我们任何提示,一无所获。
这一关才开始登堂入室了,我们开动大脑想一想,这个密码会藏在哪里呢?
开动脑洞分析吧:

  1. cookies
  2. Storage
  3. css
  4. http头
  5. console
  6. ...

我们一项一项,最终在http头里找到了疑似密码的字段:

Content-Encoding:gzip
Content-Type:text/html; charset=UTF-8
Date:Fri, 11 Nov 2016 05:23:01 GMT
The-Key-Is:a87ff679a2f3e71d9181a67b7542122c
Transfer-Encoding:chunked
X-Hit:sf-web1

3
图中部分即时我们要的key。
输入网址中,我们即可来到第四关。

第四关

光棍节程序员闯关秀第4关

这一关让我们观察我们密码的规律。
实际上不用他说我们已经发现,这些密码都是32位的,非常像一个md5有木有?
那么规律到底是什么呢?我记得上小学的时候就经常有这种找规律的题,我们先推断一下,规律可能是:

  1. 当前的md5是上一个md5的结果
  2. md5是某个有规律的数的结果

我们分别测试最后我们发现

md5("4");//a87ff679a2f3e71d9181a67b7542122c

那显然易见的是,下一关5是

md5("5");//e4da3b7fbbce2345d7772b0674a318d5

其实如果各种尝试都无法猜到规律的话,还有一个撞运气的做法,就是我们去md5解密的网站上试一下,a87ff679a2f3e71d9181a67b7542122c
的结果为4。也可以得到相同的结论。
但是这种方法只是一种碰运气无奈之举,因为md5是一种校验算法,已经破坏了数据的原始结构,再不可能还原成原来的结果。
所谓"解密"就是穷举法,自己用md5分别加密常见的字符串再将结果以key=value的字典方式保存到数据里,然后等用的时候再从这里面查找,
看是否有已经碰撞出结果。具有很大的偶然性。

第五关

光棍节程序员闯关秀第5关
这一关开始,就变得比较难了起来。
首先看到一个二维码,我相信大多数人都和我一样,先扫为快。但是手机扫描二维码非常耽误我们时间,而且不好分析。
我们百度搜索在线二维码解析,然后传上去这个图片,结果竟然是:...

http://sf.gg/你被耍了什么都没有

我了个去,我还不信邪,分别测试了

md5("http://sf.gg/你被耍了什么都没有")
md5("你被耍了什么都没有")

然后又按照第三关的步骤检查了一遍还是一无所获。
看来玄机确实就在这个图片本身上,我们下载这个图片,然后右键,详情,看看密码会不会在这些字段里。
尴尬的是里面竟然什么都没有...
等等,什么都没有?说明这个头片显然是以一种非正常方式生成的。我们用十六进制的方式打开它。
此类工具有很多,此处我使用的是WinHex,发现里面有个字符串:

KEY:bdbf46a337ac08e6b4677c2826519542

它是ANSII编码的,所以能被直观的看到。也就是说,其实我们用系统自带的记事本可以看到这个字符串的。
根本不需要WinHex之类的工具。然而我这里主要要表达的是一种分析思路。如果它这里是用的unicode编码,或者含有中文时用的utf8编码,
用记事本就不一定有效了。
还是建议大家掌握更多的诀窍和思路,结果并不重要。通关也不是目的,而是在这个过程中学到了什么东西。

第六关

光棍节程序员闯关秀第6关

f4de502e58723e6252e8856d4dc8fc3b, 只能告诉你这么多

我们同样的用第三关的步骤检查一遍,结果并没有得到有用的信息。
看来玄机就在这个字符串上了。。我们还是老样子,去解密下这个md5,然而这次就没有那么幸运了。没有能找到对应的明文。
实在没办法了,我们只能利用强大的搜索引擎了。。。。
坑爹的是我们竟然找到了这个,第一条:

6

看来是他防水给我们通过了,好吧,虽然我到现在都没弄明白这个key是怎么算出来的。。。

第七关

光棍节程序员闯关秀第7关

有问题就Google是个好习惯! 再试试 ba9b101dd284c566b78042d278e422bd

好吧,看来上题本意就是让我们谷歌啊。好吧,我们就按照他说的,继续谷歌ba9b101dd284c566b78042d278e422bd
然而时间过的很快夜幕就要降临,我们还是没有找到有用的信息。
好吧,我们注意下,再试试后面有个空格,有没有可能不是让我们试后面的关键词的呢,那后面的关键词又是什么?

第八关

光棍节程序员闯关秀第8关

上一关让我们学习到了听话并不是一个好习惯。举一反三桀骜不驯多么重要,当年孙悟空要是没明白那三下,说不定我们现在还是唐朝。。
这一关他说

有时候事情就是这么简单
钥匙就在手里, 门却不知所踪

我放佛看到了他嘴角嘲弄的笑。嗯,忍了。
我们同样查看源文件,

<html>
<head><title>光棍节程序员闯关秀第8关(总共10关)</title></head>
<body style="background: #172024; color: #54BA3E; font: 100%/1.5 Menlo, Consolas, Courier, monospace; text-align: center; padding: 10% 0 0 0">
<h2>光棍节程序员闯关秀第8关(总共10关)</h2>
<p>有时候事情就是这么简单</p>
<p>钥匙就在手里, 门却不知所踪</p>
<form method="GET">
<input type="text" name="k" value="f57c633b47691a70744a04128e491c32" />
</form> 
</body>
</html>

一看我们就笑了。

狗子不要搞事
那么大的GET,你当我傻啊。我们把这个GET改成POST,回车一下,轻松过关。

第九关

光棍节程序员闯关秀第9关
嚯,这一关厉害了。目测大多数人要栽了。这一关也是最难的一关,也是我要写本文的目的。
对于一个普通的web前端来说可能是有些难了,因为这里面牵涉到很多其它的知识。幸而我不是一个前端。
6
然而我已经看穿了一切!
首先 0 1 0 1 这种的明显是组二进制,下面有缺失的部分,我们先不管,我们先看看前面几个,打开计算器,把这些二进制转几个到十进制看看。
发现它们全是位于ascii表的可见字符范围。

32-126(共95个)是字符(32是空格),其中48-57为0到9十个阿拉伯数字。
65-90为26个大写英文字母,97-122号为26个小写英文字母,其余为一些标点符号、运算符号等。

大学c语言入门课程,大家要记牢,以后用的地方很多。
我们试着译出前面不缺失的这部分,结果为

q6GDLaJ4yq9A7xFAnxyvsc/AT

我去,这什么鬼,这么长的部分明显不是key,那是什么呢?而且乱七八糟的像个密文。假使它是个密文,它又是什么加密的?
我们注意到中间有个/,我们所知的BASE64码中间可以出现\,并且根据base64的原理,它最后加密结果一定能被4整除。
我们看了下,共有8112组2进制数据,是个能被4整除的数。同样的,据其原理,如果原字符串的长度不足,可能要在最后补一到两个等号。
有了以上这些线索,我们直接去最后一个二进制看看是不是等号。
然而最后一个却是00____01,很明显是需要我们补齐中间四位的。看来这就是算____所代表数的契机啊。看来我们的路子走对了!!
下面我们在console上执行:

let code ="=".charCodeAt();
console.log(code);//61
console.log(code.toString(2));
VM335:2 61
VM335:3 111101

由此可知____代表的应该是1111。哈哈,到时符合主题:双十一。
由此,我们已经嗅到了胜利的气息,想必马上可以到最后一关了。
耶
我们把上面的二进制列表复制下来,用替换,来编辑成一个数组:

let bin=["01001000","00110100","01110011","01001001","01000001","01000011","01001010","01001011",
"01101110","01101100","01000001","01000001","01000001","00101011","00110001","01011010",
"01000010","00110001","01010010","01010100","01010011","00110111","01100011","00101011",
"01001001","01010001","01000111","01101011","01101001","01000110","01010001","01010110",
"01000101","01001001","01101101","01000001","01100111","01001000","01010010","01000011",
"01000100","01010110","01001011","01101011","01000110","00110110","01101011","01000010",
"01010001","01110010","01010111","01000101","01000101","01000011","01000011","00110000",
"01010001","01000010","01001011","01110001","01101111","01101111","01001011","01001011",
"01101001","01101111","01100111","01010101","01010001","01010101","01000010","01000001",
"01100111","01010101","01100111","01010000","01010011","01000111","00111000","01010111",
"01010101","01001111","01101100","01100101","01110101","01100111","01101001","01001011",
"01011000","01000010","01010110","01110000","01001001","01101000","01100001","01110001",
"01110001","01001101","01100111","00110111","01000001","01010110","01010100","01110101",
"00101111","01100110","00110011","01001100","01100101","00101011","01110110","00111001",
"00101111","00110001","01110000","01110110","01110110","01100010","01110100","01011010",
"01100011","00101011","01011001","00110111","01100101","00110011","00101011","01111010",
"01011010","00101011","00111001","00111001","00110101","01110011","01111001","01011010",
"01001100","01000111","01010010","01101011","01011010","01011001","00110101","01100111",
"01001101","01100010","00110111","01110101","01001101","01110000","00110101","00101011",
"00110111","01110011","01000011","00101111","01010011","01100101","01010100","01101011",
"00110101","01001010","01010001","01010110","01000110","01100101","01000111","00110000",
"01011000","01101011","01010110","01011010","01100001","01100001","00110010","01011000",
"01010001","00110110","01111010","01100110","01110010","00110100","01101011","00111000",
"01000001","01100111","01000111","01011000","01010010","00111000","01100111","01110000",
"01110001","01101001","01100111","01110001","01001011","01101111","01000111","00110011",
"01100011","01000100","01101100","00110101","01000010","01010001","01010111","01000101",
"01000011","01100111","01000011","01011000","00101011","00110011","01100011","01000110",
"01110100","01000110",.........,"01000001","00111101"];

接下来,我们将它转为十进制,然后再转为字符,再拼接到一个字符串。

let str="";
for(i=0;i<bin.length;i++){
    let strCode=parseInt(bin[i],2);
    str+=String.fromCharCode(strCode)
}
console.log(str);

输出的结果是:

q6GDLaJ4yq9A7xFAnxyvsc/ATFJoWvu4oXwI5bmh3y3vkU7ETQsY2mwxMcBdiPrwmAqdzQR8dOQOz5BZKwppO71QdJmnWaQ3pWE58+josmD3QP+b+sHRdA0ZP/sWRwk8e1kEo4lkxQTKtK/nCB2u667E/BmuuKiO3mtcqu/QFfa+luZIng6rY83z1+sMQt6bR79I6DLRnVS9eTFpOi7qtO4Rvaq5EvWBMRfer8fs9G3IwcevwUQwlOWu+RWJFzhkI6NAyJTDLQF9dNXdqnrj3Uy2vSqDv3W7T5vX1Ud0Ps+tiRXR/ZRXzpLQY+rAERH7YJwXWFCChV4qqjYYEt0q3trMxiRwQIOJu4i+QKJix8ASQk1XdOV68KnAhZbDkdeVHsnpuCXZ2rfwl6dKwfrPuNoilZxUs2KM94qX0dmJk8L746LD33kOxrENq319IJxRYS9j0JORrWVle/NCrfB8fvy3oXd7q2MsZx/eTLerZAoiSCfXjcD0YWcybnBk6ItxjqKaGIa3IQviK+IO1nCddtTyLkvbYyi120I2UdgLK4ycsg1gkp5RZjIWMp1hvll++aYMOqO5gOJ6fmBGfi4tdJwS77IDGC886M2LSCtkjmlYroplYjh0AmWR9yoysdiL18vNgvl2WWTze8/BU8lPYneIATbo7jOv2cykO+QMZhuk6M1a02rTI0Mujt9++6mtgbHb1sy06LNPbz6swrVPd0cj4Vo0qd/2upRGfYkqJsYzaUnUv6z0vgFRbiuPULOND+9zXomT0CjJ7trXxMj0psGuTOoBhgZDdTTujO9S3PnHyRNZ0U/M3LO64NbKwgIt1g5ovEJOTF6H/afhQdsVfFLkQaoOvz1WoviTm3C19Wc7x1SbLclMCdM2ipIHqpcUdscJSp/ILZmKaldwTK0cSl26IOlgmkSRykEIa8j5N2GGJqsF3ZINDnca02Uv1zuFRicsGU2nuaYNPJ555WAgMJp6gPQtn+UoG+WzDde0cmwT/7dZbZETHhyZLlxfWrpRZs+09wcgVoHykqbzF2ayrj5KT++xNRiRu2Mu3/Jin2gP4bpJOE8f2c97lpdUee09Oyn6xTcHanLlzr21HrqQs6idt/irWU8x9kb2uxx8XOma4vDMpTb6rXT0C8GbmnhGCYTv/PRQpXjas+YR5zgONXpeq+iFiScxyflH+9x8dB9xdMA4KqILRTLD3Vrbw7uJFkq5uJM8rzjPSDJgbsXVRxj4f7KZUiQqSBhFdyydOmr0uXf8GKME8sv+rSYsvJmzF2yrZx+iWgVETlruZw3t29Nb3Nt5FE4W9DCO3xuAhrOyyzco3V1KmmItS8keYu+IJVVPR53xlBgYCX36PK7e44j4O81P87mPHiTyEgdH7hTpv6o5Fi+ij0FEnLdgnrj3csq0v6KwSk5KeWpfFLdtYmAka25aQqeGvCO+SGcxhxwxdiq22dZlb7hAcL6MkpNTmIZsSofOC56nCCfNiBi8TESZZNdiyUwyLqbpKbfBpTS1U0E8yGZS81bb3vkvFw8mt5ZzhByEx5uNLi1ryQqO9lalvuLRc49j11R+d1TB6vCRB25YTnaH59YemLiE6NHe+ITBQKfk3/BhIT6kxcIdXG1CJsTpW2eFt8iFKbTlCihj3YXDYow+ygfDoyypfLGQiVznNDQl9dShA46GsPjYzPT+YnbOA2+IoYGUisdC9WxtW+9nRQRVHN+1CzzP7wgc5DROWcxXD+yhPvn8QpMwN21bNChzY/Ehdlwq98m9BaqH78zCdvIl1xhby2c4HUr+5InETi8FzfSCFtH7IaxGguF50fwJBu+yxkynXRoUl9nVPj6ptKmuOXg+Xs+7kDWqKmZL3qEU2ToFPjpBCfmM7JGivEcn5peDa0YH+6Ki+Qxi7Azp/UYO3yTejeeTkPxKnaeuLGp1Rs0jfYtDYtDumc8cnJ6KtgrZP1mJQ4tn1cQ0MjEzaQBtBZ+Pa5rltS6npfC/MccviUmUBsXOfFW73puTw6BZy72UN3Dr6TDX5XjcFQm6k3c6jXYnFzhGXg1Rmrm/ZYycqqhHjhXBQmG33vi3QXOVentscrrTfElaQ9bGJCmD1gh3nWpzu7FLyh+QnebbJuzUL15Md5OM81KnQzQ9dYCgd8LpndN2p310srHqbGonhbpd8+N7fimuMnVvHCXl/KOYhmhWrhbkRG92aa8M+ulRoQz+6+WK2sk30NhJu4QupFK/lUX6OQk5SnnIPD7ztLlyPHBd1IZXOS12i1b+Qr5U/Oen7QsD/BHGq4BhJF63RrVFTEPhvZMialxwX5x0XFUvvJzd2fnk8Ua4wfP+eGMBc8/S3pLXpZWP4iQfsORemaovgWZCA1W7BuqYxccx7TEh3u+wX6SeJAnNryQb9BQPN2t36fi/tahUsSqrVdlhIRCeYZyKFL6CGNeoaZ8Nqw99Yqi/661zjuGiKQu1xa/q4lhyR9K3t9fCGafbcnTxSneL77lGjourN+MdxhhEGcXGnD8QwyQ9o5DJy1aXPZZ8Cjs0fot+w79kdUuykp2U0Ek0s/l46ZaXSA3v8DnCxCEBt+AYwf26ddCCUw8+KFlevZuCODxtqPFyulb47OldzbnRi8UpuFbWbTwd+FPSpbtvDzRVNFnShZ6oKDQcqWNG3A5QLOMzaymWN3fbz8bo6Woqcjmmv/a1v/P4kmoYWnjXOw1+rfCsysovWnEphsKX70fN49tU03iE8QJh3FbO7g6oHXMl42IUwCB7eNZitpBsZXaBntvKCKPPbQn5VbMcMRAtz1DoycZ8ttJn8deIcTIu/6JfGnu3mwHYXVNIZaq+ZHvrEANxf48+2fR+TNM1420mNUhkVtRo0oTjnrvhuDPv5huzoiRePdWrSFkd+if/cfpL/pK/5C/5S/5/yH8BbZ04JAAoAAA=

接下来我们百度一个在线base64解密工具。
等等,解密出来是一串乱码?

why

对嘛,这么长的一个base64显然里面不是只有一个key啊。可能需要经过我们再次加工,那么既然它不是一个字符串,很有可能是个文件。
于是我们选择

9

解密为16进制显示,然后我们注意下结果里面的1f 8b 08,所有二进制文件的头部的几个字节都是文件头,一般作为识别文件用。
我们百度文件头大全,然后发现1f 8b 08代表它是一个gz文件。
那接下来我们单纯使用控制台是不行了,我们用node环境来把这个buff输出到一个文件中去。

var b = new Buffer(str, 'base64');
var fs= require('fs');
fs.writeFileSync("test.gz", b);

然后用一个解压缩工具解压后,即可得到一个图片:
10
为啥这个女的有点眼熟呢?好啦不管了,反正它上面的就是密码,我们敲下来完成这一关。

第十关

恭喜, 你已经通过了所有关卡

然而第十关竟然是个广告。。好吧!

结语

写这篇文章用了我好几个小时的时间,键盘都敲碎了,大哥们还不支持下?嘿嘿嘿。

工具

第九关脚本

ss从入门到使用

简介

ss全称Shadowsocks。他的用途嘛,就是一把梯子。
笔者使用尊重并拥护政府的决定,但是因为工作的原因,经常需要到国外的网站上查找一些资料,再加上实在无法忍受国内诸如百度,360等流氓企业的流氓做派,所以就需要这么一个工具。由于经常需要配置,每次都需要重新去下载和配置,所以索性整理一份资料放置到这里,留待后用。
其实之前也有许多类似的工具,比如go-agent等。但这个小工具为什么如此流行的原因,我想却是因为它足够:

  • 快速,主要是因为他是异步的
  • 安全,流量均经过加密,然后在服务端解密。而且支持自定义加密算法。
  • 跨平台,它不仅支持windows而且maciosAndroid 、甚至支持路由器。

然而它为什么牛逼呢?因为这是一款开源的工具

相关链接

服务端搭建

安装python环境

安装python环境的方案有许许多多种,甚至部分系统还自带,不在本文的讨论范围之内。这里仅以最常用的办法。
Debian / Ubuntu:

apt-get install python-pip
pip install shadowsocks

CentOS:

yum install python-setuptools && easy_install pip
pip install shadowsocks

配置

安装好之后其实已经可以使用了。

sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start

然而这样太麻烦了,我们常用的是用配置文件直接把以上参数全写进去。
我们在任意一个文件创建一个json文件,但符合规范的是在/etc/shadowsocks.json
然后在里面写上。

{
    "server":"0.0.0.0",
    "server_port":8388,
    "local_address": "127.0.0.1",
    "local_port":1080,
    "password":"mypassword",
    "timeout":300,
    "method":"aes-256-cfb",
    "fast_open": false
}

具体解释如下:

Name Explanation
server the address your server listens
server_port server port
local_address the address your local listens
local_port local port
password password used for encryption
timeout in seconds
method default: "aes-256-cfb", see [Encryption]
fast_open use [TCP_FASTOPEN], true / false
workers number of workers, available on Unix/Linux

使用

ssserver -c /etc/shadowsocks.json -d start
ssserver -c /etc/shadowsocks.json -d stop

开机启动

和一般的linux脚本开机启动并没有什么不同,此处我们采用修改rc.local文件的办法。

vi /etc/rc.local

然后里面写上

#!/bin/sh
ssserver -c /etc/shadowsocks.json -d start

客户端

平台 方案
windows Shadowsocks-csharp
MacOS Shadowsocksgui
安卓 影梭

记一次坑爹的socket连接异常

事情是这样的,在docker里,socket长链接建立成功后,无论发送什么,都得不到响应。同样的bin跑在宿主机则没有问题。
非常的神奇。
冷静分析,造成此问题的可能因素有:

  • docker内网络配置有误
  • 代码有逻辑bug,实际没有发送成功
  • 时区不一致影响双方握手
  • 代码无误,但提交到缓冲区后系统不读取
  • 玄学

为了解决此问题,逐一验证,最终证明是玄学。
证明过程如下:
试图使用WireShark抓包分析,然而根本没有相关连接被建立。同时宿主机连接正常,排除服务端出错。
docker里ping和curl能正常被WireShark捕获,且响应正常
以上证明与网络无关,因为根本没有数据包被发出。
调试代码,分析后确实将数据流给到了系统,系统已经完成drain,然而就是不发。。
换了几个不通系统的底包ubuntucentos分别尝试后,均无法解决。
将docker部署在其他电脑上后,工作正常。

由此,得出结论,我的电脑有问题!至于什么问题,可能是docker的bug,也可能是玄学。

docker ver

关于ios下dlopen函数的探究

内容摘要

近日我在开发一个小工具的时候,用到了一个加载动态库的函数dlopen,基于我的windows平台的开发背景,我想当然的认为其返回值是当前moduleload address,即模块基地址。然后经验证后发现,其和windows下的LoadLibrary还是有本质的差别。那么,它返回的究竟是一个什么东西呢?又该如何通过这样的返回值,来顺利获取到我们需要的load address甚至其它更重要的信息呢?带着这样的疑惑,我与几位资深经验的开发者对这个问题进行了深入的探究。本文是探究思路和研究过程的记录。在读文本文后,你应当会对此方面了解有所加深,并且上述疑惑应当能够释疑。

前知储备

本文面向有一定技术水准的读者,所以不再赘述模块基地址等相关定义。但相对地,下文出现的一些术语或者俗语,在此做一个简短的说明,如果需要进一步了解,请参考附录或者自行检索相关资料。

  • aslr(Address space layout randomization),加载地址随机化,通俗来讲,就是同一个模块在每个进程每次被加载后的地址(可能)不一致。
  • load address,模块加载地址,即模块基地址,也等同于header address
  • patch 补丁,这里名次动用做打补丁

为什么需要load address

在通常开发情境下,一般地代码片段如下:

int main(int argc, char **argv) {  
        void *handle;  
        bool (*fuck)(girl);  
        char *error;  
      
        handle = dlopen ("libfuck.dylib", RTLD_LAZY);  
        if (!handle) {  
            fprintf (stderr, "%s ", dlerror());  
            exit(1);  
        }  
      
        fuck = dlsym(handle, "fuck");  
        if ((error = dlerror()) != NULL)  {  
            fprintf (stderr, "%s ", error);  
            exit(1);  
        }
        girl* xx= make_girl_byname("xx");
        fuck(girl);
        dlclose(handle);  
        return 0;  
    }  

但特殊情况下,我们无法通过dlsym来完成符号的寻找。如该模块根本没有导出符号,或者我们想要显式的调用某个未导出符号的函数,或者我们需要patchheader中的某个字段等情形时,我们就需要知道该模块的load address

如何获取load address

由于aslr的原因,现行通用的解决方案是通过遍历模块来获取。典型的代码片段如下:

int32_t nModNums= _dyld_image_count();
const char *pszModName = NULL;
intptr_t pModSlide = 0;
const struct mach_header* pModHeader = NULL;
    
for (uint32_t i = 0; i < nModNums; i++)
{
	
	pszModName = _dyld_get_image_name(i);
	if(!strcmp(pszModName,"/path/to/xx.dylib")){
    	pModSlide  = _dyld_get_image_vmaddr_slide(i);
    	pModHeader = _dyld_get_image_header(i);
    	NSLog(@"[FLWB]:vm_slide:%lx,load addr:%p",pModSlide,pModHeader);
	}

}

这样,我们就通过对比名字的方式,来获取到了header address
然而我们思考一下,这样做真的没有问题吗???
我认为,起码有两三个问题。

  1. 遍历导致的性能损失。
  2. 此处至少需要调用三个api来完成操作,而此三个api有较明显的特征,非常容易被hook针对。
  3. 也是最重要的,此处使用的几个api是线程安全的。这里是一般情况下我们不会注意到的,实际上,这个是一个常见的安全漏洞。而文档上也对此有说明
/*
 * The following functions allow you to iterate through all loaded images.  
 * This is not a thread safe operation.  Another thread can add or remove
 * an image during the iteration.  
 *
 * Many uses of these routines can be replace by a call to dladdr() which 
 * will return the mach_header and name of an image, given an address in 
 * the image. dladdr() is thread safe.
 */
extern uint32_t                    _dyld_image_count(void)                              __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
extern const struct mach_header*   _dyld_get_image_header(uint32_t image_index)         __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
extern intptr_t                    _dyld_get_image_vmaddr_slide(uint32_t image_index)   __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);
extern const char*                 _dyld_get_image_name(uint32_t image_index)           __OSX_AVAILABLE_STARTING(__MAC_10_1, __IPHONE_2_0);

大意是要警惕在遍历时其它进程的添加或者修改操作。那么。显然地,假使我们在遍历的过程中,其它线程删除了一个模块,那么将导致我们的索引移位,得到一个完全不匹配的结果,同时也有可能造成内存访问错误。

那么,要想同时解决以上三个问题,(我认为)最优秀的解决应当是通过dlopen返回的结果来获取。从推理上来讲,dlsym既然能通过这个参数来获取到符号信息,而符号表是需要通过header来计算的。换言之,通过这个dlopen的返回值一定可以获取到header。搜索了一番,网上对此没有任何资料。
但理论上行得通的事情,实际上应该是可以的。于是我开始了探寻。

探寻过程

有了上文的资料,显然,dlopen的返回值必定是一个结构。我们由此来做发散思维。与此同时,我与几位大佬交流后,他们也都给出了非常重要的建议。

头文件及注释

这个是一个最简单的方案,我们去看一下dlopen的头文件声明,其位于/usr/include/dlfcn.h

extern int dlclose(void * __handle);
extern char * dlerror(void);
extern void * dlopen(const char * __path, int __mode);
extern void * dlsym(void * __handle, const char * __symbol);

然而这里面定义它为void* 类型,且变量名字为handle
我们幻想中此处应为一个结构体指针的美梦破碎了。

借用linux头文件

此方案由咸🐟提出。

On Linux, dlopen doesn't return the address where the ELF binary was loaded. It returns struct link_map instead, which has .l_addr member. So you'll want something like:

struct link_map *lm = (struct link_map*) dlopen(0, RTLD_NOW);
printf("%p\n", lm->l_addr);

However, despite what comment in /usr/include/link.h says, .l_addr is actually not a load address either. Instead, it's the difference between where ELF image was linked to load, and where it was actually loaded.

看到这份资料的时候我还是比较兴奋的,因为这无疑证明了我们的推断是正确的。然而我一番寻找,发现在macos下根本没有link.h这个头文件。一番搜索没有答案之下,我们安装windows subsystem然后顺利找到这个头文件。
其中关键部分如下:

struct link_map
  {
    /* These first few members are part of the protocol with the debugger.
       This is the same format used in SVR4.  */

    ElfW(Addr) l_addr;		/* Difference between the address in the ELF
				   file and the addresses in memory.  */
    char *l_name;		/* Absolute file name object was found in.  */
    ElfW(Dyn) *l_ld;		/* Dynamic section of the shared object.  */
    struct link_map *l_next, *l_prev; /* Chain of loaded objects.  */
  };

这里我们简单的复制此关键部分,并修改为

struct link_map
  {
    /* These first few members are part of the protocol with the debugger.
       This is the same format used in SVR4.  */

    void l_addr;		/* Difference between the address in the ELF
				   file and the addresses in memory.  */
    char *l_name;		/* Absolute file name object was found in.  */
    void*l_ld;		/* Dynamic section of the shared object.  */
    struct link_map *l_next, *l_prev; /* Chain of loaded objects.  */
  };

我们的代码如下:

	void* xxHandle=dlopen("xx.dylib",RTLD_LAZY);
	NSLog(@"[FLWB]:xxHandle:%p",xxHandle);
	struct link_map *lm = (struct link_map*)xxHandle;
	NSLog(@"[FLWB]:%s = %p",lm->l_name, lm->l_addr);

运行之后神奇的事情发生了,模块名字竟然正确的输出了!虽然基地址返回的明显不对。但至少证明了它确实是有组织的。但可能因为苹果做了修改导致了不同。
我们继续尝试,发现除了名字奇迹般的正确输出以外,其它的无一相符。
这里提一句题外话,我们显然可以看到linux下的设计还是比较精巧的,它使用了链表来存在模块信息,方便插入和遍历。这一点上和windows是一致的。在此情形下,我们需要隐藏模块的话,就需要修改相邻的两个链表左右节点,也就是所谓的“断链”。有兴趣的话,可以自行研究。

借用anroid头文件

linux的头文件无法适用于ios,是否是因为版本的问题呢,android是否可行?一番搜索之下我们找到来安卓的对应头文件

struct soinfo {
 public:
  char name[SOINFO_NAME_LEN];
  const Elf32_Phdr* phdr;
  size_t phnum;
  Elf32_Addr entry;
  Elf32_Addr base;
  unsigned size;

  uint32_t unused1;  // DO NOT USE, maintained for compatibility.

  Elf32_Dyn* dynamic;

  uint32_t unused2; // DO NOT USE, maintained for compatibility
  uint32_t unused3; // DO NOT USE, maintained for compatibility

  soinfo* next;
  unsigned flags;

  const char* strtab;
  Elf32_Sym* symtab;

  size_t nbucket;
  size_t nchain;
  unsigned* bucket;
  unsigned* chain;

  unsigned* plt_got;

  Elf32_Rel* plt_rel;
  size_t plt_rel_count;

  Elf32_Rel* rel;
  size_t rel_count;

  linker_function_t* preinit_array;
  size_t preinit_array_count;

  linker_function_t* init_array;
  size_t init_array_count;
  linker_function_t* fini_array;
  size_t fini_array_count;

  linker_function_t init_func;
  linker_function_t fini_func;

#if defined(ANDROID_ARM_LINKER)
  // ARM EABI section used for stack unwinding.
  unsigned* ARM_exidx;
  size_t ARM_exidx_count;
#elif defined(ANDROID_MIPS_LINKER)
  unsigned mips_symtabno;
  unsigned mips_local_gotno;
  unsigned mips_gotsym;
#endif

  size_t ref_count;
  link_map_t link_map;

  bool constructors_called;

  // When you read a virtual address from the ELF file, add this
  // value to get the corresponding address in the process' address space.
  Elf32_Addr load_bias;

  bool has_text_relocations;
  bool has_DT_SYMBOLIC;

  void CallConstructors();
  void CallDestructors();
  void CallPreInitConstructors();

 private:
  void CallArray(const char* array_name, linker_function_t* functions, size_t count, bool reverse);
  void CallFunction(const char* function_name, linker_function_t function);
};

然而一番尝试后同样无果,这彻底说明了,至少这部分apple是有自己的实现。

特殊符号定位法

注意,这里的特殊符号不是指的标点符号,而是特殊的symbol
此方案同样由咸🐟提出,并由Zhang扩充。
原理是通过macho里导出的特殊符号来直接获取。具体的符号如

/*
 * The value of the link editor defined symbol _MH_DYLIB_SYM is the address
 * of the mach header in a Mach-O dylib file type.  It does not appear in
 * any file type other than a MH_DYLIB file type.  The type of the symbol is
 * an N_SECT symbol even thought the header is not part of any section.  This
 * symbol is private to the code in the library it is a part of.
 */
#define _MH_DYLIB_SYM	"__mh_dylib_header"
#define MH_DYLIB_SYM	"_mh_dylib_header"

我们修改代码如下:

	void* xxHandle=dlopen("fuck.dylib",RTLD_LAZY);
	void* fuck1 = dlsym(xxHandle, "__mh_dylib_header");  
    void* fuck2 = dlsym(xxHandle, "_mh_dylib_header"); 

结果均获取不到这两个符号,推测为我这边测试环境的问题,或者在链接的过程中被去掉了。

特定符号偏移修正法

此方案由柳下惠提出,原理是通过查找给定的某个符号,然后减去对应的偏移来获取。这种是理论上肯定能行得通的解决方案,但实际上如若是遇到某个模块根本没有导出符号的话,此方案也是束手无策。此处有jmpzws基于此而来的优化方案。即是查找dyld_stub_binder这个特定的符号,align然后减去0x4000。
但同样地,我这边也是无法获取到这个符号。

回调获取法

此方案由Zhang提出,原理是先通过_dyld_register_func_for_add_image来注册一个回调,然后再dlopen即可。因为此回调回调用void (*func)(const mach_header *mh, intptr_t vmaddr_slide)这样的callback,在参数里面刚好就有mach_header,以此来获取模块基地址。此方案从理论上来讲,是完全可行的。但由于操作过于复杂,且(我)没有找到对应的取消注册钩子的办法导致使用代价过大,所以没有深入探寻。

暴力偏移法

多番尝试无果后,现行简单粗暴的方法。即dump出疑似结构的handle的内存,观察load address的位置,然后计算偏移,往后通过偏移来获取。
这种思路类似于游戏外挂的思路,即不管缘由,不考虑理论,先行实现。
于是我们修改代码如下

	void* xxHandle=dlopen("xx.dylib",RTLD_LAZY);
	NSLog(@"[FLWB]:xxHandle:%p",xxHandle);
	NSData *dataData = [NSData dataWithBytes:xxHandle length:0x80];
	NSLog(@"[FLWB]:%p = %@",xxHandle, dataData);

果然,我们在第0x58个成员的位置找到了疑似load address的位置。但这在实际使用上会有很大的问题。其中最为麻烦的是32位和64位的问题,由于类型长度可能不一致,所以在32位下未必是0x58/2,而且在执行时要定义宏或者手动判断系统位数,显然是一个比较繁琐的事情。
但这进一步说明了我们的思路的正确的,一定存在这样的一个结构体。正是由于种种方案给定我们信心,我们才能坚定的走下去。
所以我们下一步就是要找到这个头文件,如果找不到,那我们就需要猜测,然后自行做一个这样的头文件。(反正windows下都是这么做的。)

源码追溯法

正在此时,突然想起来jmpzws曾发的一个截图。

ImageLoader* image = (ImageLoader*)(((uintptr_t)handle) & (-4));	// clear mode bits

那么,这个ImageLoader可不就是我们苦苦寻找的结构体吗?于是我们找到对应的dyld的源码。
我们一番寻找后,发现其原来是个类,头文件如下

/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
 *
 * Copyright (c) 2004-2010 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */


#ifndef __IMAGELOADER__
#define __IMAGELOADER__

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <mach/mach_time.h> // struct mach_timebase_info
#include <mach/mach_init.h> // struct mach_thread_self
#include <mach/shared_region.h>
#include <mach-o/loader.h> 
#include <mach-o/nlist.h> 
#include <stdint.h>
#include <stdlib.h>
#include <TargetConditionals.h>
#include <vector>
#include <new>
#include <uuid/uuid.h>

#if __arm__
 #include <mach/vm_page_size.h>
#endif

#if __x86_64__ || __i386__
	#include <CrashReporterClient.h>
#else
	// work around until iOS has CrashReporterClient.h
	#define CRSetCrashLogMessage(x)
	#define CRSetCrashLogMessage2(x)
#endif

#ifndef SHARED_REGION_BASE_ARM64
	#define SHARED_REGION_BASE_ARM64 0x7FFF80000000LL
#endif

#ifndef SHARED_REGION_SIZE_ARM64
	#define SHARED_REGION_SIZE_ARM64 0x10000000LL
#endif


#define LOG_BINDINGS 0

#include "mach-o/dyld_images.h"
#include "mach-o/dyld_priv.h"
#include "DyldSharedCache.h"

#if __i386__
	#define SHARED_REGION_BASE SHARED_REGION_BASE_I386
	#define SHARED_REGION_SIZE SHARED_REGION_SIZE_I386
#elif __x86_64__
	#define SHARED_REGION_BASE SHARED_REGION_BASE_X86_64
	#define SHARED_REGION_SIZE SHARED_REGION_SIZE_X86_64
#elif __arm__
	#define SHARED_REGION_BASE SHARED_REGION_BASE_ARM
	#define SHARED_REGION_SIZE SHARED_REGION_SIZE_ARM
#elif __arm64__
	#define SHARED_REGION_BASE SHARED_REGION_BASE_ARM64
	#define SHARED_REGION_SIZE SHARED_REGION_SIZE_ARM64
#endif

#ifndef EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER
	#define EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER 0x10
#endif
#ifndef EXPORT_SYMBOL_FLAGS_REEXPORT
	#define EXPORT_SYMBOL_FLAGS_REEXPORT 0x08
#endif

#ifndef LC_MAIN
	#define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */
	struct entry_point_command {
		uint32_t  cmd;	/* LC_MAIN only used in MH_EXECUTE filetypes */
		uint32_t  cmdsize;	/* 24 */
		uint64_t  entryoff;	/* file (__TEXT) offset of main() */
		uint64_t  stacksize;/* if not zero, initial stack size */
	};
#endif

#if __IPHONE_OS_VERSION_MIN_REQUIRED          
	#define SPLIT_SEG_SHARED_REGION_SUPPORT 0
	#define SPLIT_SEG_DYLIB_SUPPORT			0
	#define PREBOUND_IMAGE_SUPPORT			__arm__
	#define TEXT_RELOC_SUPPORT				__i386__
	#define SUPPORT_OLD_CRT_INITIALIZATION	0
	#define SUPPORT_LC_DYLD_ENVIRONMENT		1
	#define SUPPORT_VERSIONED_PATHS			1
	#define SUPPORT_CLASSIC_MACHO			__arm__
	#define SUPPORT_ZERO_COST_EXCEPTIONS	(!__USING_SJLJ_EXCEPTIONS__)
	#define INITIAL_IMAGE_COUNT				150
	#define SUPPORT_ACCELERATE_TABLES		!TARGET_IPHONE_SIMULATOR
	#define SUPPORT_ROOT_PATH				TARGET_IPHONE_SIMULATOR
	#define USES_CHAINED_BINDS				(__arm64e__)
#else
	#define SPLIT_SEG_SHARED_REGION_SUPPORT 0
	#define SPLIT_SEG_DYLIB_SUPPORT			__i386__
	#define PREBOUND_IMAGE_SUPPORT			__i386__
	#define TEXT_RELOC_SUPPORT				__i386__
	#define SUPPORT_OLD_CRT_INITIALIZATION	__i386__
	#define SUPPORT_LC_DYLD_ENVIRONMENT		(__i386__ || __x86_64__)
	#define SUPPORT_VERSIONED_PATHS			1
	#define SUPPORT_CLASSIC_MACHO			1
	#define SUPPORT_ZERO_COST_EXCEPTIONS	1
	#define INITIAL_IMAGE_COUNT				200
	#define SUPPORT_ACCELERATE_TABLES		0
	#define SUPPORT_ROOT_PATH				1
	#define USES_CHAINED_BINDS				0
#endif

#define MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE (32*1024)

#define MH_HAS_OBJC			0x40000000


// <rdar://problem/13590567> optimize away dyld's initializers
#define VECTOR_NEVER_DESTRUCTED(type) \
	namespace std { \
		template <> \
		__vector_base<type, std::allocator<type> >::~__vector_base() { } \
	}
#define VECTOR_NEVER_DESTRUCTED_EXTERN(type) \
       namespace std { \
               template <> \
               __vector_base<type, std::allocator<type> >::~__vector_base(); \
       }
#define VECTOR_NEVER_DESTRUCTED_IMPL(type) \
       namespace std { \
               template <> \
               __vector_base<type, std::allocator<type> >::~__vector_base() { } \
       }

// utilities
namespace dyld {
	extern __attribute__((noreturn)) void throwf(const char* format, ...)  __attribute__((format(printf, 1, 2)));
	extern void log(const char* format, ...)  __attribute__((format(printf, 1, 2)));
	extern void warn(const char* format, ...)  __attribute__((format(printf, 1, 2)));
	extern const char* mkstringf(const char* format, ...)  __attribute__((format(printf, 1, 2)));
#if LOG_BINDINGS
	extern void logBindings(const char* format, ...)  __attribute__((format(printf, 1, 2)));
#endif
}
extern "C" 	int   vm_alloc(vm_address_t* addr, vm_size_t size, uint32_t flags);
extern "C" 	void* xmmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);


#if __LP64__
	struct macho_header				: public mach_header_64  {};
	struct macho_nlist				: public nlist_64  {};	
#else
	struct macho_header				: public mach_header  {};
	struct macho_nlist				: public nlist  {};	
#endif


#if __arm64__
	#define dyld_page_trunc(__addr)     (__addr & (-16384))
	#define dyld_page_round(__addr)     ((__addr + 16383) & (-16384))
	#define dyld_page_size              16384
#elif __arm__
	#define dyld_page_trunc(__addr)     trunc_page_kernel(__addr)
	#define dyld_page_round(__addr)     round_page_kernel(__addr)
	#define dyld_page_size              vm_kernel_page_size
#else
	#define dyld_page_trunc(__addr)     (__addr & (-4096))
	#define dyld_page_round(__addr)     ((__addr + 4095) & (-4096))
	#define dyld_page_size              4096
#endif



struct ProgramVars
{
	const void*		mh;
	int*			NXArgcPtr;
	const char***	NXArgvPtr;
	const char***	environPtr;
	const char**	__prognamePtr;
};



//
// ImageLoader is an abstract base class.  To support loading a particular executable
// file format, you make a concrete subclass of ImageLoader.
//
// For each executable file (dynamic shared object) in use, an ImageLoader is instantiated.
//
// The ImageLoader base class does the work of linking together images, but it knows nothing
// about any particular file format.
//
//
class ImageLoader {
public:

	typedef uint32_t DefinitionFlags;
	static const DefinitionFlags kNoDefinitionOptions = 0;
	static const DefinitionFlags kWeakDefinition = 1;
	
	typedef uint32_t ReferenceFlags;
	static const ReferenceFlags kNoReferenceOptions = 0;
	static const ReferenceFlags kWeakReference = 1;
	static const ReferenceFlags kTentativeDefinition = 2;
	
	enum PrebindMode { kUseAllPrebinding, kUseSplitSegPrebinding, kUseAllButAppPredbinding, kUseNoPrebinding };
	enum BindingOptions { kBindingNone, kBindingLazyPointers, kBindingNeverSetLazyPointers };
	enum SharedRegionMode { kUseSharedRegion, kUsePrivateSharedRegion, kDontUseSharedRegion, kSharedRegionIsSharedCache };
	
	struct Symbol;  // abstact symbol

	struct MappedRegion {
		uintptr_t	address;
		size_t		size;
	};

	struct RPathChain {
		RPathChain(const RPathChain* n, std::vector<const char*>* p) : next(n), paths(p) {};
		const RPathChain*			next;
		std::vector<const char*>*	paths;
	};

	struct DOFInfo {
		void*				dof;
		const mach_header*	imageHeader;
		const char*			imageShortName;
	};

	struct DynamicReference {
		ImageLoader* from;
		ImageLoader* to;
	};
	
	struct InitializerTimingList
	{
		uintptr_t	count;
		struct {
			const char*		shortName;
			uint64_t		initTime;
		}			images[1];

		void addTime(const char* name, uint64_t time);
	};

	typedef void (^CoalesceNotifier)(const Symbol* implSym, const ImageLoader* implIn, const mach_header* implMh);
	
	struct LinkContext {
		ImageLoader*	(*loadLibrary)(const char* libraryName, bool search, const char* origin, const RPathChain* rpaths, bool enforceIOSMac, unsigned& cacheIndex);
		void			(*terminationRecorder)(ImageLoader* image);
		bool			(*flatExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image);
		bool			(*coalescedExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image, CoalesceNotifier);
		unsigned int	(*getCoalescedImages)(ImageLoader* images[], unsigned imageIndex[]);
		void			(*undefinedHandler)(const char* name);
		MappedRegion*	(*getAllMappedRegions)(MappedRegion*);
		void *			(*bindingHandler)(const char *, const char *, void *);
		void			(*notifySingle)(dyld_image_states, const ImageLoader* image, InitializerTimingList*);
		void			(*notifyBatch)(dyld_image_states state, bool preflightOnly);
		void			(*removeImage)(ImageLoader* image);
		void			(*registerDOFs)(const std::vector<DOFInfo>& dofs);
		void			(*clearAllDepths)();
		void			(*printAllDepths)();
		unsigned int	(*imageCount)();
		void			(*setNewProgramVars)(const ProgramVars&);
		bool			(*inSharedCache)(const char* path);
		void			(*setErrorStrings)(unsigned errorCode, const char* errorClientOfDylibPath,
										const char* errorTargetDylibPath, const char* errorSymbol);
		ImageLoader*	(*findImageContainingAddress)(const void* addr);
		void			(*addDynamicReference)(ImageLoader* from, ImageLoader* to);
#if SUPPORT_ACCELERATE_TABLES
		void			(*notifySingleFromCache)(dyld_image_states, const mach_header* mh, const char* path);
		dyld_image_state_change_handler (*getPreInitNotifyHandler)(unsigned index);
		dyld_image_state_change_handler (*getBoundBatchHandler)(unsigned index);
#endif
		
#if SUPPORT_OLD_CRT_INITIALIZATION
		void			(*setRunInitialzersOldWay)();
#endif
		BindingOptions	bindingOptions;
		int				argc;
		const char**	argv;
		const char**	envp;
		const char**	apple;
		const char*		progname;
		ProgramVars		programVars;
		ImageLoader*	mainExecutable;
		const char* const * imageSuffix;
#if SUPPORT_ROOT_PATH
		const char**	rootPaths;
#endif
		const DyldSharedCache*  dyldCache;
		const dyld_interpose_tuple*	dynamicInterposeArray;
		size_t			dynamicInterposeCount;
		PrebindMode		prebindUsage;
		SharedRegionMode sharedRegionMode;
		bool			dyldLoadedAtSameAddressNeededBySharedCache;
		bool			strictMachORequired;
		bool			allowAtPaths;
		bool			allowEnvVarsPrint;
		bool			allowEnvVarsPath;
		bool			allowEnvVarsSharedCache;
		bool			allowClassicFallbackPaths;
		bool			allowInsertFailures;
		bool			mainExecutableCodeSigned;
		bool			preFetchDisabled;
		bool			prebinding;
		bool			bindFlat;
		bool			linkingMainExecutable;
		bool			startedInitializingMainExecutable;
#if __MAC_OS_X_VERSION_MIN_REQUIRED
		bool			marzipan;
#endif
		bool			verboseOpts;
		bool			verboseEnv;
		bool			verboseLoading;
		bool			verboseMapping;
		bool			verboseRebase;
		bool			verboseBind;
		bool			verboseWeakBind;
		bool			verboseInit;
		bool			verboseDOF;
		bool			verbosePrebinding;
		bool			verboseCoreSymbolication;
		bool			verboseWarnings;
		bool			verboseRPaths;
		bool			verboseInterposing;
		bool			verboseCodeSignatures;
	};
	
	struct CoalIterator
	{
		ImageLoader*	image;
		const char*		symbolName;
		unsigned int	loadOrder;
		bool			weakSymbol;
		bool			symbolMatches;
		bool			done;
		// the following are private to the ImageLoader subclass
		uintptr_t		curIndex;
		uintptr_t		endIndex;
		uintptr_t		address;
		uintptr_t		type;
		uintptr_t		addend;
		uintptr_t		imageIndex;
	};
	
	virtual	void			initializeCoalIterator(CoalIterator&, unsigned int loadOrder, unsigned imageIndex) = 0;
	virtual	bool			incrementCoalIterator(CoalIterator&) = 0;
	virtual	uintptr_t		getAddressCoalIterator(CoalIterator&, const LinkContext& context) = 0;
	virtual	void			updateUsesCoalIterator(CoalIterator&, uintptr_t newAddr, ImageLoader* target, unsigned targetIndex, const LinkContext& context) = 0;
	
	struct UninitedUpwards
	{
		uintptr_t	 count;
		ImageLoader* images[1];
	};

	
										// constructor is protected, but anyone can delete an image
	virtual								~ImageLoader();
	
										// link() takes a newly instantiated ImageLoader and does all 
										// fixups needed to make it usable by the process
	void								link(const LinkContext& context, bool forceLazysBound, bool preflight, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath);
	
										// runInitializers() is normally called in link() but the main executable must 
										// run crt code before initializers
	void								runInitializers(const LinkContext& context, InitializerTimingList& timingInfo);
	
										// called after link() forces all lazy pointers to be bound
	void								bindAllLazyPointers(const LinkContext& context, bool recursive);
	
										// used by dyld to see if a requested library is already loaded (might be symlink)
	bool								statMatch(const struct stat& stat_buf) const;

										// get short name of this image
	const char*							getShortName() const;

										// returns leaf name
	static const char*					shortName(const char* fullName);

										// get path used to load this image, not necessarily the "real" path
	const char*							getPath() const { return fPath; }

	uint32_t							getPathHash() const { return fPathHash; }

										// get the "real" path for this image (e.g. no @rpath)
	const char*							getRealPath() const;

										// get path this image is intended to be placed on disk or NULL if no preferred install location
	virtual const char*					getInstallPath() const = 0;

										// image was loaded with NSADDIMAGE_OPTION_MATCH_FILENAME_BY_INSTALLNAME and all clients are looking for install path
	bool								matchInstallPath() const;
	void								setMatchInstallPath(bool);
	
										// mark that this image's exported symbols should be ignored when linking other images (e.g. RTLD_LOCAL)
	void								setHideExports(bool hide = true);
	
										// check if this image's exported symbols should be ignored when linking other images 
	bool								hasHiddenExports() const;
	
										// checks if this image is already linked into the process
	bool								isLinked() const;
	
										// even if image is deleted, leave segments mapped in
	void								setLeaveMapped();
	
										// even if image is deleted, leave segments mapped in
	bool								leaveMapped() { return fLeaveMapped; }

										// image resides in dyld shared cache
	virtual bool						inSharedCache() const { return false; };

										// checks if the specifed address is within one of this image's segments
	virtual bool						containsAddress(const void* addr) const;

										// checks if the specifed symbol is within this image's symbol table
	virtual bool						containsSymbol(const void* addr) const = 0;

										// checks if the specifed address range overlaps any of this image's segments
	virtual bool						overlapsWithAddressRange(const void* start, const void* end) const;

										// adds to list of ranges of memory mapped in
	void								getMappedRegions(MappedRegion*& region) const;

										// st_mtime from stat() on file
	time_t								lastModified() const;

										// only valid for main executables, returns a pointer its entry point from LC_MAIN
	virtual void*						getEntryFromLC_MAIN() const = 0;
	
										// only valid for main executables, returns a pointer its main from LC_UNIXTHREAD
	virtual void*						getEntryFromLC_UNIXTHREAD() const = 0;
	
										// dyld API's require each image to have an associated mach_header
	virtual const struct mach_header*   machHeader() const = 0;
	
										// dyld API's require each image to have a slide (actual load address minus preferred load address)
	virtual uintptr_t					getSlide() const = 0;
	
										// last address mapped by image
	virtual const void*					getEnd() const = 0;
	
										// image has exports that participate in runtime coalescing
	virtual bool						hasCoalescedExports() const = 0;

										// search symbol table of definitions in this image for requested name
	virtual bool						findExportedSymbolAddress(const LinkContext& context, const char* symbolName,
																const ImageLoader* requestorImage, int requestorOrdinalOfDef,
																bool runResolver, const ImageLoader** foundIn, uintptr_t* address) const;

										// search symbol table of definitions in this image for requested name
	virtual const Symbol*				findExportedSymbol(const char* name, bool searchReExports, const char* thisPath, const ImageLoader** foundIn) const = 0;
	
										// search symbol table of definitions in this image for requested name
	virtual const Symbol*				findExportedSymbol(const char* name, bool searchReExports, const ImageLoader** foundIn) const {
											return findExportedSymbol(name, searchReExports, this->getPath(), foundIn);
										}
	
										// gets address of implementation (code) of the specified exported symbol
	virtual uintptr_t					getExportedSymbolAddress(const Symbol* sym, const LinkContext& context, 
													const ImageLoader* requestor=NULL, bool runResolver=false, const char* symbolName=NULL) const = 0;
	
										// gets attributes of the specified exported symbol
	virtual DefinitionFlags				getExportedSymbolInfo(const Symbol* sym) const = 0;
	
										// gets name of the specified exported symbol
	virtual const char*					getExportedSymbolName(const Symbol* sym) const = 0;
	
										// gets how many symbols are exported by this image
	virtual uint32_t					getExportedSymbolCount() const = 0;
			
										// gets the i'th exported symbol
	virtual const Symbol*				getIndexedExportedSymbol(uint32_t index) const = 0;
			
										// find exported symbol as if imported by this image
										// used by RTLD_NEXT
	virtual const Symbol*				findExportedSymbolInDependentImages(const char* name, const LinkContext& context, const ImageLoader** foundIn) const;
	
										// find exported symbol as if imported by this image
										// used by RTLD_SELF
	virtual const Symbol*				findExportedSymbolInImageOrDependentImages(const char* name, const LinkContext& context, const ImageLoader** foundIn) const;
	
										// gets how many symbols are imported by this image
	virtual uint32_t					getImportedSymbolCount() const = 0;
	
										// gets the i'th imported symbol
	virtual const Symbol*				getIndexedImportedSymbol(uint32_t index) const = 0;
			
										// gets attributes of the specified imported symbol
	virtual ReferenceFlags				getImportedSymbolInfo(const Symbol* sym) const = 0;
			
										// gets name of the specified imported symbol
	virtual const char*					getImportedSymbolName(const Symbol* sym) const = 0;
			
										// find the closest symbol before addr
	virtual const char*					findClosestSymbol(const void* addr, const void** closestAddr) const = 0;
	
										// for use with accelerator tables
	virtual const char*					getIndexedPath(unsigned) const { return getPath(); }
	virtual const char*					getIndexedShortName(unsigned) const { return getShortName(); }

										// checks if this image is a bundle and can be loaded but not linked
	virtual bool						isBundle() const = 0;
	
										// checks if this image is a dylib 
	virtual bool						isDylib() const = 0;
	
										// checks if this image is a main executable 
	virtual bool						isExecutable() const = 0;
	
										// checks if this image is a main executable 
	virtual bool						isPositionIndependentExecutable() const = 0;
	
										// only for main executable
	virtual bool						forceFlat() const = 0;
	
										// called at runtime when a lazily bound function is first called
	virtual uintptr_t					doBindLazySymbol(uintptr_t* lazyPointer, const LinkContext& context) = 0;
	
										// called at runtime when a fast lazily bound function is first called
	virtual uintptr_t					doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context,
															void (*lock)(), void (*unlock)()) = 0;

										// calls termination routines (e.g. C++ static destructors for image)
	virtual void						doTermination(const LinkContext& context) = 0;
					
										// return if this image has initialization routines
	virtual bool						needsInitialization() = 0;
			
										// return if this image has specified section and set start and length
	virtual bool						getSectionContent(const char* segmentName, const char* sectionName, void** start, size_t* length) = 0;

										// fills in info about __eh_frame and __unwind_info sections
	virtual void						getUnwindInfo(dyld_unwind_sections* info) = 0;

										// given a pointer into an image, find which segment and section it is in
	virtual const struct macho_section* findSection(const void* imageInterior) const = 0;

										// given a pointer into an image, find which segment and section it is in
	virtual bool						findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) = 0;
	
										// the image supports being prebound
	virtual bool						isPrebindable() const = 0;
	
										// the image is prebindable and its prebinding is valid
	virtual bool						usablePrebinding(const LinkContext& context) const = 0;
	
										// add all RPATH paths this image contains
	virtual	void						getRPaths(const LinkContext& context, std::vector<const char*>&) const = 0;
	
										// image has or uses weak definitions that need runtime coalescing
	virtual bool						participatesInCoalescing() const = 0;
		
										// if image has a UUID, copy into parameter and return true
	virtual	bool						getUUID(uuid_t) const = 0;

										// dynamic interpose values onto this image
	virtual void						dynamicInterpose(const LinkContext& context) = 0;

										// record interposing for any late binding
	void								addDynamicInterposingTuples(const struct dyld_interpose_tuple array[], size_t count);
		
	virtual const char*					libPath(unsigned int) const = 0;

										// Image has objc sections, so information objc about when it comes and goes
	virtual	bool						notifyObjC() const { return false; }

	virtual bool						overridesCachedDylib(uint32_t& num) const { return false; }
	virtual void						setOverridesCachedDylib(uint32_t num) { }


//
// A segment is a chunk of an executable file that is mapped into memory.  
//
	virtual unsigned int				segmentCount() const = 0;
	virtual const char*					segName(unsigned int) const = 0;
	virtual uintptr_t					segSize(unsigned int) const = 0;
	virtual uintptr_t					segFileSize(unsigned int) const = 0;
	virtual bool						segHasTrailingZeroFill(unsigned int) = 0;
	virtual uintptr_t					segFileOffset(unsigned int) const = 0;
	virtual bool						segReadable(unsigned int) const = 0;
	virtual bool						segWriteable(unsigned int) const = 0;
	virtual bool						segExecutable(unsigned int) const = 0;
	virtual bool						segUnaccessible(unsigned int) const = 0;
	virtual bool						segHasPreferredLoadAddress(unsigned int) const = 0;
	virtual uintptr_t					segPreferredLoadAddress(unsigned int) const = 0;
	virtual uintptr_t					segActualLoadAddress(unsigned int) const = 0;
	virtual uintptr_t					segActualEndAddress(unsigned int) const = 0;

	
										// info from LC_VERSION_MIN_MACOSX or LC_VERSION_MIN_IPHONEOS
	virtual uint32_t					sdkVersion() const = 0;
	virtual uint32_t					minOSVersion() const = 0;
	
										// if the image contains interposing functions, register them
	virtual void						registerInterposing(const LinkContext& context) = 0;

	virtual bool						usesChainedFixups() const { return false; }


										// when resolving symbols look in subImage if symbol can't be found
	void								reExport(ImageLoader* subImage);

	virtual void						recursiveBind(const LinkContext& context, bool forceLazysBound, bool neverUnload);
	virtual void						recursiveBindWithAccounting(const LinkContext& context, bool forceLazysBound, bool neverUnload);
	void								weakBind(const LinkContext& context);

	void								applyInterposing(const LinkContext& context);

	dyld_image_states					getState() { return (dyld_image_states)fState; }

	ino_t								getInode() const { return fInode; }
    dev_t                               getDevice() const { return fDevice; }

										// used to sort images bottom-up
	int									compare(const ImageLoader* right) const;
	
	void								incrementDlopenReferenceCount() { ++fDlopenReferenceCount; }

	bool								decrementDlopenReferenceCount();
	
	void								printReferenceCounts();

	uint32_t							dlopenCount() const { return fDlopenReferenceCount; }

	void								setCanUnload() { fNeverUnload = false; fLeaveMapped = false; }

	bool								neverUnload() const { return fNeverUnload; }

	void								setNeverUnload() { fNeverUnload = true; fLeaveMapped = true; }
	void								setNeverUnloadRecursive();
	
	bool								isReferencedDownward() { return fIsReferencedDownward; }

	virtual uintptr_t					resolveWeak(const LinkContext& context, const char* symbolName, bool weak_import, bool runResolver,
													const ImageLoader** foundIn) { return 0; } 

										// triggered by DYLD_PRINT_STATISTICS to write info on work done and how fast
	static void							printStatistics(unsigned int imageCount, const InitializerTimingList& timingInfo);
	static void							printStatisticsDetails(unsigned int imageCount, const InitializerTimingList& timingInfo);

										// used with DYLD_IMAGE_SUFFIX
	static void							addSuffix(const char* path, const char* suffix, char* result);
	
	static uint32_t						hash(const char*);
	
	static const uint8_t*				trieWalk(const uint8_t* start, const uint8_t* end, const char* stringToFind);

										// used instead of directly deleting image
	static void							deleteImage(ImageLoader*);

	static bool							haveInterposingTuples() { return !fgInterposingTuples.empty(); }
	static void							clearInterposingTuples() { fgInterposingTuples.clear(); }

	static void							applyInterposingToDyldCache(const LinkContext& context);

			bool						dependsOn(ImageLoader* image);
			
 			void						setPath(const char* path);
			void						setPaths(const char* path, const char* realPath);
			void						setPathUnowned(const char* path);
						
			void						clearDepth() { fDepth = 0; }
			int							getDepth() { return fDepth; }
			
			void						setBeingRemoved() { fBeingRemoved = true; }
			bool						isBeingRemoved() const { return fBeingRemoved; }
			
			void						markNotUsed() { fMarkedInUse = false; }
			void						markedUsedRecursive(const std::vector<DynamicReference>&);
			bool						isMarkedInUse() const	{ return fMarkedInUse; }
		
			void						setAddFuncNotified() { fAddFuncNotified = true; }
			bool						addFuncNotified() const { return fAddFuncNotified; }
	
	struct InterposeTuple { 
		uintptr_t		replacement; 
		ImageLoader*	neverImage;			// don't apply replacement to this image
		ImageLoader*	onlyImage;			// only apply replacement to this image
		uintptr_t		replacee; 
	};

	static uintptr_t read_uleb128(const uint8_t*& p, const uint8_t* end);
	static intptr_t read_sleb128(const uint8_t*& p, const uint8_t* end);

	void			vmAccountingSetSuspended(const LinkContext& context, bool suspend);

protected:			
	// abstract base class so all constructors protected
					ImageLoader(const char* path, unsigned int libCount); 
					ImageLoader(const ImageLoader&);
	void			operator=(const ImageLoader&);
	void			operator delete(void* image) throw() { ::free(image); }
	

	struct LibraryInfo {
		uint32_t		checksum;
		uint32_t		minVersion;
		uint32_t		maxVersion;
	};

	struct DependentLibrary {
		ImageLoader*	image;
		uint32_t		required : 1,
						checksumMatches : 1,
						isReExported : 1,
						isSubFramework : 1;
	};
	
	struct DependentLibraryInfo {
		const char*			name;
		LibraryInfo			info;
		bool				required;
		bool				reExported;
		bool				upward;
	};


	typedef void (*Initializer)(int argc, const char* argv[], const char* envp[], const char* apple[], const ProgramVars* vars);
	typedef void (*Terminator)(void);
	


	unsigned int			libraryCount() const { return fLibraryCount; }
	virtual ImageLoader*	libImage(unsigned int) const = 0;
	virtual bool			libReExported(unsigned int) const = 0;
	virtual bool			libIsUpward(unsigned int) const = 0;
	virtual void			setLibImage(unsigned int, ImageLoader*, bool, bool) = 0;

						// To link() an image, its dependent libraries are loaded, it is rebased, bound, and initialized.
						// These methods do the above, exactly once, and it the right order
	virtual void		recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath);
	virtual unsigned 	recursiveUpdateDepth(unsigned int maxDepth);
	virtual void		recursiveRebase(const LinkContext& context);
	virtual void		recursiveApplyInterposing(const LinkContext& context);
	virtual void		recursiveGetDOFSections(const LinkContext& context, std::vector<DOFInfo>& dofs);
	virtual void		recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
												ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&);

								// fill in information about dependent libraries (array length is fLibraryCount)
	virtual void				doGetDependentLibraries(DependentLibraryInfo libs[]) = 0;
	
								// called on images that are libraries, returns info about itself
	virtual LibraryInfo			doGetLibraryInfo(const LibraryInfo& requestorInfo) = 0;
	
								// do any fix ups in this image that depend only on the load address of the image
	virtual void				doRebase(const LinkContext& context) = 0;
	
								// do any symbolic fix ups in this image
	virtual void				doBind(const LinkContext& context, bool forceLazysBound) = 0;
	
								// called later via API to force all lazy pointer to be bound
	virtual void				doBindJustLazies(const LinkContext& context) = 0;
	
								// if image has any dtrace DOF sections, append them to list to be registered
	virtual void				doGetDOFSections(const LinkContext& context, std::vector<DOFInfo>& dofs) = 0;
	
								// do interpose
	virtual void				doInterpose(const LinkContext& context) = 0;

								// run any initialization routines in this image
	virtual bool				doInitialization(const LinkContext& context) = 0;
	
								// return if this image has termination routines
	virtual bool				needsTermination() = 0;
	
								// support for runtimes in which segments don't have to maintain their relative positions
	virtual bool				segmentsMustSlideTogether() const = 0;	
	
								// built with PIC code and can load at any address
	virtual bool				segmentsCanSlide() const = 0;		

								// set how much all segments slide
	virtual void				setSlide(intptr_t slide) = 0;		

								// returns if all dependent libraries checksum's were as expected and none slide
			bool				allDependentLibrariesAsWhenPreBound() const;

								// in mach-o a child tells it parent to re-export, instead of the other way around...
	virtual	bool				isSubframeworkOf(const LinkContext& context, const ImageLoader* image) const = 0;

								// in mach-o a parent library knows name of sub libraries it re-exports..
	virtual	bool				hasSubLibrary(const LinkContext& context, const ImageLoader* child) const  = 0;

	virtual bool				weakSymbolsBound(unsigned index) { return fWeakSymbolsBound; }
	virtual void				setWeakSymbolsBound(unsigned index) { fWeakSymbolsBound = true; }

								// set fState to dyld_image_state_memory_mapped
	void						setMapped(const LinkContext& context);
		
	void						setFileInfo(dev_t device, ino_t inode, time_t modDate);

	void						setDepth(uint16_t depth) { fDepth = depth; }

	static uintptr_t			interposedAddress(const LinkContext& context, uintptr_t address, const ImageLoader* notInImage, const ImageLoader* onlyInImage=NULL);
	
	static uintptr_t			fgNextPIEDylibAddress;
	static uint32_t				fgImagesWithUsedPrebinding;
	static uint32_t				fgImagesUsedFromSharedCache;
	static uint32_t				fgImagesHasWeakDefinitions;
	static uint32_t				fgImagesRequiringCoalescing;
	static uint32_t				fgTotalRebaseFixups;
	static uint32_t				fgTotalBindFixups;
	static uint32_t				fgTotalBindSymbolsResolved;
	static uint32_t				fgTotalBindImageSearches;
	static uint32_t				fgTotalLazyBindFixups;
	static uint32_t				fgTotalPossibleLazyBindFixups;
	static uint32_t				fgTotalSegmentsMapped;
	static uint32_t				fgSymbolTrieSearchs;
	static uint64_t				fgTotalBytesMapped;
	static uint64_t				fgTotalBytesPreFetched;
	static uint64_t				fgTotalLoadLibrariesTime;
public:
	static uint64_t				fgTotalObjCSetupTime;
	static uint64_t				fgTotalDebuggerPausedTime;
	static uint64_t				fgTotalRebindCacheTime;
	static uint64_t				fgTotalRebaseTime;
	static uint64_t				fgTotalBindTime;
	static uint64_t				fgTotalWeakBindTime;
	static uint64_t				fgTotalDOF;
	static uint64_t				fgTotalInitTime;

protected:
	static std::vector<InterposeTuple>	fgInterposingTuples;
	
	const char*					fPath;
	const char*					fRealPath;
	dev_t						fDevice;
	ino_t						fInode;
	time_t						fLastModified;
	uint32_t					fPathHash;
	uint32_t					fDlopenReferenceCount;	// count of how many dlopens have been done on this image

	struct recursive_lock {
						recursive_lock(mach_port_t t) : thread(t), count(0) {}
		mach_port_t		thread;
		int				count;
	};
	void						recursiveSpinLock(recursive_lock&);
	void						recursiveSpinUnLock();

private:
	const ImageLoader::Symbol*	findExportedSymbolInDependentImagesExcept(const char* name, const ImageLoader** dsiStart, 
										const ImageLoader**& dsiCur, const ImageLoader** dsiEnd, const ImageLoader** foundIn) const;

	void						processInitializers(const LinkContext& context, mach_port_t this_thread,
													InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& ups);


	recursive_lock*				fInitializerRecursiveLock;
	uint16_t					fDepth;
	uint16_t					fLoadOrder;
	uint32_t					fState : 8,
								fLibraryCount : 10,
								fAllLibraryChecksumsAndLoadAddressesMatch : 1,
								fLeaveMapped : 1,		// when unloaded, leave image mapped in cause some other code may have pointers into it
								fNeverUnload : 1,		// image was statically loaded by main executable
								fHideSymbols : 1,		// ignore this image's exported symbols when linking other images
								fMatchByInstallName : 1,// look at image's install-path not its load path
								fInterposed : 1,
								fRegisteredDOF : 1,
								fAllLazyPointersBound : 1,
								fMarkedInUse : 1,
								fBeingRemoved : 1,
								fAddFuncNotified : 1,
								fPathOwnedByImage : 1,
								fIsReferencedDownward : 1,
								fWeakSymbolsBound : 1;

	static uint16_t				fgLoadOrdinal;
};


VECTOR_NEVER_DESTRUCTED_EXTERN(ImageLoader::InterposeTuple);


#endif

如此之长的头文件,我们去掉方法和静态成员,整理以后,只有以下成员

    const char*					fPath;
	const char*					fRealPath;
	dev_t						fDevice;
	ino_t						fInode;
	time_t						fLastModified;
	uint32_t					fPathHash;
	uint32_t					fDlopenReferenceCount;	// count of how many dlopens have been done on this image
	recursive_lock*				fInitializerRecursiveLock;
	uint16_t					fDepth;
	uint16_t					fLoadOrder;
	uint32_t					fState : 8,
								fLibraryCount : 10,
								fAllLibraryChecksumsAndLoadAddressesMatch : 1,
								fLeaveMapped : 1,		// when unloaded, leave image mapped in cause some other code may have pointers into it
								fNeverUnload : 1,		// image was statically loaded by main executable
								fHideSymbols : 1,		// ignore this image's exported symbols when linking other images
								fMatchByInstallName : 1,// look at image's install-path not its load path
								fInterposed : 1,
								fRegisteredDOF : 1,
								fAllLazyPointersBound : 1,
								fMarkedInUse : 1,
								fBeingRemoved : 1,
								fAddFuncNotified : 1,
								fPathOwnedByImage : 1,
								fIsReferencedDownward : 1,
								fWeakSymbolsBound : 1;

根据我们上次dump的结果来看,然后是不够的,至少这里面根本没有我们需要的load address
那么,这是什么原因呢?
由于c++继承的特性,在c++里,类指针的第一个成员是虚表地址,往后依次是父类的成员(包括私有成员)和自己类成员。显然这里真正的类型应是它的某个子类。
结合我们上面的dump结果和apple的源码,一番尝试后,我们可以总结出它的结构如下

typedef struct {
    void*                       vTable;
    const char*					fPath;
	const char*					fRealPath;
	dev_t						fDevice;
	ino_t						fInode;
	time_t						fLastModified;
	uint32_t					fPathHash;
	uint32_t					fDlopenReferenceCount;	// count of how many dlopens have been done on this image
	void*				fInitializerRecursiveLock;
	uint16_t					fDepth;
	uint16_t					fLoadOrder;
	uint32_t					fState : 8,
								fLibraryCount : 10,
								fAllLibraryChecksumsAndLoadAddressesMatch : 1,
								fLeaveMapped : 1,		// when unloaded, leave image mapped in cause some other code may have pointers into it
								fNeverUnload : 1,		// image was statically loaded by main executable
								fHideSymbols : 1,		// ignore this image's exported symbols when linking other images
								fMatchByInstallName : 1,// look at image's install-path not its load path
								fInterposed : 1,
								fRegisteredDOF : 1,
								fAllLazyPointersBound : 1,
								fMarkedInUse : 1,
								fBeingRemoved : 1,
								fAddFuncNotified : 1,
								fPathOwnedByImage : 1,
								fIsReferencedDownward : 1,
								fWeakSymbolsBound : 1;
	uint64_t								fCoveredCodeLength;
	const uint8_t*							fMachOData;
	const uint8_t*							fLinkEditBase; // add any internal "offset" to this to get mapped address
	uintptr_t								fSlide;
	uint32_t								fEHFrameSectionOffset;
	uint32_t								fUnwindInfoSectionOffset;
	uint32_t								fDylibIDOffset;
	uint32_t								fSegmentsCount : 8,
											fIsSplitSeg : 1,
											fInSharedCache : 1,
#if TEXT_RELOC_SUPPORT
											fTextSegmentRebases : 1, 
											fTextSegmentBinds : 1, 
#endif
#if __i386__
											fReadOnlyImportSegment : 1,
#endif
											fHasSubLibraries : 1,
											fHasSubUmbrella : 1,
											fInUmbrella : 1,
											fHasDOFSections : 1,
											fHasDashInit : 1,
											fHasInitializers : 1,
											fHasTerminators : 1,
											fNotifyObjC : 1,
											fRetainForObjC : 1,
											fRegisteredAsRequiresCoalescing : 1, 	// <rdar://problem/7886402> Loading MH_DYLIB_STUB causing coalescable miscount
											fOverrideOfCacheImageNum : 12;

											
	static uint32_t					fgSymbolTableBinarySearchs;
} MachoImage;

由此,我们可以顺利的通过以下代码来获取到他基地址。

	MachoImage* image=(MachoImage*)dlopen("xx.dylib",RTLD_LAZY);
	NSLog(@"[FLWB]:xxHandle:%p",image->fLinkEditBase);

当然fMachOData也是可以的,但考虑到后面的注释

add any internal "offset" to this to get mapped address

我们还是优先使用fLinkEditBase
此外,我们也可以通过此头文件来获取其它未公开的成员。
到此为止,我们不但达到了我们获取基地址的需求,还能够额外的获取甚至关键的信息。

免责声明

本文所公开的技术细节仅做技术交流,任何权利责任人需自行承担其用途产生的后果。另外,转载本文需注明来源及作者信息。

鸣谢

排名不分先后

资料文献

  1. https://stackoverflow.com/questions/19451791/get-loaded-address-of-a-elf-binary-dlopen-is-not-working-as-expected
  2. 从dlsym()源码看android 动态链接过程
  3. dyld source

git flow实践指南

前言

你会用git吗?

我相信在座的大多数人都会自信的回答:“会”。
而实际上,大家可能从来没有考虑过自己的用法是否真的科学,真的健壮,尤其是项目越来越大,人数越来越多,周期越来越长的时候。

其中,典型的有以下几个问题:

  1. 当我开发某个功能到一半的时候,PM突然给我安排了一个新的紧急任务,我该怎么开始这个任务,而不影响现在的?
  2. 当我代码写好了的时候,如何发布?
  3. 当我发布后,代码出问题了,如何快速修复?
  4. 以上的情况,还要求修复后,还要包含之后开发的所有代码?

大部分开发人员使用git的时候,基本只使用两个甚至一个分支,所以下面的这些理念,显然是打开了一扇新世界的大门了。

方案

显然,不光代码有代码规范,代码的管理和协同同样需要一个清晰的流程和规范,由此,行业内的通用解决方案是Git Flow

git-flow

怎么样,眼花缭乱吧,不过我可以给你个建议:把头左转90度,别着急骂....这是office picture,并非我画的。

在上面这幅图上,最上面的一行,代表分支,它们分别是

名称 解释
master 这个分支最近发布到生产环境的代码,最近发布的Release, 这个分支只能从其他分支合并,不能在这个分支直接修改
Develop 这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支
Feature 这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release
Release 当你需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支
Hotfix 当我们在Production发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release.

实现细则

master分支

在master分枝上工作,我们需要遵循一个基本原则,所有在master分支上的commit应该tag.

git-flow

feature分支

feature分支做完后,必须合并回develop分支, 合并完分支后一般会删点这个feature分支,但是我们也可以保留

git-flow

开始一个新的功能的开发

git checkout -b some-feature develop
# Optionally, push branch to origin:
git push -u origin some-feature    

# 做一些改动    
git status
git add some-file
git commit    

开发完成

git pull origin develop
git checkout develop
git merge --no-ff some-feature
git push origin develop

git branch -d some-feature

# If you pushed branch to origin:
git push origin --delete some-feature    

release分支

分支名 release/*
release分支基于develop分支创建,打完release分支后,我们可以在这个release分支上测试,修改bug等。同时,其它开发人员可以基于开发新的feature,一旦打了release分支之后不要从develop分支上合并新的改动到Release分支
发布release分支时,合并releasemasterdevelop, 同时在master分支上打个tag记住release版本号,然后可以删除release分支了(当然,你可以选择不删除)。

git-flow

开始release

git checkout -b release-0.1.0 develop

# Optional: Bump version number, commit
# Prepare release, commit

完成release

git checkout master
git merge --no-ff release-0.1.0
git push

git checkout develop
git merge --no-ff release-0.1.0
git push

git branch -d release-0.1.0

# If you pushed branch to origin:
git push origin --delete release-0.1.0   


git tag -a v0.1.0 master
git push --tags

hotfix分支

分支名 hotfix/*
hotfix分支基于master分支创建,开发完后需要合并回masterdevelop分支,同时在master上打一个tag
git-flow

开始hotfix

git checkout -b hotfix-0.1.1 master    

完成hotfix

git checkout master
git merge --no-ff hotfix-0.1.1
git push


git checkout develop
git merge --no-ff hotfix-0.1.1
git push

git branch -d hotfix-0.1.1

git tag -a v0.1.1 master
git push --tags

工具

如果你能坚持看到这里,我真的为你感到欣喜,这说明你是用心学习的,并且是不畏艰险的,毕竟上面那么长的一大串的代码,可能已经使你感到畏惧。
而显然的是,作为通用的一个解决方案,不可能这么繁琐,那么,唯一的可能是——有!工!具!

平台 命令
OS X brew install git-flow
Linux apt-get install git-flow
Windows wget -q -O - --no-check-certificate https://github.com/nvie/gitflow/raw/develop/contrib/gitflow-installer.sh | bash
IDEA Git Flow Integration

其中还有一大部分是gui的,比较简单,本文就不再赘述了。下面着重介绍下命令行下的使用

  • 初始化: git flow init
  • 开始新Feature: git flow feature start MYFEATURE
  • Publish一个Feature(也就是push到远程): git flow feature publish MYFEATURE
  • 获取Publish的Feature: git flow feature pull origin MYFEATURE
  • 完成一个Feature: git flow feature finish MYFEATURE
  • 开始一个Release: git flow release start RELEASE [BASE]
  • Publish一个Release: git flow release publish RELEASE
  • 发布Release: git flow release finish RELEASE
    别忘了git push --tags
  • 开始一个Hotfix: git flow hotfix start VERSION [BASENAME]
  • 发布一个Hotfix git flow hotfix finish VERSION

仔细观察,这些命令都是有规矩的,它们大概可以如下图表示

git-flow

首款ios吃鸡辅助,开源发布了。。。

项目地址

https://github.com/rozbo/ios-pubgm-hack/

为什么要做这个东西

原谅我标题吹了个牛逼。哈哈,不过这也确实是首款,至少对我来说是这样。从迷上吃鸡到现在几个月时间,因为自己水平菜,干又干不过别人,实在是很难受。很难受。特别难受。你想想,那些对我突突突突突突突突突突突突突突突突突的人,远比我有能力,远比我有天赋,难道他们就应该对我突突突突突突突突突突突突突突突突突突突突突突突突吗?我的命运就应该倒在别人的突突突突突突突突突突突突突突突突突突突突突突突突突突突突突之下吗?不!!!我绝不!!!!!!!!

我坚信每个人都有自己的位置,对于这个世界来说。我也有我的优势,自认为懂点技术,既然打不过别人,自然要利用自己的优点和特长,不能以己之短,攻人之长啊。所以,我决定,研究一下怎么使用一些技术手段,来帮助自己取的胜利。

于是我开始了漫长的研究之路,呐,你看,一个人要学习,要进步,必须得有动力,没有动力不行的。所以,玩游戏有益于学习,有利于进步。但是我翻遍谷歌和百度,网上根本没有对ios游戏hack的一丁点资料,这也是我标题上 首款 的原因。前后共耗时两周,最终实现了这些功能。

做好之后,我在想着么一个好东西,不能我自己用啊,我在完成这个东西的时候,得到了许许多多开源项目和来自五湖四海的各位兄弟的帮忙,所以,我决定!开源!!!并且,接下来会写一个系列文章,分别讲各个细节。

为什么是今天???
因为今天我18岁生日。

感谢&推荐

本项目的诞生,离不开以下这些开源项目的支持和帮助,这也是接下来文章所必须的工具。他们分别是:

hookzz

hookzz
一个hook框架,支持ios/android,支持静态patch,支持单指令劫持,插桩。运行时指令替换。最重要的是,它支持非越狱状态下的c函数hook。
这个功能在调试的过程中,非常的有用和方便,是深入分析必不可少的一样工具,由来自滴滴、阿里的
jmpews
开发。

MonkeyDev

MonkeyDev
原来iOSOpenDev 的升级,支持最新xcode9,它可以自动导头文件,恢复符号,并且集成了RevealCycript等方便调试工具,并且支持自动重签名安装和在xcode中调试
这个工具极大的提高了开发调试效率,可以说是ios逆向人士必不可少需要掌握的一个工具,由来自网易的AloneMonkey开发,同时他在网易云课堂有ios逆向的系列教程,并且有书(正在)出版。

Hikari

Hikari
LLVM混淆器,不知道咋解释,反正就是牛逼。。。

rxmemscan

rxmemscan
ios下的内存扫描工具,是目前ios下最强大最稳定的内存扫描工具了,它使用了lz4压缩内存的方式来避免内存爆满闪退,同时它是cli交互,远比igg,八门神器之流稳定,关键的是能够异步操作。是我们分析游戏内存必不可少的工具,然而,它也存在一些问题,比如内存对比功能的缺乏等,但好在是由c++开发的,所以需要进行功能的扩展后才能够方便的使用。

其他更为下游的工具

这其中还有更多使用过的工具和算法,比如d3d9 hook,lldb,restore-symbol,cycript,theos,Logos等,这里就不再专门介绍了。

从哪里开始?

如何编译

如上面说的,
先安装xcode,再安装最新版MonkeyDev ,注意⚠️,不是最新版的不行,会有bug的哟。
然后clone本项目,在xcode打开,编译即可。
对了,在此之前,你可能需要先要获取一份脱壳过的绝地求生 全军出击游戏ipa,获取方式有二:

  • 如果是非越狱手机,只能从某助手里面下载,里面大概率是脱过壳的。
  • 如果是越狱手机,可以选择使用安装frida,然后使用frida-ios-dump自动砸壳并提取出来。

然后放在Target App文件夹里面即可,这部分是MonkeyDev 的编译要求,具体可以看MonkeyDev的文档

编译后没有效果怎么办

原因是这份代码是给iphone x使用的,你要根据自己的手机,去修改代码中显卡的类。以iPhone X为例,其显卡gpu芯片为A11,则其显示处理类为,AGXA11FamilyRenderContext ,以此类推你自己手机的类,并且修改即可。

flask-migrate的各种*操作

自定义版本

很多时候我们可能不能依靠

flask db migrate

这样的话,在修改表名修改字段名等的时候,如果你信赖自动生成的话,那你可能就要瞎了,因为这样的话,他会先删除再创建,你会发现

mmp,我的数据呢!!!

所以,这个时候,你就需要

flask db revision

来创建一个自定义版本,来自己写升级脚本

修改表名

op.rename_table('shop_print_rules', 'label_print_rules')

修改字段名

op.alter_column('label_print_rules', 'box_template_type', new_column_name='template_type',
                existing_type=sa.Enum('single_box', 'all_box', 'pick', 'putaway',
                                      'stockin', 'stockout', 'delivery')
                server_default='single_box')

增加枚举值

这个很蛋疼的问题是,很多时候,你创建一个枚举,然而改动后可能不会被检测到。

op.alter_column('label_print_rules', 'template_type', 
                existing_type=sa.Enum('single_box', 'all_box', 'pick', 'putaway',
                                      'stockin', 'stockout', 'delivery'),
                type_=sa.Enum('single_box', 'all_box', 'pick', 'putaway',
                              'stockin', 'stockout', 'delivery', 'single_picking_label',
                              'multi_picking_label', 'order_picking_label'),
                server_default='single_box')

然后你就会发现,它会提示你

sqlalchemy.exc.CompileError: PostgreSQL ENUM type requires a name.

好吧,这个是sqlalchemy报错了,意思是说这个枚举必须得有一个名字,那我们给加上。

op.alter_column('label_print_rules', 'template_type', 
                existing_type=sa.Enum('single_box', 'all_box', 'pick', 'putaway',
                                      'stockin', 'stockout', 'delivery',name='style'),
                type_=sa.Enum('single_box', 'all_box', 'pick', 'putaway',
                              'stockin', 'stockout', 'delivery', 'single_picking_label',
                              'multi_picking_label', 'order_picking_label',name='style'),
                server_default='single_box')

很好,现在不报错了,然而,仔细检查一下,你会发现,这个代码并不会如你想象中的那样工作。。。

好吧,我们继续一番探索,发现pgsql中的enum类型是一个自定义类型,可以使用

ALTER TYPE xxxxx ADD value 'XXXXX'

来添加,
好吧,那我们修改以上的代码

def upgrade():
    op.execute('ALTER TYPE xxxxx ADD value \'XXXXX\''')

这下总没毛病了吧?然而生活就是这样,你以后生活只是给你一巴掌,那你就错了,他接下来还要给你一脚。

psycopg2.InternalError: ALTER TYPE ... ADD cannot run inside a transaction block

这句话的意识是这条指令不能运行在一个事务中,那怎么办呢。我们灵机一动

def upgrade():
    op.execute('COMMIT')
    op.execute('ALTER TYPE xxxxx ADD value \'XXXXX\''')

ios10 NSLog无效?

去年起,我接触到ios10的越狱开发,但发现ios10的NSLog是失效了(曾一度以为是Tweak失效了,各种蛋疼的测试,发现原来是NSLog的原因)。
既然发现了这个问题,那就必须试图解决,以下是我解决的过程,发出来给大家警个醒,以免大家重复踩坑。
其实这个问题的原因是因为在ios10,苹果更改了日志系统,添加了几个os_函数,其中就有os_log,具体可以在看这个链接

尝试历史

syslogd to /var/log/syslog

之前旧的版本一直是使用这个,其实这个小插件很简单,仅仅是在/etc/syslog.conf里面加了一句

*.* /var/log/syslog

来把所有的日志导出到/var/log/syslog这个文件里。
结果,ios10下无效。

idevicesyslog

这个工具是我一直使用的,在mac上通过数据线链接手机后,能在mac的控制台输出ios的日志,它的优点是能够输出颜色,非常的实用的一个小工具,与它一起的套件都非常的好用,共有

name
idevice_id
idevicecrashreport
idevicedebugserverproxy
ideviceimagemounter
idevicename
ideviceprovision
idevicebackup
idevicedate
idevicediagnostics
ideviceinfo
idevicenotificationproxy
idevicescreenshot
idevicebackup2
idevicedebug
ideviceenterrecovery
ideviceinstaller
idevicepair
idevicesyslog

各个功能看名字就知道了,这里不再细表,可以通过

brew install libimobiledevice

来安装。

结果实测,idevicesyslog在ios下,不能够输出的Tweak的NSLog

socat

这个工具用来干这个事。。。也大才小用了吧。这个工具功能非常强大,这里不再专门讲解了。

deviceconsole

一番尝试之下,发现这个问题没有那么简单,于是百度了一下,结果出来个更坑爹的货,让用deviceconsole,还指出了github repo,结果我clone下来,一番编译,提示

ld: framework not found MobileDevice
clang: error: linker command failed with exit code 1 (use -v to see invocation)

这个报错意为找不到MobileDevice这个库,但是我去看过了以后,它明明在哪。。。搞的我也是一脸懵逼。后来猜想这个原因可能是因为这个库是私有库,而xcode9可能不让链接私有库了。。
于是我建了一个软连接,才重新编译通过。
编译通过之后,发现没有这个执行权限,然后一番捣鼓,结果发现!!!!根!本!没!卵!用!!!
关于这个项目的更多信息,请移步我clone的repo,我修改后pull request,结果十多天了没人理我。。

ondeviceconsole

最后,再次抱着试试看的态度,准备试试ondeviceconsole,如果还是不行的话,就只能呵呵,重写NSLog到os_log了。但所幸,这个是可用的。。。

我曾经交往过一个女朋友

我曾有过一个女朋友

故事

我曾经交往过一个女朋友。有一天我半夜从梦中醒来,突然无比的想她。
那时候手机还没有像现在这样普及,我的思念自然无从寄托。在床上瞪了一会儿眼睛以后,我跳起来麻利地穿好衣服,出门去找她了。尽管第二天上早自习我就能够见到她。
那时候我以为了方便学习的名义在外面自己租房住,所以也不会遇到宿管大爷这种阻碍,可以来一场说走就走。出了门才发现外面下着大雪,地上已经有着厚厚的积雪,天空中雪花还如筛灰一般落下。但心怀着爱情的炽热,我丝毫没觉得冷。
北方下雪的冬夜格外寂静,此时已经是凌晨两点以后,街上没有一个行人,只有我自己踏在积雪上的声音格外清晰。
我穿过那条横穿这个小县城的街道,来到我当时女朋友家的楼下。然而我什么都做不了,楼门紧锁,况且即使开着我也没勇气在半夜里去挑战她母亲的忍耐度。于是我在楼下冒着大雪站了一会儿,抽了一支烟,惆怅了一阵子之后,就顺道拐去了网吧……
直到很久以后,时过境迁,妹子已经再无联系,而我也不是那能半夜扛住风雪的鸡血少年,我才领悟自己当时的心态。那不过是一种表演罢了,除了把自己感动一下,制造一点自己痴情的假象,一点意义都没有。

感慨

在感情中,我们往往觉得自己掏心掏肺,所做所为能够感天动地,闻者伤心,见者叹息,为什么偏偏感动不了你?我们总是容易用一种自虐的方式制造出一种痴情的假象来使得自己站在感情的道德制高点上,获得一种畸形的满足感和安全感。
其实无论是雪夜去对方家楼下站会儿或者是冒着大雨给她送一杯奶茶什么的,自己回想起来往往觉得如乔峰大战聚贤庄、关羽千里走单骑一样壮怀激烈,而对于对方来说,一杯奶茶就是一杯奶茶,无法承载起你想要在上面寄托的山崩地裂的情怀。
少年的时候,总是迫不及待地将自己的满腔爱意表达出来,而结果往往是陷入表演之中而不自知。所以两个人的记忆才会出现偏差,那些你觉得刻骨铭心的过去,对方往往没有同样的感觉,甚至茫然不知。成长的标志就是懂得克制自己。克制自己的情绪,克制自己的表演欲,甚至克制自己的喜欢。少年时候,喜欢一个人恨不能把她变成自己身体的一部分,她刚说冷,我这边心里已经结冰了,她说难过,我立马如丧考妣,比她还难过,唯恐无法将自己的爱意表达出来。而事实上,谁也无法承担起另一个人的价值寄托,只有做一个独立、有价值的人,才能真正学会去爱另一个人。
也千万不要尝试改变另一个人,这注定是徒劳的。做自己就好,爱情的真谛在于相互的吸引、志趣相投的同行,而不是追逐和依附。

怎么分析ios/mac系统私有库的二进制文件?

前言

长久以来,对ios下的库framework的概念的了解都十分有限,在我眼里,他基本等同于header+lib的集合,尽管这个理解有问题,但一直没有对我造成什么影响。直到前一段时间,我想搞明白系统安装app时的运行机制,就不得不去分析安装过程。
在这个过程中,遇到了非常多的苦难,直到终于发现了最关键的地方——发现其位于某个私有库中,但我费尽力气,也找不到这个私有库的任何信息,连头文件都没有。
好在,在gayhub里找到了,但是二进制文件我是手机和电脑上都找不着,直到今日——准确来说是直到刚才,才在众多大佬的帮助下,找到了这篇文章http://www.blogfshare.com/decache-dyld-shared-cache.html,理论上已经解决了我的问题,但问题是,我的乞丐本,撑不下啊,这个工具实在不够轻量。

简单的办法

一直以来我都喜欢走些所谓的“捷径”,这时我同样在gayhub上找到了jtool来快速导出库的二进制文件。

jtool下载地址:http://www.newosxbook.com/tools/jtool.tar,下载后解压,然后把jtool放到$PATH下的目录即可。

然后,需要在手机上找到库的缓存文件,他们位于 /System/Library/Caches/com.apple.dyld/下,然后拖下来。

最后,

jtool - name path/to/dyld_shared_cache_armvx

打完,收工。

更简单的办法

经过与众大佬的讨论,发现~/Library/Developer/Xcode/iOS DeviceSupport 里面本来就有各个私有库的bin文件,可以直接使用。

更更简单的办法

其实完全没必要解包,hooper可以直接识别。

hopper

使用方法不太懂

你好,看了v2的readme还是不太懂,比如:打包js并上传index.html和static目录。

能否将使用方法描述具体一些呢?谢谢

ThinkPHP中U函数和redirect、success方法的区别。

关于这三者的区别,我们可以看下thinkphp的源代码。

U函数

/**
 * URL组装 支持不同URL模式
 * @param string $url URL表达式,格式:'[模块/控制器/操作#锚点@域名]?参数1=值1&参数2=值2...'
 * @param string|array $vars 传入的参数,支持数组和字符串
 * @param string|boolean $suffix 伪静态后缀,默认为true表示获取配置值
 * @param boolean $domain 是否显示域名
 * @return string
 */
function U($url = '', $vars = '', $suffix = true, $domain = false)
{
//省略
}

其实他的注释已经说的很清楚了,返回值是一个string类型,其实返回的是生成的网址。
他不是一个动作,只是一个辅助函数而已。

success方法

    /**
     * 操作错误跳转的快捷方法
     * @access protected
     * @param string $message 错误信息
     * @param string $jumpUrl 页面跳转地址
     * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
     * @return void
     */
    protected function error($message = '', $jumpUrl = '', $ajax = false)
    {
        $this->dispatchJump($message, 0, $jumpUrl, $ajax);
    }

    /**
     * 操作成功跳转的快捷方法
     * @access protected
     * @param string $message 提示信息
     * @param string $jumpUrl 页面跳转地址
     * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
     * @return void
     */
    protected function success($message = '', $jumpUrl = '', $ajax = false)
    {
        $this->dispatchJump($message, 1, $jumpUrl, $ajax);
    }

这里可以很明显的看到,successerror都是封装的dispatchJump方法,区别是第二个参数。
我们再去dispatchJump看看。

 /**
     * 默认跳转操作 支持错误导向和正确跳转
     * 调用模板显示 默认为public目录下面的success页面
     * 提示页面为可配置 支持模板标签
     * @param string $message 提示信息
     * @param Boolean $status 状态
     * @param string $jumpUrl 页面跳转地址
     * @param mixed $ajax 是否为Ajax方式 当数字时指定跳转时间
     * @access private
     * @return void
     */
    private function dispatchJump($message, $status = 1, $jumpUrl = '', $ajax = false)
    {
        if (true === $ajax || IS_AJAX) {
            // AJAX提交
            $data           = is_array($ajax) ? $ajax : array();
            $data['info']   = $message;
            $data['status'] = $status;
            $data['url']    = $jumpUrl;
            $this->ajaxReturn($data);
        }
        if (is_int($ajax)) {
            $this->assign('waitSecond', $ajax);
        }

        if (!empty($jumpUrl)) {
            $this->assign('jumpUrl', $jumpUrl);
        }

        // 提示标题
        $this->assign('msgTitle', $status ? L('_OPERATION_SUCCESS_') : L('_OPERATION_FAIL_'));
        //如果设置了关闭窗口,则提示完毕后自动关闭窗口
        if ($this->get('closeWin')) {
            $this->assign('jumpUrl', 'javascript:window.close();');
        }

        $this->assign('status', $status); // 状态
        //保证输出不受静态缓存影响
        C('HTML_CACHE_ON', false);
        if ($status) {
            //发送成功信息
            $this->assign('message', $message); // 提示信息
            // 成功操作后默认停留1秒
            if (!isset($this->waitSecond)) {
                $this->assign('waitSecond', '1');
            }

            // 默认操作成功自动返回操作前页面
            if (!isset($this->jumpUrl)) {
                $this->assign("jumpUrl", $_SERVER["HTTP_REFERER"]);
            }

            $this->display(C('TMPL_ACTION_SUCCESS'));
        } else {
            $this->assign('error', $message); // 提示信息
            //发生错误时候默认停留3秒
            if (!isset($this->waitSecond)) {
                $this->assign('waitSecond', '3');
            }

            // 默认发生错误的话自动返回上页
            if (!isset($this->jumpUrl)) {
                $this->assign('jumpUrl', "javascript:history.back(-1);");
            }

            $this->display(C('TMPL_ACTION_ERROR'));
            // 中止执行  避免出错后继续执行
            exit;
        }
    }

我们看到了,这里面没有任何关于跳转的代码,只是装载了模板,注册了几个模板变量并显示而已。
那么它是在哪里跳转的呢?显而易见地,是在模板上。我们去默认模板看看:

(function(){
var wait = document.getElementById('wait'),href = document.getElementById('href').href;
var interval = setInterval(function(){
    var time = --wait.innerHTML;
    if(time <= 0) {
        location.href = href;
        clearInterval(interval);
    };
}, 1000);
})();

可以看到,是通过javascriptlocation.href进行的跳转。即他是客户端实现的跳转。

redirect 方法

    /**
     * Action跳转(URL重定向) 支持指定模块和延时跳转
     * @access protected
     * @param string $url 跳转的URL表达式
     * @param array $params 其它URL参数
     * @param integer $delay 延时跳转的时间 单位为秒
     * @param string $msg 跳转提示信息
     * @return void
     */
    protected function redirect($url, $params = array(), $delay = 0, $msg = '')
    {
        $url = U($url, $params);
        redirect($url, $delay, $msg);
    }

我们可以看到,redirect方法先是用U函数获取了地址做参数,传给了redirect函数。是对这个函数的封装。
我们再去redirect函数看看。

/**
 * URL重定向
 * @param string $url 重定向的URL地址
 * @param integer $time 重定向的等待时间(秒)
 * @param string $msg 重定向前的提示信息
 * @return void
 */
function redirect($url, $time = 0, $msg = '')
{
    //多行URL地址支持
    $url = str_replace(array("\n", "\r"), '', $url);
    if (empty($msg)) {
        $msg = "系统将在{$time}秒之后自动跳转到{$url}";
    }

    if (!headers_sent()) {
        // redirect
        if (0 === $time) {
            header('Location: ' . $url);
        } else {
            header("refresh:{$time};url={$url}");
            echo ($msg);
        }
        exit();
    } else {
        $str = "<meta http-equiv='Refresh' content='{$time};URL={$url}'>";
        if (0 != $time) {
            $str .= $msg;
        }

        exit($str);
    }
}

可以看到一个很短的函数,有两种服务端跳转方式,分别用于是否已输出了http头部的情况。是服务端的跳转。

总结

U函数只会用来生成一个url,不会执行跳转。
success方法本身不会跳转,而是因为默认的模板上有跳转的javascript代码。
redirect方法是调用的服务端函数进行的跳转。

我完全不敢相信,竟然有如此奇葩的包

root-check
这个包会检测到用户是否在root权限下运行,然后尽可能的降低权限,问题是关键是这一切是静默的,你根本不知道发生了什么。

就在刚才,我准备制作一个vscode插件,开始运行 yo code时,却提醒我没有权限。

fk, 我可是root用户啊,怎么可能没有权限,当时就怀疑是不是给我降权了,一番搜索后,果然找到了
yeoman/yo#348 (comment)

sed -i -e '/rootCheck/d' "${NPM_CONFIG_PREFIX}/lib/node_modules/yo/lib/cli.js"

这才发现了这个奇葩的包,关键yo还使用了它。
sindresorhus/root-check#1

无fuck可说

vs2022 with vsvim2022

故事开始了

近日来经济拮据,以前用的挺顺手的Jetbrans 系列也用不起了,迫于经济压力,决定重回宇宙第一编辑器——visual studio,我对它的印象就是巨大且硬,像一坨翔一样,根本难以直视,但如今没有办法,只有捏着鼻子用了。

我是一个vim的忠实粉丝,即使在VisualStudio里也不例外,好在visual studio 也有社区提供了类似于ideavim 的插件,但社区毕竟是社区,基本上用爱发电,怎么可能比得过有专门预算的ideavim 团队呢。我已经可以想象到里面的巨坑。

配置vsvim

正如ideavim.ideavimrc 一样,vsvim 也有自己的.vsvimrc ,这很好。同样地,正如ideavim:action xxxx <cr> 可以调用IDE的命令一样,vsvim 也可以通过:vsc xxxx <cr> 调用vs的命令。这非常好。那么下一步就是获取指定的 vs command,but how to do it?

获取 vs commnad

https://github.com/JetBrains/ideavim#finding-action-ids 在ideavim里就做的很好,它内置了一个功能--track action ids 。显然, 想在vs里实现同样的功能,我不知道是否自带了这样的功能,反正我是没找到。但我找到了这个 https://learn.microsoft.com/en-us/visualstudio/ide/default-keyboard-shortcuts-in-visual-studio?view=vs-2022#bkmk_edit-popular-shortcuts 后来我突然想到会不会有个一个插件来实现类似的事情呢,经过一番查找,我找到了这个https://marketplace.visualstudio.com/items?itemName=MadsKristensen.LearntheShortcut 但遗憾的是,它不支持我所使用的vs2022,经过查看它的代码https://github.com/madskristensen/ShowTheShortcut,已经三年没有更新了。。

经过一番**挣扎,理智战胜了懒惰,我决定重写它。

一边看巨硬的文档,一遍各种摸索,花费一天半的事件,终于完成了类似ideavim的track action id的功能,我给它取名Command Tracker 上架到了微软的插件商店里,至此终于可以安心的配置个各种奇怪的快捷键了。

快捷键冲突

造完上述的轮子,我满以为可以快捷的coding了,事实证明我还是太年轻了,由于历史原因,vs 的快捷键成千上万,复杂无比,而这其中,甚至有奇葩的组合快捷键,就比如 Ctrl+K 、Ctrl+C 是用来注释的,诸如此类的快捷键盘不胜枚举,那就会造成一个问题,vim的快捷键怎么办,vim里也有许多重要的ctrl系列快捷键,比如Ctrl+R 的redo,Ctrl+W 的分屏系列,Ctrl+D Ctrl+U 这种翻页的,那我可怎么用?

vim文档中关于键盘疑难解答 的描述,用手动去一个个删除这些快捷键,我去看了下,尼玛,一个Ctrl+R下都有几十个各种scope的的快捷键,这他妈要一个个删完,不把人累死了?

偷懒,又去插件市场上看看有没有类似能一键删除某个快捷的所有绑定的插件,一番寻找后,答案是——没有!这个时候又突发奇想,能不能把快捷键的配置文件导出,然后对吧,批量删除,然后再导入进去!我都为自己这个机智的想法感到自豪!一番寻找,找到了一个能导出键盘配置的插件justcla/VSShortcutsManager ,它刚好具备了导出&导入的功能。

直到我看到了最终导出的结果,我才觉得有点儿不对劲,这什么鬼?

这里面竟然有RemoveShortcut 这说明了什么呢,这说明了在vs里,有些快捷键是默认定义的,一开始就是有默认的快捷键,后来的配置如果想要定义自己的快捷键,需要先取消掉原来的,这是什么天才设计!

这意味着哪怕我把这个配置文件全删了,导入后应该还是有快捷键的,一番试验过后发现果然如此,并且这个RemoveShortcut 校验还挺严格,里面的Command或者Scope不对,还删不了,这尼玛,还没有我去设置里手动删除快呢!!

没办法,又花费了两天时间,给上面的justcla/VSShortcutsManager 改造了改造了,重新添加了几个功能

  • 重构项目
  • 修正搜索
  • 增加ListView列表模式
  • 增加按照快捷键搜索
  • 增加指定位置导出
  • 增加批量删除

其中,最重要的是

能根据某个快捷键搜索到对应所有的对应的命令,并且可以批量删除

把指定的快捷键都删除了之后,终于可以愉快的使用起来vim的快捷键了。

故事完了没有

后面还有没有坑,我还不知道,我现在心里并不踏实。。不过,艺多不压身,这两天造了很多 vs插件得轮子(以上得两个是造成功得,还有很多夭折的,主要是vs的插件很多地方都要靠摸索),希望别有什么我解决不了的问题吧。

dotnet的webapi模型验证的一个小坑

考虑以下一种情况

引言

// nullable enabled
public enum Role
{
    Admin = 0,
   User =1,
}
public class SomeDto
{
  [Required]
  public int test {get;set;}
  [Required]
  public DateTime  test1 {get;set;}
  [Required]
  public Role Role {get;set;}

假如此时我们post一个空的json body {}, 你以为他能通过校验吗?
答案是
什么?那么大的 [Required]你看不到吗?一开始我也是这么想的,但我付出了惨烈的代价。。。

故事

客户一直给我反馈,说有人不经过允许就修改了xx数据,随随便便就越权干了xxx,我还以为忘记配置权限了,但仔细检查过发现也没问题啊,后台一看,原来这个用户被修改成了管理员。
我大感震惊,这都3022年了,难道还有人能黑的掉我基于宇宙最强最硬的公司开发的最新一代dotnet7和ef7的系统吗?或者前端被xss了嘛,仔细一想也不能够啊,没有能够xss的地方,而且全系统基于efcore,满补丁的那种。
但是,安全无小事嘛,这个问题困扰了我一夜的时间。
第二天中午睡醒后,突发奇想,有没有可能是运营脑袋瓦特了,用超管账号给其他人上了管理员权限?查了一番audit logs,果然如此,我一阵窃喜,妈的锅终于有人背了。
于是,我就在内部群里一阵阴阳怪气。
让我尴尬的脚趾扣三室一厅的过程就按下不表了吧。

定位

反正最后定位到具体的场景就是,修改用户信息的dto里有类似如下伪代码

public class UpdateUserDto
{
  [Required]
  public Role Role {get;set;}

在后台的界面上,这是一个下拉框,于是同时,后台的前端页面为了节流,有个优化输入的功能,即如果只向服务端提交本地修改了的数据。(这是一个很棒的做法,也符合HTTP的PATCH请求的要求)
灾难就发生在管理员修改其他用户信息时,自然不会管默认处于 USER ROLE的下拉框。于是。。。
就没有向服务端发送 role这个字段。
但我以为它是会向服务端发的,不然肯定会报错。

分析

非常的反直觉是,确实不会报错。
一步步调试代码才发现,原来dotnet的mvc中间件会先使用json反序列化 content-type为json的请求,在反序列化时,由于启用了nullable,而Role又是 non-nullable的,所以反序列化后会给Role一个默认值,而Role这个枚举的默认值又是其第一个元素,而其第一个元素又是Admin,所以如果用户不传Role,那Role就是Admin。
离了个大谱哇我真的是吐了。
这就意味着在一个 non-nullable的参数上放置[Required]是完全没有意义的,因为在校验之前它就已经有一个默认值了。

方案

问题找到了,那怎么解决呢?查阅了dotnet的相关文档,其中提到这种情况的是

dontet还给它取了个名字叫 Under-Posting,并且建议我们使用

public class UpdateUserDto
{
  [Required]
  public Role? Role {get;set;}

看到这里我直接想骂人了,这也太弱智了吧,使用可用类型,就意味着后续的每次使用都要用Role.Value,而这明明又是不可空的,这简直太反人类了。

想了想,我最终还是保持接口没变(实际上主要是甩锅给前端)

ubuntu work on iosre

安装python

sudo apt-get update; sudo apt-get install --no-install-recommends make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev

安装iproxy

Binary package “libusbmuxd-tools” in ubuntu focal

xx消消乐的逆向分析与利用

前言

今天我们来研究一下如何收获妹子崇拜的眼神,从而获得妹子的芳心...啊不,是如何在妹子面前装一个圆润的漂亮的逼。即帮他减轻喜欢的游戏的压力,让她知道游戏是如此的...无聊。
这款游戏的名字叫《xx消消乐》,怎么样,一看到这个游戏就知道妹子显然是一个清纯的人。
这里,我们的目的是使用多种方式来给这款游戏作弊。
本次探索中可能重要的技术或工具为

  • ida
  • frida
  • lua
  • c/c++

但本文只为技术交流,无任何赢利目的。但恶意修改游戏是违法行为,各位读者需自负责任,不要走上违法犯罪的道路。本文作者不负相关责任。
且本文所分析样本为2019年1月份的某个版本,现行版本不一定适用,因此所有代码仅供参考。同时,我也希望能对游戏行业的开发人员有点警示作用,使之明白安全防护的重要性,以及现在裸奔是多么的危险。

分析

引擎分析

正如之前提到的,分析一个游戏的第一步,显然是先分析到游戏所用的引擎。常见的框架有cocos2dUnity3dunrealengine等。本次探索的目标是使用的lua引擎,这类游戏和U3d一样,都非常的简单,只要反编译到游戏的源代码,则基本如履平地,驰骋疆场。
你问我如何知道它是lua?最简单的方法是拖到ida里面一顿梭,只要含有lua相关关键字的,一般八九不离十就是了,另外,对于安卓来说,包解压后直接看lib目录里是否有libcocos2dlualiblua,libhellolua等即可快速判断出。由此,我们可以得出结论。除了这款游戏之外,《梦幻西游》《奇迹暖暖》等也是这个引擎,也可按本文下述套路一顿梭。

lua引擎的弱点

  1. 基于lua是一种脚本语言的说法,且为了开发和更新方便,一般安全意识较弱的公司,对lua脚本的存放都在资源文件夹里,有的甚至文件根本没有加密。
  2. 稍微安全意识较强的,可能会把lua给打包存放,甚至加密存放。但这些属于徒劳,顶多算是自欺欺人,因为最后在lua虚拟机装载的时候,总要进行解密,我们可以在这个时候勾住装载函数,以获取所有的脚本。本文示例的游戏即是此类型。
  3. 安全意识更强的,则考虑把lua编译后再放入客户端,此时攻击者无法直接获取到lua的源码,取而代之的是获取到编译后的结果。但显然这也是自欺欺人的表现,因为lua解释器开源的,制作一个lua的反编译工具是非常简单的,且现在已经有很多的实现。反编译后依然能得到源码。
  4. 安全意识极强的,可能由上述方案更进一步,既然lua解释器开源的,那就对lua解释器进行魔改,使之成为非标准的,则市面上成型的工具不是全都失效了吗?显然是。这种方案是安全程度相对较高的。但也不是绝对安全。因为解释器还是放在客户端,我们只需要分析出解释器修改了那些地方,再同样的修改反编译工具即可。之前发布的触动精灵加密脚本还原即是对应的逆向实现。

实战

获取脚本

根据上述的理论知识,我们直接依次尝试,结果发现此游戏属于运行时解密的类型,(即上述第2种),我们直接使用frida来做勾取

nptr = Module.findExportByName(null, "luaL_loadbuffer");
var keep = null;
var keep2 = [];
var keep1 = null;
if (!nptr) {
    console.log("luaL_loadbuffer can be found!");
} else {
    console.log("find %d", nptr);
    Interceptor.attach(nptr, {
        onEnter: function(args) {
            var len = args[2].toInt32();
            var code = args[1].readCString(len);
            send({ path: args[3].readCString(), dump: code });
        }
    }
}

上述脚本,hook了

int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);

这个函数,第一个参数为lua虚拟机指针,第二个为代码的字符串buff,第三个为代码的长度,最后一个则为这个脚本的名字,由此,天时地利人和,参数齐备,我们获取之后发回到frida即可保存在主机上,当然frida这边需要保存。

def savefile(path, data):
    create_dir(os.path.dirname(path))
    with codecs.open(path, 'w', 'utf-8') as f:
        f.write(data)
def on_message(message, data):
    if 'payload' in message and message['type'] == 'send':
        payload = message['payload']
        if 'dump' in payload:
            origin_path = payload['path']
            data = payload['dump']
            savefile(origin_path,data.encode("utf-8"))



            return
        if message['type'] == 'send':
            print("[*] {0}".format(message['payload'].encode('utf-8')))
        else:
            print(message)

此时,我们即可成功dump《xx消消乐》的所有代码,如下
kaixin

在图中,我们甚至可以看到注释。。。显然,这家公司心太大了。

无限步数

既然有源码,甚至有注释,这一步的工作可以说是非常简单了。我们一番搜索之后发现上图中所示位置

	mainLogic.theCurMoves = mainLogic.theCurMoves - 1;
  if mainLogic.PlayUIDelegate then --------调用UI界面函数显示移动步数

mainLogic.theCurMoves即代表剩余步数,我们可以选择每走一步+1,或者一直不减....等等逻辑。

无限精力

经过上面这么一整,显然是舒服了,再也不担心会输了,一口气过了好多关。但接下来问题出现了,精力不够了,怎么办?
只能盘他了。
我们一顿直接搜索jingli然而什么都没找到,看来这家公司技术不怎么行但英语还是不错的。我给我初中同学打了个电话,问了问她精力的英文怎么拼,她骂了我一顿说我神经病然后让我百度翻译....然后一顿翻译以后得知他的英文果然不错。正则搜索energy[\s]+=果然命中。

function UserRef:setEnergy(v)
	local key = "UserRef.energy"..tostring(self)
	self.energy = v --onlu used for encode
	encrypt_integer_f(key, v)
end

更牛逼的是这里面的注释竟然写着他有编码,嗯,显然是为了防止类似ce八门神器等的业余玩家,我百度了一下,发现这个公司的技术负责人竟然在教别人游戏怎么防攻击。。。好吧,我实在不知道该说什么好了。

无限金币/风车币

这个就简单了,就在上面精力的下面

function UserRef:getCoin()
	local key = "UserRef.coin"..tostring(self)
	return decrypt_integer(key)
end
function UserRef:setCoin(v)
	local key = "UserRef.coin"..tostring(self)
	self.coin = v --onlu used for encode
	encrypt_integer(key, v)
end

function UserRef:getCash()
	local key = "UserRef.cash"..tostring(self)
	return decrypt_integer(key)
end
function UserRef:setCash(v)
	local key = "UserRef.cash"..tostring(self)
	self.cash = v --onlu used for encode
	encrypt_integer(key, v)
end

function UserRef:getRealTopLevelId()--最高通过关卡而不是最高停留关卡
	local topLevel = self:getTopLevelId()	
	local levelScore = UserManager.getInstance():getUserScore(topLevel)
	if levelScore and levelScore.star > 0 then
		return topLevel 
	else
		return topLevel - 1
	end
end

function UserRef:getTopLevelId()
	local key = "UserRef.topLevelId"..tostring(self)
	local level = decrypt_integer_f(key)
	if level > kMaxLevels then level = kMaxLevels end
	return level
end
function UserRef:setTopLevelId(v)
	local key = "UserRef.topLevelId"..tostring(self)
	self.topLevelId = v --onlu used for encode
	encrypt_integer_f(key, v)
end

function UserRef:getStar()
	local key = "UserRef.star"..tostring(self)
	return decrypt_integer(key)
end
function UserRef:setStar(v)
	local key = "UserRef.star"..tostring(self)
	self.star = v --onlu used for encode
	encrypt_integer(key, v)
end

function UserRef:getHideStar()
	local key = "UserRef.hideStar"..tostring(self)
	return decrypt_integer(key)
end
function UserRef:setHideStar(v)
	local key = "UserRef.hideStar"..tostring(self)
	self.hideStar = v --onlu used for encode
	encrypt_integer(key, v)
end

function UserRef:getEnergy()
	local key = "UserRef.energy"..tostring(self)
	return decrypt_integer_f(key)
end
function UserRef:setEnergy(v)
	local key = "UserRef.energy"..tostring(self)
	self.energy = v --onlu used for encode
	encrypt_integer_f(key, v)
end

function UserRef:getUpdateTime()
	local key = "UserRef.updateTime"..tostring(self)
	return decrypt_number(key)
end
function UserRef:setUpdateTime(v)
	local key = "UserRef.updateTime"..tostring(self)
	v = tonumber(v) or Localhost:time() --onlu used for encode
	self.updateTime = v
	encrypt_number(key, v)
end

function UserRef:encode()
	local dst = {}
	self.updateTime = self:getUpdateTime()
	self.energy = self:getEnergy()
	self.hideStar = self:getHideStar()
	self.star = self:getStar()
	self.topLevelId = self:getTopLevelId()
	self.cash = self:getCash()
	self.coin = self:getCoin()

	for k,v in pairs(self) do
		if k ~="class" and v ~= nil and type(v) ~= "function" then dst[k] = v end
	end
	return dst
end

这里面什么都有了,对着修改即可。。

开发者模式

对代码一番研究,竟然发现有开发者模式。
而且就是前几行

_G.kUseSmallResource = true
_G.kScreenWidthDefault = 720
_G.kScreenHeightDefault = 1280
_G.kDefaultSocialPlatform = "ios_all"
_G.kUserLogin = false
_G.isLocalDevelopMode = StartupConfig:getInstance():isLocalDevelopMode()

也是牛逼顶天了。开发者模式开启后,界面会多出来一些了不得的功能。

GM模式

这个就不讲实现了吧。。。。

完整frida脚本

function patchLua(src, dst, args) {
    var origLength = args[2].toInt32();
    var test = args[1].readCString(origLength);
    if (test.indexOf(src) > -1) {
        var test2 = test.replace(src, dst); //"mainLogic.theCurMoves<100?mainLogic.theCurMove+ 1:mainLogic.theCurMove - 1;");
        //test3.writeByteArray(strToBinary(test));
        var tmpP = Memory.allocUtf8String(test2);
        keep2.push(tmpP);
        args[1] = tmpP;
        var length = getStringLen(args[1]);
        args[2] = ptr(length);
        console.log(src + "patch ok");
    }
}

nptr = Module.findExportByName(null, "luaL_loadbuffer");
var keep = null;
var keep2 = [];
var keep1 = null;
if (!nptr) {
    console.log("open can be found!");
} else {
    console.log("find %d", nptr);
    Interceptor.attach(nptr, {
        onEnter: function(args) {

            var origLength = args[2].toInt32();
            var test = args[1].readCString(origLength);
            //send({ path: args[3].readCString(), dump: test });
            if (test.indexOf("mainLogic.theCurMoves - 1") > -1 &&
                args[3].readCString().indexOf("BonusStep") < 0) {
                var test2 = test.replace("mainLogic.theCurMoves - 1", "mainLogic.theCurMoves + 1"); //"mainLogic.theCurMoves<100?mainLogic.theCurMove+ 1:mainLogic.theCurMove - 1;");
                //test3.writeByteArray(strToBinary(test));
                var tmpP = Memory.allocUtf8String(test2);
                keep = tmpP;
                args[1] = tmpP;
                var length = 0;
                var p = args[1];
                var length = getStringLen(args[1]);
                args[2] = ptr(length);
            }
            //修改精力
            patchLua("self.energy = v", "v=30\nself.energy = v", args);
            //修改风车币
            patchLua("self.cash = v", "v=79878\nself.cash = v", args);
            //开发者模式
            //patchLua("StartupConfig:getInstance():isLocalDevelopMode()", "true",args);
        },
        onLeave: function(retval) {}
    });
}

防检测

逆向这么强的吗?那游戏公司不是混不下去了???显然不是,虽然客户端是攻击者的主场,但游戏公司也有自己的主场,那就是各种层出不穷的检测。。

但一般来讲,我们不应该去修改lua源码本身,因为可能大多数的公司都会有lua源码进行类似crc,md5adlerhash等校验,我们一旦对源码进行修改,则意味者被认定为作弊玩家。因此,对于此种lua游戏,可以选择再次加载自身的lua脚本,因为该脚本也和游戏脚本在同一个虚拟机的命名空间之内,由此可以实现变量的覆盖,函数的调用等。

但即便如此,也不可能全然绕过游戏的检测和防御。而关于如何绕过?这个是最有技术含量的,一般来说,功能的实现都是比较简单的,但往后检测的对抗才是智力体力的终极对抗。

我为什么要使用markdown?

前言

其实本次博客更新完成后,有细心的朋友发现我已经使用了markdown语法,因为在整个博客中到处充斥着markdown的特点,说起来用markdown也有三年的时间了,本次也更一篇博客作为介绍。

Markdown的意图

如果你是个经常码字的人,你肯定有所体会,码字过程一半时间用在遣词造句,一半时间则在死磕排版。那么有没有一种方法能够自动排版,且各处通用呢?
Markdown是一种轻量级的「标记语言」。是为那些经常需要码字或者进行文字排版的、对码字手速和排版顺畅度有要求的人群设计的,他们希望用键盘把文字内容打出来的同时搞定排版,最好从头到尾都不要使用鼠标。这些人最常见的是经常需要写文档的码农,另外包括博客写手、网站小编、出版业人士等等。

如果你是程序员,你可以用来泡技术论坛、写博客日志、技术文稿、记录代码片段、起草邮件 如果你是科研人员/工科学生,你可以用来撰写科技论文,记录工科笔记 如果你是文字工作者/热爱文字的人,你可以用来编辑文档 如果你是微信运营人员,你可以用来代替微信编辑器编写微信文章

Markdown的特点

易写易读是Markdown语法的最大亮点,也是它为什么能提高书写效率的原因。

易写

Markdown语法十分简单,常用的标记符号不超过十个,用于日常写作记录绰绰有余,非程序员人群不到半小时完全能够掌握。但这十个不到的标记符号,却能让人优雅地沉浸式写作,专注内容而 不纠结排版,大大提高书写效率。名副其实的「码字神器」。

易读

兼顾“什么人都能打开” 和 “版样式不变”。 所谓“什么人都能打开”是指,Markdown属于兼容性极强的纯文本,可以用所有文本编辑器打开,避免了“用 Windows 的人打不开 .pages 文件”的情况发生;Markdown文本轻松可转成HTML、电子书等格式,而HTML 是整个万维网(web)的标记语言,也是目前主流电子书格式( EPUB、mobi、Kindle专有格式 .azw)所用的标记语言。人们如果采用Markdown 标注格式,对日后的文件转换工作将大有裨益。

所谓“排版样式不变”是指,在转化为富文本/HTML等格式后依然可以保留原本的排版和阅读体验。不会出现“我这篇稿子是用旧版 Word 写的,你用新版 Word 看可能格式会有点问题”的情况。

这是因为,实质上markdown的本质仍是一种基本文本,并非word等后期加工自我渲染的结果。这与html其实非常相似。但是html有几个比较明显的缺陷。比如对于非专业认识来说认知困难,另外html的过于强大和驳杂的标记也对安全造成了比较大的影响。
而Markdown实质上是html其中的几个标记的友好宏定义,使其对书写更加的便利和方便。

MarkDown的语法

标题

html中或word中,标题是非常重要的一个概念。在html中,默认定义了从大到小的标题的标签为<h1><h2><h3><h4><h5><h6>,那么在MarkDown中也是类似的设定为*********************。 比如在html中想要输出

<h1>我是大标题</h1>

MarkDown中就需要

# 我是大标题  

同理,<h2>就等于##,以此类推。

段落

这里我要着重做一个解释,常常有新手问我_MarkDown的换行是什么?,其实在MarkDown中根本没有换行的行为,只有段落的概念。_
段落在html中是<p>,这个标记比较简单,而在MarkDown中没有相对应的标记去对应它,因为你写个一段文字本身就是段落。
比如在MarkDown中写

我是段落

对应的html

<p>我是段落</p>

引用

引用对于一篇文章是不可或缺的,我们常常需要引经据典,在MarkDown中,我们用>来表示引用。 比如在MarkDown中写

> 举头天外望,无我这般人。

对应的html

<blockquote>举头天外望,无我这般人。</blockquote>

修辞、强调

Markdown 使用星号来标记需要强调的区段。 比如在MarkDown中写

后面的 *被强调*.
后面的 **着重说明**.

对应的html

<p>后面的<em>被强调</em>.</p>
<p>后面的<strong>着重说明</strong>.</p>

列表

html中有序列表和无序列表,同样地,MarkDown也支持如此。

有序列表

有序的列表则是使用一般的数字接着一个英文句点作为项目标记。 MarkDown这样写

1. 嘿嘿
2. 哈哈
3. 嘎嘎

对应的html

<ol>
<li>嘿嘿</li>
<li>哈哈</li>
<li>嘎嘎</li>
</ol>

无序列表

无序列表使用星号来做为列表的项目标记。 MarkDown这样写

* 嘿嘿
* 哈哈
* 嘎嘎

对应的html

<ul>
<li>嘿嘿</li>
<li>哈哈</li>
<li>嘎嘎</li>
</ul>

链接

MarkDown这样写

欢迎访问[青枫浦](https://post.zz173.com/)

对应的html

<p>欢迎访问<a href="https://post.zz173.com/">青枫浦</a>.</p>

相信聪明的你一定发现了,前面的就是点击链接上面的文字,后面的就是链接地址。是不是很简单。

图片

和上面链接的语法一模一样,只是前面需要个叹号。 MarkDown这样写

![美女](https://post.zz173.com/meinv.jpg)

代码

用一个```之间的代表代码。

三个```之间的代表成段代码。

分割线

---代表html中的<hr>,可以画出一条分割线。

书写工具

  • 对于Mac用户用来说,Mod可能是一个不错的选择。
  • 对于windows用户的老手来说,我个人使用的是Sublime Text3配合的MarkDownEditing插件。好处在于习惯了,并且心中已经知道大概什么样子。对于老手来说推荐使用。缺点在于没法导出PDF等。
  • 对于windows用户的新手来说,推荐使用Typora

为什么选择了pnpm

因为yarn2实在太难用了,实在太坑爹了。
它完全不配叫yarn2
Pnp 现在在很多场景下也依然存在问题,目前对我来说最大的问题有两个

  1. microsoft/WSL#5118 由于wsl2的bug,在wsl2的远程里,pnp使用了连接,导致了文件不能正确的被访问到。
  2. pnp 目前不知道 package.json import关键字来指定alias map

类似的观点有:
vitejs/vite#304 (comment)

iOS Jailbroken Programming

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.