wp拜读:[原创]H&NCTF RE 部分题解-CTF对抗-看雪-安全社区|安全招聘|kanxue.com
注:由于本题需要远程服务环境,所以无法复现只能通过分析伪代码加大佬的wp来理解这题了!
0.锁定关键伪代码
根据购买按钮的位置找到处理flag的逻辑:
这里点击就可以触发购买验证!
1.锁定发送网络请求的代码
继续向下找逻辑!
这个函数就算发送网络请求的函数了比较复杂:
下面就来一层层解析一些它!
a.整理复杂伪代码,并进行分析
先用chatgpt优化一下代码!
private void verifyPurchase(final String username, final double amount, final String signature) {
RequestQueue requestQueue = Volley.newRequestQueue(this);
StringRequest stringRequest = new StringRequest(
Request.Method.POST,//参数一
PURCHASE_URL,、、参数二
new Response.Listener<String>() {//**匿名内部类**
@Override
public void onResponse(String response) {
// 处理成功响应
showPurchaseResult(response);
}
},
new Response.ErrorListener() {//**匿名内部类**
@Override
public void onErrorResponse(VolleyError error) {
// 处理错误响应
handleError(error);
}
}
) {
@Override //重写StringRequest类的getParams方法
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("username", username);
params.put("amount", String.format("%.2f", amount));
params.put("signature", signature);
return params;
}
};
// 将请求添加到请求队列
requestQueue.add(stringRequest);
}
再简化一下代码结构:
private void verifyPurchase(final String str, final double d, final String str2)
{
//申请一个网络请求对象
RequestQueue requestQueue = Volley.newRequestQueue(this);
//创建这个网络请求的数据
StringRequest stringRequest = new StringRequest(
POST, URL.pURL, new R.L(){},new R.ErrorL() {}
)
{
//相当于设置网络请求的参数
}
//加入网络请求队列!
Volley.newRequestQueue(this).add(stringRequest)
}
这段代码简写出来就是这样:
通过Volley.newRequestQueue(this).add来添加网络请求,通过StringRequest构造要发送的请求参数,和处理请求放回的结果,这里是一个http请求,请求方法是post!下面来研究不同http返回的处理流程!
b.整理http请求返回的异常值
来看一下http请求返回error的处理方式:
直接让chatgpt整理一下Error返回值的处理方式:
- 402 Payment Required:
- 显示消息 “Insufficient funds to complete the purchase.”-资金不足,无法完成购买。
- 403 Forbidden:
- 显示一个对话框,通知用户禁止访问的错误。
- 404 Not Found:
- 显示消息 “User not found.”
- 默认处理:
- 显示通用错误消息。
- 无响应处理:
- 处理网络错误,未接收到响应。
下面分析:
发送的数据是用户名、资金、apk程序的特征值,可以通过抓包来知道发送的post请求参数!
直接白嫖大佬的图片:
c.整理http请求返回成功值后的操作
这里就很简单了,得到正确的回应后,app页面就会弹出网络请求收到的数据!!
如果收到flag自然也就会弹出flag!
2.分析后发现flag存储在服务器
通过抓包可以知道发送服务器的数据是什么格式!
所以直接手动用python在模拟一次发送数据包!
import socket
# 指定服务器地址和端口
server_address = ('hnctf.imxbt.cn', 20528)
# 指定表单数据
form_data = {
# 这里应该填入POST请求的具体数据
"Money": "114514.00",
"signature": "376c4fe518240bc513f67bf477d5d950d757d51bb58db594fa4551e248364413",
"username": "" #填自己的用户名即可
}
# 将表单数据转换为application/x-www-form-urlencoded格式
form_data_encoded = "&".join("{}={}".format(key, val) for key, val in form_data.items())
# 准备HTTP请求头
http_request = """POST /purchase HTTP/1.1\r
Host: hnctf.imxbt.cn:20528\r
If-None-Match: W/"12-hfcD7BKoo9fJ4GgYnq9Rj3aNpD8"\r
Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r
User-Agent: Dalvik/2.1.0 (Linux; U; Android 9; PCRT00 Build/PQ3A.190605.01231654)\r
Connection: Keep-Alive\r
Accept-Encoding: gzip\r
Content-Length: {}\r
\r
""".format(len(form_data_encoded))
# 完整的HTTP请求数据
http_request += form_data_encoded
# 创建TCP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器
sock.connect(server_address)
# 发送HTTP请求
sock.sendall(http_request.encode())
# 接收响应数据
response = sock.recv(4096)
print(response.decode())
# 关闭socket连接
sock.close()
发送过去后会有两种回显:
一个是:Did you cheat with this much money?!
另一个是:Not enough money!!
出题人给了金额的上限200000,那么获得flag 的逻辑也就出来了,发送出正确的金额就可以获得flag!
直接就可以使用二分法来爆破出金额,最后得到金额是114514.00
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 61
ETag: W/"3d-lbjcXjt9Oql9sd2OEm6pVRILsSE"
Date: Thu, 16 May 2024 01:31:23 GMT
Connection: keep-alive
Keep-Alive: timeout=5
H&NCTF{Congratulati0ns!_3cad3260-0c7e-4d98-afbc-814cc5221fcc}
下面就是二分法的爆破脚本:
import requests
from time import sleep
def check_money(money):
url = 'http://103.8.69.140:3000/purchase'
data = {
"username": "1",
"Money": money,
"signature": "376c4fe518240bc513f67bf477d5d950d757d51bb58db594fa4551e248364413"
}
headers = {
"Content-Type": "application/json"
}
try:
response = requests.post(url, json=data, headers=headers)
return response.text, response.status_code
except requests.exceptions.RequestException as e:
print(f"Network error: {e}")
return None, None
def find_correct_money(low, high):
attempts = 0
while low <= high:
mid = (low + high) // 2
print(f"Trying money: {mid}")
response_text, status_code = check_money(mid)
if response_text is None or status_code is None:
print("Failed to get response, retrying...")
sleep(2) # Wait for 2 seconds before retrying
attempts += 1
if attempts > 5: # Maximum 5 retries
print("Too many failed attempts, aborting.")
return None
continue
if "H&NCTF" in response_text:
print("Flag found:", response_text)
return mid
elif "Not enough money" in response_text:
low = mid + 1
elif "Did you cheat with this much money" in response_text or "Invalid signature" in response_text:
high = mid - 1
else:
print(f"Unexpected response: {response_text}, status code: {status_code}")
break
attempts = 0
return None
# Example usage
if __name__ == "__main__":
flag_money = find_correct_money(1, 200000)
if flag_money is not None:
print(f"The correct money value is: {flag_money}")
else:
print("Failed to find the correct money value")