[!] Exploit trong post này đã bị fix.
Khoảng tháng 5, 2022, trên các diễn đàn/chợ đen về Liên Minh Huyền Thoại có quảng cáo một loại ma thuật đen có thể giúp bạn mở khóa tất cả skin. Là một thằng chơi chùa như mình thì làm sao lại có thể bỏ qua cái thứ hắc ám này được cơ chứ 😊
Ban đầu mình nghĩ là thứ gì đó cao siêu hơn cơ, kiểu mua được tất cả skin trong shop mà không tốn một đồng RP nào. Mà đã là quảng cáo thì có bao giờ nói chính xác về công dụng sản phẩm đâu 😉. Thực ra không hẳn là “mua skin không mất RP” như mình nghĩ. Bạn vẫn cần 1 lượng RP nhất định đủ để mua cái skin mà bạn muốn, sau đó chạy tool và nó sẽ trả lại RP cho bạn. Sau khi RP được trả về, cái skin bạn vừa mua ban nãy cũng bay theo gió luôn 😆
Sau khi xem cách hoạt động của nó, thiết nghĩ cái tool kia có công dụng hoàn trả lại skin và RP. Vì người dùng hoàn trả khi đang trong quá trình chọn skin để vào trận, nên vẫn có thế sử dụng skin đó đến hết trận đấu.
Nhưng tại sao nó lại là exploit và được rao bán trong khi đơn thuần chỉ là cơ chế game thôi nhỉ 🤔
Ngâm cứu
Để có thể hoàn trả, bạn sẽ phải sử dụng thứ được gọi là Kỷ vật hoàn trả. Mỗi lần hoàn trả sẽ tiêu hao 1 kỷ vật.
Kỷ vật hoàn trả là một cách khác để yêu cầu hoàn trả nội dung/vật phẩm trong Liên Minh Huyền Thoại và Đấu Trường Chân Lý, đồng thời cách này sẽ có những quy tắc riêng.
Các quy tắc của nó như sau:
- Mỗi tài khoản LMHT sẽ có 3 kỷ vật hoàn trả.
- Kỷ vật hoàn trả dùng để nhận lượt hoàn trả đối với:
- Nội dung/vật phẩm chưa sử dụng trong vòng 14 - 90 ngày sau khi mua
- Nội dung/vật phẩm đã sử dụng trong vòng 90 ngày sau khi mua
- Số tiền hoàn trả sẽ tương đương và cùng đơn vị tiền tệ bạn đã dùng để mua. Ví dụ: nếu mua một tướng đang giảm giá bằng RP, bạn sẽ được hoàn lại số RP đã bỏ ra ngay cả khi vị tướng đó không còn được giảm giá nữa.
- Bạn có thể sở hữu tối đa cùng lúc 3 kỷ vật hoàn trả. Bạn sẽ được bổ sung thêm 1 kỷ vật mỗi năm nếu như có ít hơn 3 kỷ vật.
- Kỷ vật sẽ biến mất sau khi bạn sử dụng. Không có loại kỷ vật nào để hoàn lại kỷ vật hoàn trả.
Oh, vậy tại sao cái tool kia có thể hoàn trả bao nhiêu lần cũng được nhỉ?
Reverse
Được 1 người bạn quen qua Discord gửi tool, mình thử vọc xem nó hoạt động ra sao. Ngồi mò một lúc thấy tool không có gì đặc biệt khác ngoài việc sử dụng API này để hoàn trả:
POST ${region}.store.leagueoflegends.com/storefront/v3/refund
với payload như sau:
{
"accountId": accId,
"transactionId": firstId,
"inventoryType": inventoryType,
"language": "en_US"
}
Trong đó accountId
là id tài khoản người dùng, transactionId
là id của giao dịch đầu tiên, inventoryType
là loại vật phẩm.
Nhìn đi nhìn lại thì vẫn chỉ thấy nó là một request hoàn toàn bình thường, không có gì đặc biệt. Vậy nên mình đoán có thể do request khi đang trong quá trình chọn tướng sẽ không tiêu hao kỷ vật chăng?
Đúng là phải thử mới biết được 😄
GGWP
Lúc này mình mới nhận ra rằng Garena không có cơ chế hoàn trả như server Riot 🙁
Welp, tắt máy đi ngủ thôi 😴
Comeback
6 tháng 1 2023, game đã chính thức về với mái nhà Riot.
Một tuần sau, mình mới nhớ ra rằng mình đã từng có một bài nghiên cứu về cơ chế refund nhưng đã dừng lại vì server Garena không làm được. Vâng chính là post này đây 😁
Đào sâu hơn
Mình thử dùng cái tool hồi đó nhưng không được, vậy chắc hẳn Riot xài URL store khác so với các server của Riot trước đó.
Thử gọi API get để get url store xem sao:
curl -X GET /lol-store/v1/getStoreUrl
"https://vn2-red.lol.sgp.pvp.net"
Đúng như dự đoán, server VN không dùng URL store.leagueoflegends.com
. Nhớ lại nghiên cứu hồi trước, sử dụng store url bên trên với endpoint /storefront/v3/refund
để có thể refund.
API này có 3 param chính:
accountId
: id của tài khoảntransactionId
: id giao dịch cần refundinventoryType
: loại vật phẩm refund
accountId
có thể lấy thông qua /lol-summoner/v1/current-summoner
curl -X GET /lol-summoner/v1/current-summoner
___
{
"accountId" : 312104550701xxxx,
"displayName" : "Fallen Angel",
"gameName" : "Fallen Angel",
"internalName" : "fallenangel",
"nameChangeFlag" : false,
"percentCompleteForNextLevel" : 22,
"privacy" : "PUBLIC",
"profileIconId" : 3150,
"puuid" : "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"rerollPoints" :
{
"currentPoints" : 482,
"maxRolls" : 2,
"numberOfRolls" : 1,
"pointsCostToRoll" : 250,
"pointsToReroll" : 18
},
"summonerId" : 312104550701xxxx,
"summonerLevel" : 1337,
"tagLine" : "1337",
"unnamed" : false,
"xpSinceLastLevel" : 792,
"xpUntilNextLevel" : 3456
}
yup nó là 312104550701xxxx
đó
transactionId
và inventoryType
không thể dùng LCU để lấy mà phải sử dụng API store
curl -X GET https://vn2-red.lol.sgp.pvp.net/storefront/v3/history/purchase
___
{
"player": {
"accountId": 312104550701xxxx,
"rp": 31337,
"ip": 1337,
"summonerLevel": 1337
},
"catalog": [
{
"itemId": 516001,
"inventoryType": "CHAMPION_SKIN",
"iconUrl": "championsskin_516001.jpg",
"releaseDate": 1503502200000,
"name": "Thunder Lord Ornn",
"tags": [
"champions_516"
],
"parentItem": {
"inventoryType": "CHAMPION",
"itemId": 516
},
"hasVelocityRules": false
}
],
"refundCreditsRemaining": 3,
"enableGiftingHistory": true,
"enableRpPurchaseHistory": false,
"showRemainingTokens": true,
"transactions": [
{
"itemId": 233,
"inventoryType": "CHAMPION_SKIN",
"datePurchased": "01/12/23",
"transactionId": "VN2-fdc00000-5119-4b7f-868b-cfd97b0e7c9e",
"amountSpent": 1350,
"quantity": 1,
"currencyType": "RP",
"refundable": true
}
]
}
Bạn có thể thấy "transactionId": "VN2-fdc00000-5119-4b7f-868b-cfd97b0e7c9e",
. Đó chính là id giao dịch để có thể hoàn trả. inventoryType
chính là loại mặt hàng, như ở đây mình mua skin Ornn Thần Sấm thì nó là CHAMPION_SKIN
.
Khai thác
Biết cách nó hoạt động rồi, giờ thử thực hành xem sao :3
Kết nối tới LCU
Để có thể kết nối tới LCU, đầu tiên bạn cần phải mở League Client lên.
Sau đó vào thư mục chứa LCU ..?/Riot Games/League of Legends/
hoặc có thể bật Task Manager lên và click chuột phải vào process LeagueClient.exe chọn “Open file location”
Bạn sẽ thấy một file có tên là lockfile, mở nó bạn sẽ thấy như sau:
LeagueClient:1234:31337:aBcdeFG1hI2j3KLMnopq4r:https
Lấy dấu hai chấm làm điểm ngăn cách, ta được 5 phần từ trái qua như sau:
- Phần thứ ba là cổng của kết nối
- Phần thứ tư có thể coi là pass
- Giao thức kết nối thì mặc định là https
Có nhiều thư viện hỗ trợ trong việc kết nối tới LCU, đối với Python thì có lcu-driver
Nhưng thế thì game hơi dễ, mình muốn đi đường vòng cơ 😁
Vậy nên mình sẽ lấy thông qua lockfile.
Đầu tiên cần viết 1 hàm lấy process theo tên:
def get_process_by_name(process_name):
while True:
for proc in psutil.process_iter():
try:
if process_name in proc.name():
return proc
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
Sau đó lấy pass và port thông qua lockfile:
class LeagueClientAPI(object):
def __init__(self):
print('Waiting for League client...')
self.process = get_process_by_name("LeagueClientUx")
self.lockfile = open(os.path.join(self.process.cwd(), "lockfile"), 'r').read()
split = self.lockfile.split(":")
self.process_name = split[0]
self.process_id = split[1]
self.port = split[2]
self.password = str(base64.b64encode(("riot:" + split[3]).encode("utf-8")), "utf-8")
self.protocol = split[4]
self.access_token = requests.get(
self.protocol + "://127.0.0.1:" + self.port + "/lol-rso-auth/v1/authorization/access-token",
verify=False,
headers={"Authorization": "Basic " + self.password}).json()
Lấy thêm access_token
để tiện cho việc request api store sau này.
Hàm request tới store:
def storeAPI(self, path):
token = self.access_token["token"]
return requests.get("https://" + "vn2-red.lol.sgp.pvp.net" + path,
verify=False,
headers={"Authorization": "Bearer " + token}
)
Hàm refund:
def postRefund(self, path, json=None):
token = self.access_token["token"]
return requests.post(
self.protocol + "://" + "vn2-red.lol.sgp.pvp.net" + path,
verify=False,
headers={"Authorization": "Bearer " + token},
json=json
)
Test
Làm theo trình tự: Kết nối LCU -> Lấy accountId
-> transactionId
, inventoryType
-> Refund là OK 😊:
api = api.LeagueClientAPI()
summoner = api.get('/lol-summoner/v1/current-summoner').json()
accId = summoner["accountId"]
name = summoner["displayName"]
print("Current account: " + name)
transaction = api.storeAPI('/storefront/v3/history/purchase').json()
transactionId = transaction['transactions']
i = 1
for e in transactionId:
first = e['transactionId']
inventoryType = e['inventoryType']
if i == 1:
break
i += 1
data = {"accountId":accId,"transactionId":first,"inventoryType":inventoryType,"language":"en_US"}
boost = api.postRefund('/storefront/v3/refund', data)
print("Done!")
time.sleep(5)
Vậy là đã xong, giờ bạn chỉ cần vào trận thấy pick tướng, sau đó mua skin của tướng đó rồi refund là bạn có thể chơi mà vẫn được trả lại RP 😋
Source code
Tất cả những gì được sử dụng trong post này đều có tại repo:
https://github.com/rumi-chan/RefundExploit/
FAQ
Có bị ban acc không?
Uhhhh, mình không biết :v, nhưng bạn vẫn nên cẩn thận vì đây là exploit, mình không khuyến khích lạm dụng.
Ngoài dùng với skin ra thì nó còn có thể refund cái gì nữa không?
Không chỉ với skin, bạn còn có thể dùng với tướng. Thậm chí còn có thể refund vật phẩm mở từ rương hextech 🤣
Acc hết Kỷ vật hoàn trả thì có dùng được không?
Được.