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)