等额本息的战争

安全界老大哥@yuange1975最近揭露微博借钱的套路贷导致微博账号被删。套路贷问题本身还是很有意思的,很像是那种偷换概念的陷阱趣味题。

问题来了,如果借了20000块钱,一年还完,每个月要求还款2266.67,那么究竟这笔贷款的年利率该怎么算呢。

一个错误的示范是,年利率P = (2266.67 * 12 – 20000)/20000 = 0.36。也就是说一年才36%而已,好像很符合国家规定的上限。

很久不实际推算点什么东西了,这里的陷阱还真一下没反应过来。题干简单的背后,是故意模糊的概念。首先什么是年利率,一般来说它是一个等价概念,粗略说就是一笔钱过一年后增加的数目/初始钱数。如果一年到期后,一次性还2266.67 * 12 = 27200的话,那年利率计算结果就是36%。

但现在提前还了钱,先不论每个月的钱里有多少是本金,多少是利息,显然对于借钱的你是要亏一些的,而对于债主来说,它能提前拿到钱是很赚的,最次最次他拿到钱以后存到余额宝也有利息啊。所以直观感受是,这种还款年利率应该是比一年以后一起要高的,但究竟怎么量化这种感觉呢。

在此期间我看到有公众号也在讨论这个问题,其解决方法是经济学里的IRR公式。本来应该主动去分析的问题,现在都成了被动的朋友圈推送文章,久而久之现在处理问题好像更加被动了。另外公众号的文章从来都是短小精干(浅尝辄止)。即便后来找到了IRR公式“原理”,依然不甚过瘾。记得大学时候替学生会的一个部长喊到,上过一节经济学原理的课程,依稀记得课上的一道题目我是用等比数列极限求解,而老师给出的是经验公式,且没有推导过程。所以至今我仍冒昧且莽撞的认为经济学里的很多理论,由于很多人不去深究其背后数学根基,讲出来的话也就特别缺少逻辑的严谨性,仅用于计算倒是没有问题。不过这样的分析文字总让人坐立不安,一个享受证明题那种逐层推倒过程的人特别不能接受。

所以这个利率问题,感觉一定要用自己知识范式里的逻辑推导出结果才能真正“过瘾”。我们不去造什么现钱未来升值的概念,也不去假设每个月还的钱里面到底本金是不是相等。现在只是求一个等效的月利率q出来,然后用它刻画我们的直观感受。下面试试在q不变的情况下,是否能求解这个问题。

对于拿到20000块钱后第一个月末,要还2266.67,这时欠人家的本金一直是20000,而这2266.67中包含还掉的本金部分用a表示。于是有:

20000 * q + a = 2266.67

即这个月还款的2266.67是还掉的一部分本金和利息的和。由于欠的本金一直是20000,所以利息就是20000 * q,而a是还掉的本金。

现在来到了第二个月末,仍然还款2266.67,其中还掉的本金用b表示。第二个月开始,我们欠人家的钱已经不是20000,而是20000 – a了,所以这个月的利息应该是(20000 – a) * q,这样就有:

(20000 – a) * q + b = 2266.67

对于第三个月来说,还掉的本金用c表示,依据之前的推导应该有:

(20000 – a – b) * q + c = 2266.67

依次类推,到第十二个月初,我们已经还了十一笔款,每次还的本金依次是a, b, c, d, e, f, g, h, i, j, k,所以现在欠的本金应该是(20000 – a – b – c – d – e – f – g – h – i – j – k),那对于最后一个月来说有

(20000 – a – b – c – d – e – f – g – h – i – j – k) * q + l = 2266.67

最后第十三个等式是,a + b + c + d + e + f + g + h + i + j + k + l = 20000,就是每个月还掉的本金和为最初的欠的20000。

现在一共有十三个方程,十三个未知数,我们只要求出来方程里的q a b c d e f g h i j k l的正实数解即可。这个方程应该可以手动化简,不过偷懒的方法是用现成的工具,如Matlab或者 Wolfram。以Matlab为例,以下命令可以解开方程:

