这道题卡了我好久好久,知道今天才好不容易得以解决,题目描述如下:

UBC crackme, 

simple but you have to find the critical "cmp" first...

press "start" then download it.

Hint: The checkin key is serial of "HelloWorld"

首先拿到这道题的程序,我本能的第一反应就是在MessageBox上加断点,然后运行的时候,IDA给谈了个窗,我直接没太注意就确定了,可谁想到,就是这不经意的一下,让我掉入了深坑……

点完确定后,IDA就被挂在MessageBox上的断点停住了,一切看起来都很正常,可是,就这样找了好久,一直找不到关键代码,于是也便曾放下了好久,知道今天又拿起来做才终于知道自己怎么掉坑里的了。

仔细看下IDA给的那个提示框:

提示框是说exception发生了,是否传递给程序,这样看起来,我们是进了程序的异常处理部分,这也就难怪我们一步步的像上追踪也没有找到要找的地方。

仔细看下程序给出的提示:

这是要让我们在serial里输入数字,那么我们就按照要求来做,看能不能不进入异常处理部分。

可是,在我们输入的是数字后,程序却没有被我们放在MessageBox上的断点中断住,直接就正常运行了,不过这至少说明,程序没有进入异常处理部分,我们的方向是对的。

接下来就要考虑如何下断点了,我想在获取文本框内容的函数上下断点,可却没找到是哪个函数,然后就想既然MessageBox没用,那应该就是直接产生的一个普通窗口,然后就想到了ShowWindow,下断点,重新运行程序,发现,程序一上来就被暂停了,应该是主窗体的显示,无视就好,然后接下来输入完成后点check it发现程序果然被我们ShowWindow上的断点暂停住了,跟踪下去很轻松就找到了关键代码:

int __usercall sub_458800<eax>(int a1<ebx>)
{
  int v1; // ecx@1
  int v2; // ecx@2
  unsigned int v4; // [sp-10h] [bp-14h]@1
  int v5; // [sp-4h] [bp-8h]@4
  int v6; // [sp+0h] [bp-4h]@1
  int v7; // [sp+4h] [bp+0h]@1

  v4 = __readfsdword(0);
  __writefsdword(0, (unsigned int)&v4);
  sub_458760(v4, loc_458874, &v7, a1, 0);
  sub_4255C0(v1, &v6);
  if ( sub_407774() == dword_45B844 )
  {
    sub_44496C();
    sub_4255F0(v2, "CRACKED");
  }
  else
  {
    sub_44496C();
  }
  __writefsdword(0, v4);
  return sub_4037E8(loc_45887B, v5);
}

跟踪调试一下可以发现,sub_4255C0是用来将输入框的内容读入,v7中存的是指向读入的name字符串的指针,v6中存的是指向读入的serial字符串的指针,sub_407774是将serial字符串转为整数,转换失败是抛出异常,也就会进入我们之前掉入的那个坑里。

sub_458760中调用sub_4255C0读入了name字符串并做了一定的处理,观察会感觉sub_458760还是比较麻烦的,于是乎,再按照之前的老方法,在if ( sub_407774() == dword_45B844 )上加断点,直接看dword_45B844的值(填入的name为HelloWorld,题目中有说),然后输入到serial中即可。

成功得到正确的提示框,此题得以解决,正如题目中所说,这题的关键在于找到正确的cmp,只要找到了,基本做就不成问题了。

ubccrackme1.rar

这个实验涉及到的信息有点多,解释了一个线程从产生到运行的全过程,不过好在需要填写的代码不多,注释也还算清楚,所以实现起来也不算困难。

然而,这次还是没能一番风顺,程序补充完成后,运行的时候却出现了莫名其妙的错误,无奈之下甚至于去对答案,但是却除了发现少了关闭中断的处理外,并没有发现什么问题。

最后,在不断的尝试之后,终于发现,在将lab2中修改的代码default_init_memmap、default_alloc_pages、default_free_pages替换成标准的答案,代码就成功运行起来了。想来错误大概是出在了页的分配算法的实现,但着实没想清楚为什么会出现问题,或许等啥时候有时间了可以仔细研究下问题出在什么地方。

老规矩,还是附上lab4的实验报告和修改的代码。

lab4.zip

这两天弄了个国外服务器的SSH账号,本意当然是拿来翻墙的了,但之前从来没有用过SSH做代理,于是乎便在网上搜了下解决方案。

SSH做代理,就是架设SOCKS代理,有很多软件都是可以实现,但是由于洁癖不想装些乱七八糟的软件,于是最后找到个直接使用的命令方式:

