level5
逆向一个虚拟机,编写适应于其的二进制文件,实现tea算法;
上图左侧为main函数,逻辑就是读取名称 “binary” 的内容,然后将其赋给code,之后将code扔进vm函数充当指令集;
每条指令分三个数值,一个指令数,两个操作数,分别给了instru和One,Two变量;
根据输入不同的instru变量来调用不同的函数,这些函数就是指令执行的操作了,翻译如下:
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 67 68 69 70
| code[8] 是计数器 -> i
code[9] 是flag 控制数
code[7] 是栈针
code[6] 用于实现加法
--- 0 code[one] = two ++i
1 code[code[7] + 10] = one ++code[7] ++i
2 code[code[7] + 10] = code[one] ++code[7] ++i
3 code[code[7] + 10] = code[8] + 1 ++code[7] code[8] = one
4 code[one] = code[two] ++i
5 code[8] = one
6 code[one + 10 + code[6]] += code[two] ++i
7 code[one] = code[code[6] + 10 + two] ++i
8 code[one] < < = two ++i
9 code[one] > > = two ++i
a code[one] += code[two + 10 + code[6]] ++i
b code[one] ^= code[two] ++i
c if( !code[9] ) -> code[8] = one ; else ++i
d end
e if(two < = code[one + 10 + code[6]])
{
if(two = code[one + 10 + code[6]])
code[9] = 1
else
code[9] = 2
}
else -> code[9] = 0
++i
f --code[7] code[one] = code[code[7] + 10] ++i
10 --code[7] code[8] = code[code[7] + 10] ++i
11 code[code[6] + 10 + one] += two ++i
12 code[code[6] + 10 + one] += code[code[6] + 10 + two] ++i
13 code[one] = cin ++i
14 cout code[one] ++i ---
所以1,2,3都表示入栈,3表示call因为改变了计数器;f,10表示出栈,10表示return;
|
由此对照机械码手撸汇编:
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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
0 6 32 mov reg6, 50
1 11223344 push 11223344h
1 22334455 push 22334455h
1 33445566 push 33445566h
1 44556677 push 44556677h
---
11 0 0 add reg60, 0
tag1:
13 0 mov reg0, cin
2 0 push [reg0]
11 0 1 add reg60, 1
e 0 4 cmp reg60, 4
c 43 jnz tag1
---
3 4E call cry
14 0 mov cout, reg0
14 1 mov cout, reg1
14 2 mov cout, reg2
14 3 mov cout, reg3
d retn
---
cry:
1 9E3779B9 push delta (push后code[7] = 10)
4 0 E mov reg0, [esp + 6]
4 1 F mov reg1, [esp + 5]
4 2 10 mov reg2, [esp + 4]
4 3 11 mov reg3, [esp + 3]
---
0 3C 0 mov reg60, 0
11 0 0 add reg60, 0
tag2:
//sum
6 1 13 add reg61, [esp + 2]
//v0
4 3E 1 mov reg62, reg1
8 3E 4 shl reg62, 4
6 2 A add reg62, [esp + a]
4 3F 1 mov reg63, reg1
6 3 3D add reg63, reg61
4 40 1 mov reg64, reg1
9 40 5 shr reg64, 5
6 4 B add reg64, [esp + 9]
b 3E 3F xor reg62, reg63
b 3E 40 xor reg62, reg64
6 2 0 add reg62, reg0
4 0 3E mov reg0, reg62
//v1
4 3E 0 mov reg62, reg0
8 3E 4 shl reg62, 4
6 2 C add reg62, [esp + 8]
4 3F 0 mov reg63, reg0
6 3 3D add reg63, reg61
4 40 0 mov reg64, reg0
9 40 5 shr reg64, 5
6 4 D add reg64, [esp + 7]
b 3E 3F xor reg62, reg63
b 3E 40 xor reg62, reg64
6 2 1 add reg62, reg1
4 1 3E mov reg1 reg62
//v2
4 3E 3 mov reg62, reg3
8 3E 4 shl reg62, 4
6 2 A add reg62, [esp + a]
4 3F 3 mov reg63, reg3
6 3 3D add reg63, reg61
4 40 3 mov reg64, reg3
9 40 5 shr reg64, 5
6 4 B add reg64, [esp + 9]
b 3E 3F xor reg62, reg63
b 3E 40 xor reg62, reg64
6 2 2 add reg62, reg2
4 2 3E mov reg2 reg62
//v3
4 3E 2 mov reg62, reg2
8 3E 4 shl reg62, 4
6 2 C add reg62, [esp + 8]
4 3F 2 mov reg63, reg2
6 3 3D add reg63, reg61
4 40 2 mov reg64, reg2
9 40 5 shr reg64, 5
6 4 D add reg64, [esp + 7]
b 3E 3F xor reg62, reg63
b 3E 40 xor reg62, reg64
6 2 3 add reg62, reg3
4 3 3E mov reg3, reg62
11 0 1 add reg60, 1
e 0 20 cmp reg60, 32
c 55 jnz tag2
---
f 13 pop delta
10 retn
---
|
没实现栈平衡,不过芜锁胃;反正最后return回去输出就行;
注意:写二进制文件时用小端序,而且以DWORD为基本单位,并以3个DWORD对齐,比如 c 55
写成:0c 00 00 00 55 00 00 00 00 00 00 00
;
还有个问题,根据调试,每次第一条指令开始是code[8] = 3C; 所以二进制文件需要填充垃圾信息,填多少?第一幅图中instru = code[3 * code[8] + 1010], 所以 括号里的内容为 : 1190 ;而写二进制文件需要以DWORD为单位,所以需要填充 1190 * 4 个 00 ;
通过:
level1
ida:
调试可以发现main挂不上,然后就发现旁边的函数长得和main都差不多,一个一个断点试,找到第三个是真正的main函数;
主要思路就是输入15个内容,进行异或和加运算,然后和v4开始的数据比较;
写出逆运算:
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
| #include <iostream> using namespace std;
int main() { int i, j; unsigned int buf[15] = { 0x04, 0x46, 0x81, 0x63, 0x14, 0x53, 0x17, 0x6D, 0x6A, 0x67, 0x76, 0x16, 0x34, 0x14, 0x34 }; int v0 = 0; char ans[16]; for (i = 0; i <= 14; ++i) { for (j = 0; j <= 2; ++j) { v0++; } } v0--; for (i = 14; i >= 0; --i) { for (j = 2; j >= 0;--j) { buf[i] -= v0--; buf[i] ^= i ^ j ^ 0x32; } ans[i] = buf[i]; } ans[15] = '\0';
printf("%s", ans); return 0; }
|
得到flag:SYC{0h_y0u1_finD0V0}
level2
ida:
进去之后第一感觉会发现main是个scanf函数,但是点进去之后会发现这个东西,让调试才能查看代码;点进这个函数之后会发现是个线程创建,注意调试时改变if判断的变量值为1;
在线程中可以发现以下代码:
在thread main 函数里有两个函数:check() 和 encrypt() ;
check一开始就执行,判断长度,以及输入的内容必须为数字;
encrypt读入key和输入的数据,将数据前12位与key加密运算;
最后比较数据;
写出encrypt的逆向算法:
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
| for (i = 0; i < 12; ++i) { switch (key[i]) { case 3: v4 += 23; v3 ^= 5; break; case 9: us[i] ^= key[i]; break; case 161: us[i] -= 2 * key[i]; break; case 196: us[i] -= 10; break; case 229: us[i] = inter(us[i]); break; default: break; } }
int inter(int a) { return a - 0x19; }
|
将比较数据经过以上运算得到flag:SYC{03062639056784}
level3
ida进去可以发现是加壳了,函数很少;
打开二进制格式搜索upx,果然就找到了老朋友:
upx加壳的标识码是 UPX! 全大写,拿不准改哪个就全部 Upx 都改成 UPX;
改完就可以脱壳了;
看代码:
看不懂子进程作用,反正主要内容在父进程里:输入内容后,先进入change函数,把4个4个的char内容放到4单位的int里;然后把这个int数组放到xor函数里,把每个字节都和j做异或运算,最后和v19比较数据,v19的内容就是cpy的16长度的字符串;
异或的逆运算还是异或,写出复原代码:
1 2 3 4 5 6 7 8 9 10
| for (i = 0; i < 16; i = i + 4) { for (j = 0; j <= 9; ++j) { we[i] = we[i] ^ j; we[i + 1] = we[i + 1] ^ (j + 1); we[i + 2] = we[i + 2] ^ (j + 2); we[i + 3] = we[i + 3] ^ (j + 3); } }
|
将逆向得到的cpy字符串带入we得到flag:SYC{0k_y0u_s0lv3_it_}
level4
这道题缺库不能调,直接看静态;
ida:
左图main函数,右图rc4函数,将enter用rc4加密了,密钥是 syclover:)
;
使用大厨把enter加密后的内容烤出来:(CyberChef (gchq.github.io))
发现开头是ELF,说明这加密出来的内容是个elf文件,将其写入二进制文件再用ida打开:
属于就正常了;
如上可知,输入16长度内容,然后进行tea算法(小魔改,每次异或了i),之后比较数据;
写出逆算法:
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
| int i; unsigned int j = 0,l , r ,sum = 0; int v[4] = { 0x6A318EC6 , 0x5B898EC2 , 0x42FB5DD1 , 0x50AC4C5F }; int k[4] = {0x11 , 0x22 , 0x33 , 0x44};
while (j < 3) { l = v[j]; r = v[j+1];
for (i = 0; i < 32; ++i) sum += DELTA;
for (i = 31; i >= 0; --i) { r -= (k[(sum >> 11) & 3] + sum) ^ (((l >> 5) ^ (16 * l)) + l) ^ i; sum -= DELTA; l -= (k[sum & 3] + sum) ^ (((r >> 5) ^ (16 * r)) + r) ^ i; }
v[j] = l; v[j + 1] = r;
sum = 0; j += 2; }
char* p; p = (char *)v; p[16] = '\0';
printf("%s", p);
|
得到flag:SYC{w3f-2hs-ij7-9is}
调试GLIBC_2.34小技巧
安装glibc-all-in-one
1 2 3
| sudo git clone https://github.com/matrix1001/glibc-all-in-one.git cd glibc-all-in-one/ sudo python3 update_list
|
下载glibc
1
| sudo ./download 2.35-0ubuntu3_amd64
|
安装patchelf
1 2 3 4
| git clone https://github.com/NixOS/patchelf.git cd patchelf sudo apt-get install autoconf automake libtool ./bootstrap.sh
|
继续:
1 2 3 4
| ./configure make make check sudo make install
|
配置ld.so
1
| patchelf --set-interpreter path/to/.so the/elf/you/debug
|
配置环境
1
| patchelf --set-rpath path/to/.so the/elf/you/debug
|
level6
逆向一个CPP服务器,得到flag,并编写socket客户端和远程服务器提交flag;
逆
ida查看服务器:
这是一个服务端框架,首先创建套接字类型文件,并返回fd文件饰描述符;
然后和IP端口进行绑定;
之后一直监听这个端口,直到接收客户端请求,执行处理,并且是多线程的处理;
而处理的主体在 CTask_server 里可以找到:
先发送 “Please …” (send) 到客户端,然后等待输入,被inside变量接收,之后进入cc加密,和tt生成的v7进行比较数据;
看看里面加密吧,都tea ptsd了,不想放图了;直接来吧:
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
| int i; unsigned int j = 0, l, r, sum = 0; int v[3] = { 0xED3E9980 , 0x57284856 ,0 }; int k[4] = { 0x6C , 0x30 , 0x76 , 0x33 };
l = v[0]; r = v[1];
for (i = 0; i < 32; ++i) sum += DELTA;
for (i = 31; i >= 0; --i) { r -= ((l >> 5) + k[3]) ^ (l + sum) ^ (16 * l + k[2]) ^ i; l -= ((r >> 5) + k[1]) ^ (r + sum) ^ (16 * r + k[0]) ^ i; sum -= DELTA; }
v[0] = l; v[1] = r;
char* p = (char*)v; p[9] = '\0';
printf("%s", p);
|
得到flag:D0Y0uKSk
写
如同服务端,自己写的客户端也需要一个框架,然后把发送的flag放到主体里就行;
根据题目中的链接,可以知道客户端只需要使用socket创建套接字后通过ip端口连接就行;
那么大概的框架就是: socket() -> 结构地址 -> connect() 连接到地址 -> 读 & 写 -> close() 结束;
内容:
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
| #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>
int main() { printf("Client Start\n"); int fd = socket(2, 1, 0);
char lines[] = "D0Y0uKSk";
struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("1.14.92.115"); serv_addr.sin_port = htons(1234);
printf("Connecting..\n"); connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); printf("Done !\n");
char buffer[40]; read(fd, buffer, sizeof(buffer) - 1); printf("%s\n", buffer); memset(&buffer, 0, sizeof(buffer));
printf("%s\n", lines); write(fd, lines, sizeof(lines) - 1);
read(fd, buffer, sizeof(buffer) - 1); printf("%s\n", buffer); memset(&buffer, 0, sizeof(buffer));
close(fd); return 0; }
|
结果:
更多socket学习
https://blog.csdn.net/m0_37947204/article/details/80489431
level7
给二进制加载器实现更多功能:1、转储节内容 2、输出数据符号 3、使用capstone反汇编.text段;
称之为环境恶心人之题;
题不难,但在wsl上装环境会变得千奇百怪,反正就是跑不起来,只有vm搞;
1 2 3
| sudo apt-get install binutils-dev apt-get install libcapstone-dev
|
完成这道题需要知晓一点点bfd和capstone,以及更多的模仿;
注意引头 bfd.h 和 capstone/capstone.h;
题目已经把各种各样的代码都实现好了,只要求增添几个功能,逐一实现:
转储节内容
这里要求命令行输入三个参数,而第三个参数为节名称,并打印节的原始字节;
那么可以在原来打印节的地方(main.cc)镶嵌一个东西进去:
flag一开始设置为0,找到同名节后设置为1;
第一个判断是否有三个参数并且调控数为0,则执行这个函数;
外面的判断是如果没有找到第三个参数一样的节名称,则此时flag依然是0,所以执行打印没有找到;
下面是函数具体实现:
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
| int print_origin_bytes(Section* sec, char *one) { int i,j = 0; if(sec->name == one) { printf("\n"); printf("contents of %s section\n", one); printf(" "); for(i = 0 ; i < 16 ; i++) { printf("%02jx ", i); } printf("\n\n"); i = 1; printf(" "); while(j < sec->size) { printf("%02jx ", sec->bytes[j++]); if(i == 16) { i = 0; printf("\n"); printf(" "); } i++; } printf("\n\n"); return 1; } return 0; }
|
输出数据符号
找到 loader.h 中的symbol类,可以发现 SymbolType 里面只有 SYM_TYPE_UKN SYM_TYPE_FUNC 两个,需要打印 DATA 符号,则添加一个 SYM_TYPE_DATA = 2,
;
之后找到 loader.cc 中的一个函数:load_symbol_bfd,可以发现其中有一步是给函数添加 FUNC项的,镶嵌如下内容:
当不添加FUNC项的内容时,添加DATA就好了;
最后改变下main.cc里面打印符号的地方为:
1 2 3 4
| printf(" %-40s 0x%016jx %s\n", sym->name.c_str(), sym->addr, (sym->type & Symbol::SYM_TYPE_FUNC) ? "FUNC" : "DATA");
|
使用capstone反汇编.text段
吐槽一下,edge搜索capstone 反汇编会出现一个博客,详细的记录了如何使用capstone;
具体函数实现:
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
| int disass_text(Binary bin) { csh dis; cs_insn *insns; Section *text; size_t n, i; Section* sec;
for(i = 0; i < bin.sections.size(); i++) { sec = &bin.sections[i]; if(sec->name == ".text") text = sec; }
if(!text) goto fail;
if(cs_open(CS_ARCH_X86, CS_MODE_64, &dis) != CS_ERR_OK) goto fail;
n = cs_disasm(dis, text->bytes, text->size, text->vma, 0, &insns);
if(n <= 0) goto fail;
printf("disassembly of .text section:\n"); for(i = 0;i < n;i++) { printf("0x%016jx\t%s\t\t%s\n", insns[i].address, insns[i].mnemonic, insns[i].op_str); }
cs_free(insns,n); cs_close(&dis);
return 0;
fail: printf("err\n"); return 1;
}
|
然后在main.cc的打印节后一部分调用就好了;
通过:
总结
纯粹对今年的有兴趣,5和7都比较新鲜,也是第一次手撸汇编了解bfd库,还挺有意思;