syms q a b c d e f g h i j k l
[q_, a_, b_, c_, d_, e_, f_, g_, h_, i_, j_, k_, l_] =  vpasolve((20000)*q + a == 2266.67,(20000 - a)*q + b == 2266.67,(20000 - a - b)*q + c == 2266.67,(20000 - a - b - c)*q + d == 2266.67,(20000 - a - b - c - d)*q + e == 2266.67,(20000 - a - b - c - d - e)*q + f == 2266.67,(20000 - a - b - c - d - e - f)*q + g == 2266.67,(20000 - a - b - c - d - e - f - g)*q + h == 2266.67,(20000 - a - b - c - d - e - f - g - h)*q + i == 2266.67,(20000 - a - b - c - d - e - f - g - h - i)*q + j == 2266.67,(20000 - a - b - c - d - e - f - g - h - i - j)*q + k == 2266.67,(20000 - a - b - c - d - e - f - g - h - i - j - k)*q + l == 2266.67,a + b + c + d + e + f + g + h + i + j + k + l == 20000,q,a,b,c,d,e,f,g,h,i,j,k,l)

解出来的正实数解为:

q_ = 0.050797584803673053315458232134993
a_ = 1250.7183039265390064504114991344 
b_ = 1314.2517730357534996140256951894 
c_ = 1381.0125889299148582748617671113 
d_ = 1451.1646930310222824133176198543 
e_ = 1524.880354589361811121911194386
f_ = 1602.3405937170699537423596414655 
g_ = 1683.7356259107806443553650286218 
h_ = 1769.2653291549490520943120595088  
i_ = 1859.139734752896094790930379943 
j_ = 1953.5795430908845605782957674657
k_ = 2052.8166656017646249690438355595
l_ = 2157.0947942590636115951655117603

所以这里抽象出来的等效月利率q = 0.050797,则年利率P = 12 * q 或者(1+q)^12 – 1。之所以P是这两种情况,是因为很多金融机构对年利率的定义不同,通常的P = 12 * q 给出的年利率数值会小一些,在贷款时使用更具有迷惑性,让借钱的人直观上觉得划算。但实际计算时用的P应该是(1+q)^12 -1,这是考虑了复利,相当于每个月末把本金取出来,又一起存进去。我认为关于这个q和P的关系比较容易理解,所以就不赘述了。

这样其实等效的P应该是(1+q)^12 – 1 = 0.81,即年利率为81%,比一开始的36%要高很多。这里的81%也是一个概念值,给你这个数值并没有办法直接用于计算比如到底还了多少钱之类的问题。它只是量化了我们最开始的直觉,即每个月还钱比到期一起还时的情况,其利率要更高,而且高很多。

推导到这里,基本上算是完事了。但是这个问题与实际世界是如何建立联系的呢。直到这时,我才了解,其实上述每个月还固定钱的问题就是所谓的等额本息还款。另一种偿还贷款的方法叫等额本金。等额本息看起来舒服,因为每个月数额整装,可背后原理复杂;等额本金看起来数额零散,不过背后计算简单。

既然对应到了现实世界的问题,那就有现实的工具可用。比如同样是这个问题,我们可以打开招商银行的计算工具,在等额本息工具下,输入本金20000,分12个月,年利率部分输入P = 12 * q,即60.96%,点击计算,你就发现其计算结果是每个月还款2266.67。这里银行用的P并没有填写我们刚刚的(1+q) ^ 12 – 1,原因就是银行贷款为了让你心里预期低一些,选择P = 12 * q。其实这两种P如前所述都没法直接用于计算,只是给你一种直观感受。真正计算用到的是q。

关于这个问题至此已经可以说讨论完整了。

最后想从纯数学的角度再看一下刚刚的方程,我们唯一的假设是q它作为一个月等效利率是不变的。如果月利率q可变,而假设每个月还款中本金是固定的呢,即用a b c d e f g h i j k l表示每个月的利率,而还的本金是q,仍然用Matlab解开这个方程就变成了:

syms q a b c d e f g h i j k l
[q_, a_, b_, c_, d_, e_, f_, g_, h_, i_, j_, k_, l_] =  vpasolve((20000 - 0 * q)*a + q == 2266.67,(20000 - 1 * q)*b + q == 2266.67,(20000 - 2 * q)*c + q == 2266.67,(20000 - 3 * q)*d + q == 2266.67,(20000 - 4 * q)*e + q == 2266.67,(20000 - 5 * q)*f + q == 2266.67,(20000 - 6 * q)*g + q == 2266.67,(20000 - 7 * q)*h + q == 2266.67,(20000 - 8 * q)*i + q == 2266.67,(20000 - 9 * q)*j + q == 2266.67,(20000 - 10 * q)*k + q == 2266.67,(20000 - 11 * q)*l + q == 2266.67,12 * q == 20000,q,a,b,c,d,e,f,g,h,i,j,k,l)

