Y1rannn 发表于 2022-1-27 22:31

SUCTF2019_Akira-Homework

这道题真的是很难的,....,做完之后上网看了看别的师傅的题解,虽然整体是这个流程,但是细节处理一个人一个方法,有的方法就会简便很多.

不过我这种各种瞎猜的果然还是比较简单(着实是沾了非实战是个做题的光)

做题和实战最大的区别就是做题往往整个程序控制流都和题有关,不会有大量的无关的东西.

看上去又是一道双线程"双手互搏"题.

~~运行一下发现全程不需要输入~~,后来发现是第一次我的terminal抽风.

```sh
PS E:\ctf\work\SUCTF2019 Akira Homework> .\WinRev.exe
[+]======================[+]
[+] Akira's Homework 2nd [+]
[+]======================[+]
[=] My passwords is:
```



调用很多WinAPI,还是动调一下. 一动调直接谈了个对话框"Stop debugging program"

![屏幕截图 2022-01-27 192049.png](https://tianyu.xin/usr/uploads/2022/01/3058362573.png)


找到对应位置, 扬了就行

![屏幕截图 2022-01-27 193801.png](https://tianyu.xin/usr/uploads/2022/01/66862689.png)

对应位置后面是这样的,把四个数组分别异或一个table中的值,我们直接打个断点看看

![屏幕截图 2022-01-27 194210.png](https://tianyu.xin/usr/uploads/2022/01/3813817876.png)


荧光笔打上的地方就是四个字符串
![屏幕截图 2022-01-27 194310.png](https://tianyu.xin/usr/uploads/2022/01/3214645829.png)


(https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress)

可以读到就是从ntdll提取这三个模块的地址, 我们继续检查这个函数8850的交叉引用,看看这个地址存在哪里要用来干嘛
![屏幕截图 2022-01-27 194646.png](https://tianyu.xin/usr/uploads/2022/01/985278495.png)


放到了一个堆内存里.这个函数是main的第一个函数.没太能看的出来它想干啥, 但是这三个函数分别是获取进程\线程信息和Apc队列. 没见过这东西,查一下.

大概就是在多线程运行条件下,当某个程序执行到特殊函数造成的等待状态时,系统中断, 本线程优先去APC队列中调用一个Callback.

但是现在没有APC到底注入了什么函数的信息, 翻了翻也没找到, 我们回到main, 去看看第二个线程执行了什么

![屏幕截图 2022-01-27 195436.png](https://tianyu.xin/usr/uploads/2022/01/3632254144.png)


第二个线程从StartAddress开始,注意到main的结尾调用了一个SleepEx,这个函数是可以触发APC的

![屏幕截图 2022-01-27 195527.png](https://tianyu.xin/usr/uploads/2022/01/2628442398.png)


StartAddress里也同样有一个Sleep, 我们看看都干了啥

```c
__int64 sub_7FF7B0ED8B20()
{
size_t v1; // rax
int i; //
int j; //
BOOL v4; //
int v5; // BYREF
HANDLE hSnapshot; //
__int64 v7; // BYREF
PROCESSENTRY32W pe; // BYREF
char v9; // BYREF
wchar_t String1; // BYREF

sub_7FF7B0ED8300(&v7, 1i64, 1048577i64);
memset(&pe, 0, sizeof(pe));
pe.dwSize = 568;
hSnapshot = CreateToolhelp32Snapshot(2u, 0);
if ( hSnapshot == (HANDLE)-1i64 )
    return 0i64;
v4 = Process32FirstW(hSnapshot, &pe);
if ( !v4 )
    return 0i64;
while ( v4 )
{
    memset(v9, 0, 0x21ui64);
    memset(String1, 0, 0x42ui64);
    v1 = wcslen(pe.szExeFile);
    sub_7FF7B0ED7DD0(pe.szExeFile, v1, v9);
    for ( i = 0; i < 16; ++i )
      sprintf_s((char *const)&String1, 3ui64, L"%02x", (unsigned __int8)v9);
    for ( j = 0; (unsigned __int64)j < 0x31; ++j )
    {
      if ( !wcscmp(String1, &a438078d884693c) )
      exit(-1);
    }
    v4 = Process32NextW(hSnapshot, &pe);
}
v5 = 106;
if ( !byte_7FF7B0EE6158 )
{
    sub_7FF7B0ED6C10(qword_7FF7B0EE6178, v7, &v5);
    byte_7FF7B0EE6158 = 1;
}
return 1i64;
}
```

8300这个函数不明所以,但是后面6C10用到了它的返回值.我们先从可读的地方探一探

这个(https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot)是为了获取一个进程快照,第一个参数代表快照的系统部分,为2代表要包括系统中的所有进程.第二个参数为0代表当前进程

之后Process32First/Next就是遍历这个Snapshot中打下的系统的所有进程. szExeFile是这个遍历到的进程的名称.其实我目前还没有理解这到底是是打下了所有进程还是我当前进程, 我猜测是因为不知道有没有其他进程创建当前进程,所以dump下来系统其他进程的信息, 和当前进程内存信息

![屏幕截图 2022-01-27 200317.png](https://tianyu.xin/usr/uploads/2022/01/1516743077.png)


7DD0这个函数可以通过magic number判断是一个md5函数, 没有什么好说的, 最后结果存到a3指向地址里

结合一起猜测这应该也是个反调, 要求不能有进程的md5和固定值符合, 虽然是瞎猜的,但是调试时确实会进到这里, 扬掉

后半段是一些和线程锁相关的东西,感觉是切换线程前的准备

继续看StartAddress的第二个函数

![屏幕截图 2022-01-27 201143.png](https://tianyu.xin/usr/uploads/2022/01/1671345340.png)


晕, 这反调多的眼花缭乱的, 这个a1通过交叉引用也找到了位置:正是我们一开始解密的ntdll里的三个函数, 注意到有一个qword_7FF7B0EE6188 = (__int64)v6; 这里用到了这个指针qword

```c
fastcall func2(void (__fastcall **callback)(HANDLE, __int64, _QWORD, _QWORD))
```

这里的函数声明也可以对应的改成callback了,方便看

这整个函数都不好,看了也没用,直接干掉它的调用了(我在想是不是整个子进程都可以干掉). 再测一下发现main能正常进去了, 我们调一下

![屏幕截图 2022-01-27 201826.png](https://tianyu.xin/usr/uploads/2022/01/1300396941.png)


这个exit-1看着就不像好事,猜测9710这个函数要返回true, table 是 0x49, 我们直接看看它会puts啥吧.

擦, 这个进程进去之后会等一会-1退出, 正好我拖IDA的窗口,把IDA干崩了,

```c
unsigned char ida_chars[] =
{
0x4A, 0x3A, 0x4C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C,
0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C,
0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x4A, 0x3A, 0x4C, 0x1B, 0x4A,
0x3A, 0x4C, 0x31, 0x50, 0x7A, 0x78, 0x63, 0x70, 0x36, 0x62,
0x31, 0x59, 0x7E, 0x7C, 0x74, 0x66, 0x7E, 0x63, 0x7A, 0x31,
0x23, 0x7F, 0x75, 0x31, 0x4A, 0x3A, 0x4C, 0x1B, 0x4A, 0x3A,
0x4C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C,
0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C,
0x2C, 0x2C, 0x2C, 0x4A, 0x3A, 0x4C, 0x1B, 0x4A, 0x2C, 0x4C,
0x31, 0x5C, 0x68, 0x31, 0x61, 0x70, 0x62, 0x62, 0x66, 0x7E,
0x63, 0x75, 0x62, 0x31, 0x78, 0x62, 0x2B, 0x11
};
#include <cstdio>
int main() {
    for(int i = 0; i < 108; i ++ ) {
      putchar(ida_chars ^ 0x11);
    }
}

---
/out:test1.exe
test1.obj
[+]======================[+]
[+] Akira's Homework 2nd [+]
[+]======================[+]
[=] My passwords is:
```

9710干了这件事, 然后应该是读入一个18s, 并扔到9200里判断要不要返回1

如果判断错误会打印`WHO ARE YOU????`然后退出, 先把9200的结果逆了

```cpp
memset(v6, 0, 0x13ui64);
memcpy(v6, &target1, 0x12ui64);
for ( i = 0; i < 0x13; ++i )
    v6 ^= xor_table;
memset(v7, 0, 0x13ui64);
for ( j = 0; j < 0x12; ++j )
    v7 = j ^ a1;
v5 = 1;
v5 = 5;
v5 = 4;
v5 = 2;
v5 = 3;
v5 = 0;
for ( k = 0; k < 0x12; ++k )
{
    if ( v6 != v7] )
      return 0;
}
```

核心直接摆这儿了,就是一个Xor+轮换,很好逆

```cpp
#include <cstdint>
#include <cstdio>
uint8_t target1[] =
{
0x2F, 0x1F, 0x20, 0x2E, 0x34, 0x04, 0x37, 0x2D, 0x10, 0x39,
0x7C, 0x22, 0x7B, 0x75, 0x0A, 0x38, 0x39, 0x21
};
uint8_t ans;
uint8_t shift = {1,5,4,2,3,0};
int main() {
    for(int i = 0; i < 18; i ++ ) target1 ^= 0x45;
    for(int i = 0; i < 18; i ++ ) {
      ans] = target1;
    }
    for(int i = 0; i < 18; i ++ ) {
      ans ^= i;
    }
    printf("%s", ans);
}
// Akira_aut0_ch3ss_!
```

Akira自走棋是吧

下面还有个6C10用了我们的输入, 走进去看看, 又用了个虚函数, 懒得找表,动调看看在哪

```c
char __fastcall sub_7FF6B9348910(__int64 a1, const char *a2)
{
int i; //
int v4; //

v4 = strlen(a2);
for ( i = 0; i < dword_7FF6B9351194; ++i )
{
    if ( !(i % 3) )
      byte_7FF6B93511A0 ^= a2;
}
SetEvent(Handles);
return 1;
}
```

把一段长度4C00的内存和我们的输入异或了, 而且这段内存后面还有几次调用,暂时不知道在哪,我们给它改个名叫secret

```c
[=] My passwords is:
Akira_aut0_ch3ss_!
Have no sign!
Time out!
```

接下来要在有限的时间内sign,不然就exit

![屏幕截图 2022-01-27 203859.png](https://tianyu.xin/usr/uploads/2022/01/841570420.png)


我想找找在哪,首先关注到了main函数最后调用的一个函数,的最末尾,但是这里是另一个failed

`Failed to check sign!`
![屏幕截图 2022-01-27 204047.png](https://tianyu.xin/usr/uploads/2022/01/2635815987.png)


我们回到这个函数的开头,关注另外两个异或解密,

Source的结果是:signature

Buffer是Have no sign!

就是要创建或打开一个$FileName:signature的文件,如果创建或打开不了就no sign, 我猜后面的参数是不创建只打开, 看看(https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew)

> | **OPEN_EXISTING**3 | 仅当文件或设备存在时才打开它。如果指定的文件或设备不存在,则函数失败并且最后一个错误代码设置为 **ERROR_FILE_NOT_FOUND** (2) |
> | ------------------ | ------------------------------------------------------------ |
> |                  |                                                            |

正是如此, 所以我们要创建一个这个文件然后过验证

诶人傻了怎么不能带冒号啊...

查了一下这个冒号是CreateFIleW里指定的文件名:文件流, 也就是我们实际上是要创建一个叫signature的流写到源文件里

这里要求流的md5和给定的md5值相同, 然后有一个类似之前的结构调用了第二个虚函数

先给这个MD5解密
![屏幕截图 2022-01-27 210808.png](https://tianyu.xin/usr/uploads/2022/01/1541477183.png)


![屏幕截图 2022-01-27 210915.png](https://tianyu.xin/usr/uploads/2022/01/1328531834.png)


我尝试直接向WinRev.exe:signature写\用Linux写,都失败了

![屏幕截图 2022-01-27 211343.png](https://tianyu.xin/usr/uploads/2022/01/172252857.png)


先不管了, 先找到了这三个虚函数, 8910是和自走棋异或, 8A80没有参数, 是把它的高低位交换

89e0则是把它和一个参数^0x33再异或,结合这部分调用的虚函数传进去的参数为0, 而且调用在8910的后面,我优先猜测它应该是高低位交换,现在不管文件流,我们要找找89e0在哪里调用的

![屏幕截图 2022-01-27 211849.png](https://tianyu.xin/usr/uploads/2022/01/144669027.png)


其实就是找到这样的结构, 诶诶诶, 其实要是点进去6C10, 会发现这里也有很多和mutex相关的东西,还记得好久以前我说过一个函数和信号量相关,应该没用么?我猜当时是虚函数调用被我忽略了.我们回去找找8B20, 没错,就是这儿,而且当时调用的也是6C10这个函数

![屏幕截图 2022-01-27 212250.png](https://tianyu.xin/usr/uploads/2022/01/1122045321.png)


其实三次调用的都是这个,不过第二个参数影响了调用虚函数具体是哪个,结合这个函数出现的位置,我们可以猜测它就是第一个异或固定值的虚函数, 传进去的参数是106, 这样我们应该就把那段长度4C00的内存解密了, 虽然那么长应该不是Flag还没完...

```cpp
uint s[] = {
    略
}
#include <cstdio>
#include <cstdint>
int main() {
int len = 19456;
char s1[] = "Akira_aut0_ch3ss_!";
for(int i = 0; i < len; i ++ ) if(i % 3 == 1) {
    s ^= 106 ^ 0x33;
}
for(int i = 0; i < len; i ++ ) if(i % 3 == 0) {
    s ^= s1;
}
for(int i = 0; i < len; i ++ ) if(i % 3 == 2) {
    s = (s >> 4) | ((s << 4)&0xFF);
}
FILE *fp = fopen("tt.bin", "wb");
fwrite(s, 1, len, fp);
fclose(fp);
}
```

这东西打开之后清晰可见的一个MZ头,不是dll就是一PE, 直接扔IDA里看好了

![屏幕截图 2022-01-27 213423.png](https://tianyu.xin/usr/uploads/2022/01/465096575.png)


这下怎么都该完了吧..看看这个解一下

跟到2800里一看,跟了好几层, 终于有了一个变换了
![屏幕截图 2022-01-27 213531.png](https://tianyu.xin/usr/uploads/2022/01/2503061988.png)


内存是这样的, 开头0x63,0x7C,0x77,0x7B..这..一眼AES, 我现在只期望它是个朴素的AES-128,不要再玩花了, 我们输入要和AES的结果一样, AES的明文由原程序给出,看来原来的程序最后应该是装载了这个DLL, 我猜是最后一个sleep之后, 前面那个A什么队列里塞了装载.. 当然,这是本着做题的角度,我觉得那个不可能没用.. 现在回头看看合格到底怎么调用的..

而且我觉得这里真的好怪啊, 传过来的是AES明文, 输入的是加密后的结果, 那加密后的结果也够呛能打印啊.一般应该是我输入加密之后和传过来的密文对比吧..不管怎么样先弄到共享内存的值再说, 因为这后面有个0x52, 0x9, 没准下标+256这不是加密是解密呢((((

[共享内存的MSDN文档](https://docs.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory)

直接搜ShareMemory字符串

![屏幕截图 2022-01-27 221207.png](https://tianyu.xin/usr/uploads/2022/01/2762718398.png)


找到Target,
![屏幕截图 2022-01-27 221648.png](https://tianyu.xin/usr/uploads/2022/01/1885744697.png)


呜..这题真麻烦啊...我做了3个小时..

Hmily 发表于 2022-2-10 17:55

@Y1rannn 你帖子用的图床ssl证书有问题,直接把几个帖子的图片上传到论坛本地吧,也能防止丢失不显示。

Y1rannn 发表于 2022-2-18 12:33

Hmily 发表于 2022-2-10 17:55
@Y1rannn 你帖子用的图床ssl证书有问题,直接把几个帖子的图片上传到论坛本地吧,也能防止丢失不显示。

图床是我自己的博客,前一段证书过期但是我一直在旅游, 当天就发现了但是一直没解决,马上签好。
上传论坛好像有一点点麻烦(比 Typecho

Hmily 发表于 2022-2-18 14:57

Y1rannn 发表于 2022-2-18 12:33
图床是我自己的博客,前一段证书过期但是我一直在旅游, 当天就发现了但是一直没解决,马上签好。
上传 ...

其实论坛上传还行,不算麻烦,传完了,用discuz的方式贴图,你别用markdown的语法,其实还行。

hua111 发表于 2022-2-19 10:57

页: [1]
查看完整版本: SUCTF2019_Akira-Homework