1. 程式人生 > >180828 逆向-網鼎杯(3-2)

180828 逆向-網鼎杯(3-2)

I_like_pack

IDA載入一看啥都沒有,再根據題目名顯然是個殼
windows下脫殼相對而言麻煩一些,ExeInfoPe查殼啊、各種殼的針對性操作啊啥的
Linux下一方面系統開源隨便魔改,另一方面有一個/proc/pid/mem的檔案可以直接讀取程序的記憶體,使得dump極為容易

本題放到系統下跑起來後發現如果輸入會回顯“NO”,而不輸入的話大概三秒就會自動結束
這顯然是alarm函式的功勞

如果僅是alarm函式的話,其實可以比拼一下手速,畢竟三秒鐘還算在人類的反應速度內,另一方面也可以通過sh指令碼來執行dump
試了一下cat /proc/pid/mem會報錯,在這裡有官方的說明,提供了三種方法
1. 獲取maps,根據模組地址來讀取程式的記憶體
2. open mem以後attach目標程序使其暫停,然後即可讀
3. gcore pid

嘗試了一下,其中第一種方法可以直接使用–因為只是讀取mem檔案
通過這個指令碼

#! /usr/bin/env python
import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'r', 0)
for line in maps_file.readlines():  # for each mapped region
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
    if m.group(3) == 'r':  # if this is a readable region
start = int(m.group(1), 16) end = int(m.group(2), 16) mem_file.seek(start) # seek to region start chunk = mem_file.read(end - start) # read region contents print chunk, # dump contents to standard output maps_file.close() mem_file.close()

操作方法如下:
後臺起一個程序可以快速獲得pid
然後通過上面的指令碼來dump

而第二、第三種方法由於依賴ptrace,對於使用ptrace(TRACE_ME)的程序就會報錯
本題中的程式就有使用這個方法來反除錯

對於trace_me,有兩種方法可以繞過:
1. 直接用偵錯程式啟動子程序,斷在ptrace之前然後跳過它的執行
不過這種方法會被alarm中斷掉,當然也很好繞過,只需要同樣跳過alarm的執行就好
缺點有兩個,1是浪費時間,2是靜態連結很難識別,本題是使用動態連結,相對還算好找
2. 通過LD_PRELOAD來覆蓋函式
LD_PRELOAD可以指定載入庫,此時如果庫中有與其他動態連結庫同名的函式將會覆蓋,使得原函式失效

這裡講一下後者的操作方法:

unsigned int alarm(unsigned int seconds)
{
    ;
}
long ptrace()
{
    ;
}

將上述程式碼編譯成so
gcc --shared fake.c -o fake.so
然後通過LD_PRELOAD載入
LD_PRELOAD=./fake.so ./re
此時即可發現alarm失效

然後通過ps或其他方法查到pid後,用gcore pid即可dump

然後通過字串搜尋即可找到main函式

一個數組亂序比對,直接dump即可得到flag

a = [11, 8, 7, 7, 8, 12, 3, 2, 16, 6, 13, 5, 7, 16, 4, 1, 0, 15, 16, 8, 3, 6, 14, 16, 0, 8, 6, 9, 12, 14, 13, 11, 15, 7, 11,14]

for i in range(36):
    print(chr(Dword(a[i]*4+0x60f0e0)+45)),

最好的語言

這題問題太大了!
做之前我先去找了web隊友過來嚴陣以待,開啟以後根本不是PHP!

開啟以後發現跟之前SUCTF的一題一毛一樣,給瞭解析過後的pyc文字
當時寫過輪子可以按照pyc格式進行解析和還原
這個pyc解析網上大概有兩種
pyc解析1
pyc解析2

除了都把解析出的位元組碼刪去以外,區別主要在兩點
屬性標題一種為argcount另一種為<argcount>xxx</argcount>
排列順序一種將consts放在names之前,另一種將consts放在之後

我寫的指令碼僅能針對前者,而本題遇到的後者需要手動修改一下
不過其實難度也不大,通過正則替換還是比較容易的

指令碼在這裡

解析得到pyc,然後線上反編譯即可得到python原始碼

import base64
from hashlib import md5
import random
import string
f = 'flag{*******}'

def _(b):
    o = ''.join(random.sample(string.digits, 4))
    s = ''
    for i in range(len(b)):
        s += chr(ord(b[i]) ^ ord(o[i % 4]))

    return s


def ____(a):
    ___ = md5()
    ___.update(a)
    return ___.digest()


e = _(f[:12]) + ____(f[12:19]) + _(f[19:])
print base64.b64encode(e)
e = 'U1VQU05pSHdqCEJrQu7FS7Vngk1OTQ58qqghXmt2AUdrcFBBUEU='

前後兩段是通過隨機數異或出來的,中間一段則是md5
分析一下可以知道_函式加密後長度不變,因此12~12+32
扔去解密得到613u21i
前一段由”flag”得到key=”5914”
後一段由結尾字元”}”得到key的第二位為8
其餘位爆破,篩選出在ASCII範圍內的,然後肉眼選擇看起來像的

import base64
import random
import string
# "5914"
def foo_a(b):
    o = ''.join(random.sample(string.digits, 4))
    o = "5914"
    s = ''
    for i in range(len(b)):
        s += chr(ord(b[i]) ^ ord(o[i % 4]))

    return s

def foo_b(b):
    r = []
    for i in range(10):
        for j in range(10):
            if(j==i):
                continue
            for k in range(10):
                if(k==j or k==i):
                    continue
                if(k==8 or i==8 or j==8):
                    continue
                o = str(i) + "8" + str(j) + str(k)
                s = ''
                for l in range(len(b)):
                    if(ord(b[l]) ^ ord(o[l % 4])>127):
                        break
                    s += chr(ord(b[l]) ^ ord(o[l % 4]))
                else:
                    r.append((o, s))


    return r


e = b'U1VQU05pSHdqCEJrQu7FS7Vngk1OTQ58qqghXmt2AUdrcFBBUEU='
e = base64.b64decode(e)
print(e)
a = e[:12]
b = e[12:12+16]
c = e[12+16:]

print(foo_a(a.decode()),end='')
print("613u21i",end='')
print()
for i in (foo_b(c.decode())):
    print(i)