结果为

q_ = 1666.6666666666666666666666666667
a_ = 0.03000016666666667030464547375838
b_ = 0.032727454545454549423249607736414
c_ = 0.036000200000000004365574568510056
d_ = 0.040000222222222227072860631677839
e_ = 0.045000250000000005456968210637569
f_ = 0.051428857142857149093677955014365
g_ = 0.060000333333333340609290947516759
h_ = 0.072000400000000008731149137020111
i_ = 0.090000500000000010913936421275139
j_ = 0.12000066666666668121858189503352
k_ = 0.18000100000000002182787284255028
l_ = 0.36000200000000004365574568510056

注意,这里的q已经是每个月要还的本金了,而变化的a到l是每个月的月利率。但现在不过是纯粹的数学计算了,我们并不是在解等额本金问题。通过a到l没办法抽象出一个所谓的等价月利率或者年利率。

题目为什么叫战争呢,因为我和媳妇周末讨论怎么计算时,尽管两个人都一知半解但却几次三番激烈异常,时不时面红耳赤,更有甚者唇枪舌剑。当然,问题最后还是在健康平静恩爱的气氛中解诀了。

下载编译AOSP

下载


Android的源码下载编译可以算是一个持久性老大难问题了,如果是在国内弄,更是涉及政治,经济,历史,文化的方方面面:-)

除了安装Google文档里的依赖库,源码本身的下载编译才是重头戏,我目前比较试过行之有效的方法如下:

首先,从清华开源站下载最新的源码tar包,解压后,修改.repo/manifests.git/config文件

然后,repo init -b xxx到你要的分支,再repo sync就可以了

选择两个站是因为,清华有tar包,可以显著提升下载速度,但至少按照他们的文档来看,仅支持http方式的git同步。相比ssh的git在repo sync阶段要慢且非常容易中断,所以下载好的.repo要修改url指向科大的源。

再来就是repo sync总是提示中断,下载错误,无论你的网络有多好,同步半个小时左右就会出现这个问题。对于这一点也确实试了所有灵丹妙药,最后发现无非就是限制线程数量,然后反复同步,为此专门写了一个脚本自动反复单线程repo sync,想要停止的话,touch stopme即可

 

编译


至于编译一般来说问题不大,无非就是Java版本可能出些问题,或者堆大小要调高之类的,编译开始前就先设置一下堆的大小:

 

刷机


由于编译好的rom并不完整,仅包含boot.img,system.img等基本信息,可能和手机内的其他模块如modem,radio,bootloader等冲突,最好先用同版本或接近版本的factory image刷一次,然后再到编译好的rom目录执行

如果刷机以后系统无法启动,可能是bootloader版本不匹配导致的,从官网下载同版本rom后,先./flash-all一下就好了。

Android下的permission和gid

Android是在linux基础上构建的,权限的管理即要依赖apk中的permission,也要考虑和linux的uid/gid的方式结合。虽然很多操作可以在java层完成,但诸如设备文件的访问,又要回归到传统的uid/gid管理模式,比如设备文件

net_bt_stack组对设备文件是可以操作的,如果查看/system/etc/permissions/platform.xml

可以看到,如果apk申请了android.permission.BLUETOOTH_STACK权限,它的进程将具备linux的uid/gid管理体系下的net_bt_stack组权限

运行该apk,然后查看/proc/pid/status,Groups中也直接阐明了这一点

但这并不意味着,直接su app_id得到的shell具备该gid。Android下的su并不是基于/etc/passwd等文件实现的完整权限切换,仅保留了uid的信息。不过Android提供的run-as可以完整切换权限,得到具备该gid的shell

Immunity Debugger设置JIT

Windows 7以后,Immunity Debugger毕竟不再更新了,很多人开始转用x64dbg了。但用习惯了,除非是x64代码,不然还是不想换呢。Immunity Debugger一直有个不大不小的问题,就是当其他应用crash时,它的即时调试器模式总是启动不起来。

如果直接查看注册表,会发现是程序当前路径获取失败;只要人为添加如下信息到注册表即可让Immunity Debugger成为JIT:

 

 

FlashPlayer针对dll劫持的缓解措施

不知道Adobe究竟是受什么启发,在FlashPlayer的23版本开始引入了针对dll劫持的缓解措施。

FlashPlayer 22的启动参数处理流程示意如下:

而23版本在GetCommandLineA前插入了新的缓解代码,如下所示:

插入的代码功能:FlashPlayer在运行的时候,会检测当前目录是否包含*.dll文件,如果包含,就拷贝自身到temp目录,然后以-relaunched参数启动。

如果以-relaunched启动后的FlashPlayer检测到目录仍然包含*.dll就会弹出错误对话框,然后终止运行。

所以包含dll时,查看进程管理器,看到的FlashPlayer都是这样的形式:

“C:\Users\admin\AppData\Local\Temp\{F0CF3F41-B0CC-44A3-B59F-EA1D57B9DF7C}\FlashPlayer.exe” -relaunched

暴力破解Android锁屏口令

JellyBean开始,Android的锁屏口令以hash形式存放,口令通常是4位数字(对于多位复杂口令方法也是一样的),暴力破解完全可行

锁屏口令的hash存放在/data/system/password.key,形如

1136656D5C6718C1DEFC71B431B2CB5652A8AD550E20BDCF52B00002C8DF35C963B71298

共72个字符,包含Sha1和MD5两个hash,参考Android源码

前40位是Sha1,后32位是MD5,计算(口令+salt)得到hash

salt的存放位置为/data/system/locksettings.db,使用sqlite3打开数据库,输入

就得到形如3582477098377895419的salt值了,最后将其转化为小写的16进制64位整数31b783f0b0c95dfb

有了这些信息用就可以用hashcat跑了,用MD5部分(0E20BDCF52B00002C8DF35C963B71298)爆破的指令为

 

OSX下调试Flash插件

其实无论调试什么,都会发现lldb的功能朴实的让人心急如焚。比如Windows调试器基本都会自动记录上一次的断点信息,每次调试时根据模块位置重新下好端点。

lldb可能天生就是为源码调试准备的,一旦没有源码,根据模块名+偏移的下端点方式它是无论如何都不能识别。好在它提供了python接口,方便开发调试插件弥补自身的缺陷。

所以与其说lldb是个调试器,不说它是个SDK,只有基于它开发出来的图形调试器才具备实用性。

那就来看看一些在Windows调试时不值得一提的简单操作,在lldb下该如何达阵

 

附加Flash进程


用Safari打开指定页面后,执行以下脚本lldb就会附加到包含Flash的进程上了

 

搜索内存中的指定常数


由于没有查看memory layout的命令,只能借助vmmap,先找到比如malloc的内存区域,然后再逐一生成查找命令。以下命令用于从WebKit找到HeapSpray的特定字符:

 

在模块固定偏移下断点


这个听起来是最稀松平常的任务了,比如打算在Flash的0x78A4C0偏移处下一个断点,而且要在每次lldb附加后自动完成。首先要编写一个lldb的插件,完成Flash模块基地址的查找和断点地址的计算,最后下断点:

更多内容

OSX下调试WebKit

Safari的解析和渲染引擎WebKit是开源的项目,并提供了很多脚本方便调试。在OSX下分析一个漏洞还是头一回,用到的技巧大都取自WebKit官方的一篇JS引擎漏洞分析

 

准备环境


首先当然是要下载Webkit的源码,以前写过如何在Ubuntu环境里下载编译WebKit,当时只考虑了最新版本,所以直接从官网下载代码压缩包。

但如果是分析漏洞,一般要根据testcase的描述找到对应版本的WebKit,然后用svn和git下载指定版本的代码。

官方的建议的命令是

由于我查看的testcase信息来自于WebKit的Github镜像,所以用git下载的源码。

不过国内访问git的速度实在不怎么样,最后就用VPS从美国先git clone好整个源码树,然后压缩传回本地

根据testcases找到对应的branch编号以后,再切换过去

更多内容

编译avm

2013年时Adobe停止了对Tarmain项目的维护,不想2016年三月居然重新更新了代码,近几年AVM的变化终于又能借此一窥究竟了。

linux下编译avmplus是最容易的:

Windows下无论是用VS2010还是VS2015都无法顺利编译通过,基本上会遇到两类问题,一个是字符不识别,一个是函数未解析
1. 字符问题在Windows 10时已经不存在了,但Windows 7与到时候也非常容易解决,删掉ErrorConstant文件中非英语部分就OK了
2. 至于 unresolved symbol 问题,可以先查找到缺失类所属的文件,然后把它们加入到工程的对应目录也就OK了,这个错误按说真是很奇怪,怎么会漏掉文件没有加入到项目中呢