ssh -nNTf -D 7070 username@server_address -p port

通过这条命令,将ssh绑定到7070端口(端口是自己选的),这样,在填写代理的时候,填localhost:7070或者127.0.0.1:7070即可。

这里的-p参数是由于我这个ssh账号不是在默认端口22,所以才需要指定的。

原来一直都是用的http(s)或者VPN来代理,第一次发现一个ssh账号就能方便的架设出SOCKS代理,真好~~~

今天又来试着做NCTU,于是乎挑了这道题,题目描述如下:

BCG's full name is BeGiNnEr'S CrAcKiNg Group....

Do you have the ability to crack this program?.... 

press "start" then download it!

Hint1: The checkin key is the "content"...

Hint2: key length is the asked length, and all character is same as the first one.

这道题做完之后确实的说,也是挺简单的,但由于自己还是太弱,做的时候就没有这么顺利了。

首先打开程序,会发现上面有着各种说明,似乎这题是BCG申请加入时的题,程序上写的那些限制我也没太弄明白,不过这里既然只是解题,就不管了。

IDA反汇编程序会发现,程序只有一个很短的start,感觉应该是自解压的程序,于是乎先打开程序,再将OD附加到该程序上便开始调试。然而,尝试了半天之后,还是没有什么收获,一直没能看到什么关键代码。

实在无奈之下,用PEiD查了下,发现是有个aspack壳,于是在网上找了个脱壳软件将壳脱掉,再用IDA查看,顿时感觉就是柳暗花明。

程序很明确,一眼就看到关键程序,用IDA反编译得:

int __stdcall DialogFunc(HWND hDlg, int a2, int a3, int a4)
{
  int result; // eax@4
  int v5; // eax@14

  if ( a2 == 273 )
  {
    if ( a3 == 1 )
      return MessageBoxA(0, "中, "OfficialCrackme", 0x1000u);
    if ( a3 == 2 )
      return EndDialog(hDlg, 0);
    if ( a3 != 3 )
      return 0;
  }
  else
  {
    if ( a2 != 272 )
    {
      if ( a2 != 16 )
        return 0;
      return EndDialog(hDlg, 0);
    }
    SetDlgItemTextA(hDlg, 10, String);
  }
  hFile = CreateFileA("[BCG].Key", 0xC0000000u, 3u, 0, 3u, (DWORD)"€", 0);
  if ( hFile == (HANDLE)-1 )
    goto LABEL_22;
  if ( !ReadFile(hFile, (LPVOID)&String2, 0xAu, &NumberOfBytesRead, 0) )
    goto LABEL_22;
  if ( !ReadFile(hFile, String1, 0xAu, &NumberOfBytesRead, 0) )
    goto LABEL_22;
  CloseHandle(hFile);
  v5 = 0;
  do
  {
    *(&String2 + v5) ^= 0x58u;
    ++v5;
  }
  while ( *(&String2 + v5) );
  if ( !lstrcmpA(String1, &String2) )
    result = MessageBoxA(0, "注册验证成功,恭喜, "OfficialCrackme", 0x1000u);
  else
LABEL_22:
    result = MessageBoxA(0, "革, "OfficialCrackme", 0x1000u);
  return result;
}

程序流程很清楚,是从[BCG].Key中读出两个长度为0xA的字符串,然后其中一个字符串与0x58异或之后的结果正好要为另一个字符串,那我们可以取'0' xor 'h',于是在文件中写入hhhhhhhhhh0000000000,再尝试运行程序。可是,结果却与我们的预期大相径庭,还是告诉我们“革命尚未成功”。

下断点再调试程序发现,String2和String1在内存空间上正好相距10,也就是说String2的后面没有字符串结尾标志 ,这样导致异或的时候String2和String1都被异或的,这还不止,在lstrcmpA的时候,更是认为String1是String2的字串了,那么看来,这两个字符串肯定不会相等了。

为了解决这个问题,第一想法就是在文件中写入 ,然String2并不能真正读入10个字符或者说让第10个字符为 ,但这样且不说是否可行,但至少是相当麻烦的,那再仔细想想会发现,如果我们输入的String2中有字符X(即0x58),那么异或的时候就会产生 ,这样在lstrcmpA的时候,两个字符串就可以相等了。

那么,我们直接在文件中写入20个X,运行程序便成功弹出正确的提示框,得以解决。

PS:chekckin的时候需要注意,是提交10个X即可,不要提交20个,这问题坑了我半天……

bcgcrackme.rar

前两天好像GitHub抽风的厉害,gfw不知道又在搞什么,反正导致想push东西push不上去,而纷印的代码又是放在GitHub上的,于是很是郁闷。

原先都是push到GitHub上再服务器上pull一下就好,现在服务器虽然还能连上GitHub,但是我本机连不上,导致代码更新不了。无奈之下,只得再祭出老办法,直接push到服务器。

首先git remote add,十分顺利的就建立了和服务器reposite的联系,但是push的时候,却出现了问题:

remote: fatal: Unable to create temporary file '/var/www/***/.git/./objects/pack/tmp_pack_XXXXXX': Permission denied
error: unpack failed: index-pack abnormal exit
To jayvic@***:***
 ! [remote rejected] master -> master (unpacker error)

发现很明显是权限问题,毫无疑问,最简单的办法莫过于直接在把整个repository改成777,但是由于这个服务器并不是用来玩的,所以不太想这样做。

于是按照他要写的文件位置来进行权限修改,几次尝试之后,最终得到命令如下:

sudo chmod -R a+w objects/ logs/ refs/

修改完这三个文件夹的权限后,push的时候就没有权限问题了,但是需要注意的是,直接push还是会失败,不能push到当前的分支,要首先checkout到一个free的分支,push完后再checkout回来。

前两天,在写程序的时候,有个函数需要传一个回调函数,但是,我需要在这个回调函数的内部使用外面的一些变量,同时需要获取该函数的最终计算结果。

首先,获取计算结果还是比较容易的,查看那个函数的实现发现,其返回的结果就是传入的回调函数的返回结果,那么我们就可以直接在回调函数中返回即可。

但是,接下来想要使用外面的变量的时候就没有这么一帆风顺了。首先直接我以为是像swift的闭包一样,会自动捕获外部变量当时值,可是直接使用却发现并不是这样,完全没有捕获任何外部变量。

接下来我想通过给匿名函数添加参数的形式将外部变量传递进去,但是调用回调函数的时候,传递的参数并不能由我设置,于是乎我尝试将外部变量当做参数的默认值,可是语法还是不允许这样做。

最后,终于在php.net中的一个示例中找到了答案:

$callback =
    function ($quantity, $product) use ($tax, &$total)
    {
        $pricePerItem = constant(__CLASS__ . "::PRICE_" .
            strtoupper($product));
        $total += ($pricePerItem * $quantity) * ($tax + 1.0);
    };

即在匿名函数定义的时候,后面使用use,来将上一级作用范围中的变量导入进匿名函数中,这样不仅可以使得里面得到外面的值,也能让里面对变量进行的修改反映到外面。

仔细想想,其实php这样的方式也挺好的,这样每一个传递的变量都是由我们自己在use中明确的,那么也就不至于出现像swift中自动捕获变量时极易出现的相互引用而导致的内存泄露。

助教给我们找了这么个网站,据说题目很水……

第一题Simple "crackMe"…short and easy实在是太水,于是直接上第二题了,传送门在此

题目描述:

'lovetc' forgot his own password,

maybe you can reverse it and find the encryption algorithm....

press "start" then download it.

Hint: The checkin key is his password....

这是个图形化程序,首先IDA走起,然后找到消息分发函数:

int __stdcall DialogFunc(HWND hDlg, int a2, int a3, int a4)
{
  HICON v4; // eax@2
  UINT v5; // eax@9
  unsigned int v7; // [sp-4h] [bp-8h]@11

  switch ( a2 )
  {
    case 272:
      v4 = LoadIconA(hInstance, (LPCSTR)0x1F4);
      SendMessageA(hDlg, 0x80u, 0, (LPARAM)v4);
      break;
    case 16:
      EndDialog(hDlg, 0);
      break;
    case 273:
      switch ( a3 )
      {
        case 300:
          MessageBoxA(
            hDlg,
            "       +=================================+               n       |   Keygen-me N Created on 27/08/2003  |n       +=================================+               n                                     nTry to keygen this program, and send your solution tonwww.crackmes.de, for more informations contact me at n#eminence channel on eFnet.n                                                               Enjoy Crypto.....n                                                               n                             (C)2003 BytePtr [e!]  n",
            "AbOut",
            0);
          break;
        case 900:
          v5 = GetDlgItemTextA(hDlg, 100, String1, 300);
          if ( !v5 )
            return MessageBoxA(0, "Your name please !!!", "oooH input Error", 0);
          v7 = v5;
          if ( !GetDlgItemTextA(hDlg, 200, String, 300) )
            return MessageBoxA(0, "Where is Da serial DuDe ?", "oooH input Error", 0);
          lstrcatA(String1, "BytePtr [e!]");
          sub_401000((int)String1, v7, (int)&unk_4056A8);
          sub_401B79();
          if ( lstrcmpA(String, byte_4079D0) )
          {
            MessageBoxA(0, "hmmm not like this  DuDe Try again....", "Fatal Error", 0);
            return 0;
          }
          MessageBoxA(0, "Good serial", "Good Work", 0);
          break;
        case 400:
          EndDialog(hDlg, 0);
          break;
      }
      break;
  }
  return 0;
}

很显然,关键应该就在sub_401000了,可是,当我们打开这个函数一看的时候,简直是长的不忍直视,虽说是好像没再调用些别的神马函数,耐着性子一步步来肯定可以搞出答案,但肯定不是个明智的选择。

然后再仔细观察程序会发现,sub_401000只对输入框中输入的Name做了处理,最后是直接拿处理得到的字符串和byte_4079D0,也就是输入的Serial做对比,那么显然我们可以debug让这个程序跑起来,输入正确的Name(lovetc),让程序停在lstrcmpA前,然后我们便可以很轻松的得到Serial。

于是,此题得以解决~~~

keygenme.rar

做完lab3之后还是得说,lab3也挺水的了,基本上也就是按照注释来一步步的写,需要用的函数都在注释中给了,基本没有自己需要找得函数或者常量定义。

lab3主要是涉及到了完整的虚实地址映射,完成虚拟内存管理器,虽然有和硬盘交换的部分,但该部分的代码已经给出,不用自己完成。

lab3虽说不难,但自己脑抽了一下,于是就被自己给坑了。在完成_fifo_swap_out_victim函数的时候,其中需要将链表指针转化为其对应的Page的指针,当时就记得在前面的lab中见到是有个宏可以把对一个数据结构中的一个成员的指针转换成对该数据结构的指针,但是不记得在哪里了,于是乎就死命的找啊找。最后好不容易灵光一闪,想起了负责lab2中page和list entry转化的函数,才好不容易通过其找到想找的东西。

然后几经波折的使用好后,竟发现,这不就是直接用le2page就好了,只不过把第二个参数换成pra_page_link即可。

至于chanllege,看了下感觉不难,不过暂时太忙,就先放着了,有时间了再说吧……

最后,还是附上lab3的实验报告和修改的代码。


这次的challenge确实很水不错,不过没想到竟然有一个大坑,它ucore的程序是有点问题的,在进行页换入的时候,ucore并没有将换入的页的pra_vaddr设置为对应的线性地址。而如果不做这个设置的话,之前的FIFO算法本来是会有问题的,但是由于它FIFO中实现的check里面,是5个虚拟页在4个物理页上进行替换,故而无论怎么check也不会遇到程序崩溃的情况。

最后鉴于改ucore本身的代码不太好,就在实现的_ec_map_swappable中对page的pra_vaddr进行了一下设置,以保证程序能够正确运行不崩溃。

这个问题实在是太坑了,本来程序老早就写对了,却没想到最后被这个坑给搞的把整个页置换debug了一遍,花了好长时间,本来就是因为时间不够才没做之前challenge,这次以为很快能搞定才做的,没想到却成了这番结果……

lab3.zip

lab3(含challenge).zip

Crack类的题目也没神马题目描述了,传送门在此

今天发现IDA原来还有反编译功能,顿时觉得自己之前似乎SB了。然后试用了下,感觉效果还不错。

这题整个程序很简单,就是读入name和pass,然后通过下面的子程序(IDA生成的)来judge:

bool __cdecl sub_401000(const char *username, const char *password)
{
  const char *v2; // esi@1
  unsigned int len_user_plus; // kr04_4@1
  int len_user; // ebp@1
  bool result; // al@2
  int i; // eax@3
  int *str_con; // edi@4
  const char v8; // bl@5
  const char *v9; // edi@6
  int j; // ecx@6
  bool v11; // zf@6

  v2 = username;
  len_user_plus = strlen(username) + 1;
  len_user = len_user_plus - 1;
  if ( len_user_plus - 1 == strlen(password) )
  {
    i = 0;
    if ( len_user > 0 )
    {
      str_con = &global_str;
      do
      {
        v8 = *(_BYTE *)str_con ^ password[i];
        ++str_con;
        password[i++] = v8;
      }
      while ( i < len_user );
    }
    v9 = password;
    j = len_user_plus - 1;
    v11 = 1;
    do
    {
      if ( !j )
        break;
      v11 = *v2++ == *v9++;
      --j;
    }
    while ( v11 );
    result = v11;
  }
  else
  {
    result = 0;
  }
  return result;
}

程序很明确,就是将username与一个全局的常量进行xor,然后再将结果与password对比,相等则正确。

00408030  01 00 00 00 02 00 00 00  03 00 00 00 04 00 00 00  ................
00408040  01 00 00 00 05 00 00 00  01 00 00 00 05 00 00 00  ................
00408050  01 00 00 00 03 00 00 00  06 00 00 00 05 00 00 00  ................
00408060  04 00 00 00 08 00 00 00  05 00 00 00 03 00 00 00  ................
00408070  01 00 00 00 02 00 00 00  03 00 00 00 04 00 00 00  ................
00408080  05 00 00 00 03 00 00 00  05 00 00 00 07 00 00 00  ................
00408090  02 00 00 00 03 00 00 00  02 00 00 00 04 00 00 00  ................
004080A0  08 00 00 00 02 00 00 00  05 00 00 00 06 00 00 00  ................
004080B0  04 00 00 00 74 72 79 20  61 67 61 69 6E 21 00 00  ....try again!..

注意这个字符串是每个dword只去某位的一个byte,所以相当于是01、02、03、04、01、05、01、05……

我们可以将username和password分别输入为0、1来测试,会发现程序提示我们正确。

于是,按照要求,使用用户名当username,算出password,计算MD5就是答案了。

CrackMe1.rar

前两天在写程序的时候,需要在内存中维护一个任务队列,但是Laravel的Cache是没有锁的,所以在往任务队列添加任务,在并发的时候可能会出问题,那么就需要手动来实现加锁。

思前想后,又看了半天Laravel的文档,发现Cache有一个add方法,当某一数据不在缓存中是才将其保存,如果该项实际上 已经添加 到缓存中,那么 add 方法将返回 true 。否则,此方法将返回 false。

通过add,我们就有了如下的加锁办法:

/**
 * 从缓存中获取全局任务列表
 *
 * @return array 任务列表
 */
public static function getTaskListFromCache() {
    /* 手动加锁以防止同时操作任务列表冲突 */
    while (!Cache::add('task_list_lock', true, 1)) {
    }
    Log::notice('task list 加锁');

    $task_list = Cache::get('task_list');
    /* 若任务列表未初始化则初始化之 */
    if (is_null($task_list)) {
        return [];
    }
    return $task_list;
}

这样,所有的并发会在Cache::add处串行起来,解锁时forget这个锁即可。

这样看起来似乎挺美好的,但是真正跑起来会发现总是会被锁卡住,仔细一想便会发现,原因其实很简单,如果程序正常运行了整个加解锁流程肯定没有问题,可是,如果程序在加锁后、解锁前这段时间内崩溃了,锁就没有被清除,导致后续的程序一直等不到锁。

那么有原因了,对症下药,我们在laravel的异常处理中,针对所有异常退出,都清除本程序加的锁即可:

function unlock_task_list() {
    global $task_list_locked;
    if (!empty($task_list_locked)) {
        Cache::forget('task_list_lock');
        $task_list_locked = false;
        Log::notice("task list 解锁(异常处理)");
    } else {
        Log::notice("task list 未加锁(异常处理)");
    }
}

App::error(function(Exception $exception, $code)
{       {
    /* 异常退出时清除全局任务列表的锁 */
    unlock_task_list();

    Log::error($exception);
});

相应地,需要修改加锁部分添加全局变量表示是否被本进程加锁,以防止异常退出时清除了其它进程加的锁:

/**
 * 从缓存中获取全局任务列表
 *
 * @return array 任务列表
 */
public static function getTaskListFromCache() {
    /* 手动加锁以防止同时操作任务列表冲突 */
    while (!Cache::add('task_list_lock', true, 1)) {
    }
    global $task_list_locked;
    $task_list_locked = true;
    Log::notice('task list 加锁');

    $task_list = Cache::get('task_list');
    /* 若任务列表未初始化则初始化之 */
    if (is_null($task_list)) {
        return [];
    }
    return $task_list;
}

需要注意的,如果有其它异常捕捉返回了Response,会导致程序不进入上面的基础异常捕捉,于是需要在该异常处理中也调用unlock_task_list。