[NullCon2025] Numberizer

<?php
ini_set("error_reporting", 0);

if(isset($_GET['source'])) {
    highlight_file(__FILE__);
}

include "flag.php";

$MAX_NUMS = 5;

if(isset($_POST['numbers']) && is_array($_POST['numbers'])) {

    $numbers = array();
    $sum = 0;
    for($i = 0; $i < $MAX_NUMS; $i++) {
        if(!isset($_POST['numbers'][$i]) || strlen($_POST['numbers'][$i]) > 4 || !is_numeric($_POST['numbers'][$i])) {
            continue;
        }
        $the_number = intval($_POST['numbers'][$i]);
        if($the_number < 0) {
            continue;
        }
        $numbers[] = $the_number;
    }
    $sum = intval(array_sum($numbers));

    if($sum < 0) {
        echo "You win a flag: $FLAG";
    } else {
        echo "You win nothing with number $sum ! :-(";
    }
}
?>
<html>
    <head>
        <title>Numberizer</title>
    </head>
    <body>
        <h1>Numberizer</h1>
        <form action="/" method="post">
            <label for="numbers">Give me at most 10 numbers to sum!</label><br>
            <?php
            for($i = 0; $i < $MAX_NUMS; $i++) {
                echo '<input type="text" name="numbers[]"><br>';
            }
            ?>
            <button type="submit">Submit</button>
        </form>
        <p>To view the source code, <a href="/?source">click here.</a>
    </body>
</html>

일단 $sum을 음수로 만들어줘야 하는데 컴퓨터에서는 int의 한계 범위가 있기 때문에 엄청 큰 양수를 만들어주면 된다.

php에서 엄청 큰 양수를 만들기 위해 어떤 것을 할 수 있는지 조건문에 있는 함수 취약점을 찾다가..

E를 입력하면 지수로 인식된다는 글을 찾았다.
https://github.com/php/php-src/issues/9311

is_numeric()은 E를 지수로 인식한다(여담이지만 소수도 numeric에 포함된다. 그래서 처음에 이걸로 푸는 줄 알았음)

api 개발 자료

아래는 다양한 API 개발 자료입니다.

  1. Creating a simple image processing API with Python and Flask
    • Learn how to build a basic image processing API using Python and Flask, covering setup, image resizing, format conversion, and deployment. Image
  2. Flutter(플러터) Python Flask 서버 만들어서 CartoonGAN 적용하기
    • 개요 학습된 머신러닝 모델들을 찾을 수 있는 tfhub에서 cartoongan 모델을 발견하게 되었습니다. 이미지를 만화같은 스타일로 변환해주는 모델인데 재미있을 것 같아서 모델 파일을 다운로드 받고. Image
  3. CycleGAN을 만든 사람이 한국인이라고? CycleGAN 논문 뜯어보기
    • Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks. Image
  4. How to create a simple API from a machine learning model in Python using Flask
    • Flask is one of the most straightforward but widely used Python microservices web frameworks for building APIs. Image
  5. GAN실습 - 환경구축 & BeautyGAN(1)
    • 이 자료는 한국인공지능협회에서 진행한 2021년 사업주 직업능력 개발훈련으로 BASIC AI(32시간)-박성주 강사의 온라인 교육내용을 참고한 것입니다. Image
  6. Windows - Nvidia Driver, CUDA, cuDNN, PyTorch 설치
    • 최종 환경 CPU : Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz GPU : NVIDIA GeForce RTX 2060 SUPER. Image
  7. CUDA & cuDNN 설치하는 방법
    • 환경 : Windows 11설치버전 : cuDNN 8.2.2 / CUDA 11.4. Image

basic RCE 05

요것도 내부 스터디 준비하면서 풀어야 하는 문제
요건 이제 upx 패킹에 대한 문제이다.

UPX Packing

UPX Example

그냥 Register now하면 이렇게 뜬다.
먼저 패킹이란 무엇이고 UPX가 무엇이닞 짚고 넘어가자.

패킹이란? -> 컴프레서와 프로텍터로 나뉨
압축! 다만, zip과 다른 부분은 확장자는 그대로 실행 파일 확장자를 유지, 파일의 코드나 PE구조를 압축하는 것이 패킹의 주요 포인트.
이중에 하나가 UPX 패킹, Ultimate Packer for Executables

UPX Releases

UPX GitHub

열어보면 요렇게 있을 것이다. 이 폴더에 압축 풀어야 하는 파일도 배치시켜줘야함

UPX Folder

upx 언패킹은 다음과 같이 함

upx -d -o <저장할이름> <언패킹할파일이름> 
//파일 이름에 확장도 넣어서 작성해줘야 함

자주 쓰는 ida로 함수 개수 비교

IDA Function Count

동적 분석 툴로도 비교

Dynamic Analysis Tool

hxd로 파일을 까보면 위와 같이 표기되어 있음! (함수 수가 너무 적다. 코드 분석이 이상하게 된다면 패킹된 것이 아닌지 확인해보기)

코드엔진 RCE 05 문제는 동적 분석으로 넘어가보자

Dynamic Analysis

문자열 참조 먼저..

String Reference

일단 문자열을 보고 대충 이건가 try해봤는데 맞았고.. 너무 날로 먹은 거 같으니 자세한 assembly 분석을 써보자

문자열은 그냥 보기 편하도록 문법에 맞지 않게 넣음 

mov eax, 입력한 user 네임 
mov edx, "Registered User"
call 05unpack.403b2c -> eax와 edx 맞는지 구분하는 함수 
jne 05unpack.440f8c -> wrong 출력 

...

mov eax, 입력한 serial 
mov edx, "754-GFX-IER-954"
call 05unpack.403B2C -> eax와 edx 맞는지 구분하는 함수 
jne 05unpack.440F72 -> wrong 출력 
이후로는 congrats! you cracked this crackme 출력하는 코드

…너무 대충 작성한 거 같긴 해

basic RCE 04

내부 스터디 준비하면서 풀어야 하는 문제
이걸 풀기 위해서는 먼저 안티 디버깅을 알아야 한다.
여기서 쓰인 안티 디버깅이 뭔지부터 파악해보자

이 프로그램은 디버거 프로그램을 탐지하는 기능을 갖고 있다. 디버거를 탐지하는 함수의 이름은 무엇인가

image

디버거에서 실행해봤더니 아니나 다를까 이렇게 문자열이 출력된다.

image

ida로 열었더니 이부분이 딱 보인다. 저 함수는 대표적인 안티 디버깅 방법 중 하나이다.

구글링에서 찾은 해당 함수 그대로 인용하자면.. https://iforint.tistory.com/52
가장 간단한 디버거 탐지 윈도우 API 함수이다.
이 함수는 PEB 구조에서 IsDebugged 필드를 찾아 디버그가 동작 중인지 판단하여 동작 중이 아니라면 0, 동작 중이라면 0이 아닌 값을 반환한다.
그럼 PEB란? -> process environment block이다. 프로세스 관련 환경 정보들을 담고 있다(운체에서 배운다)
fs 레지스터가 이 PEB를 가리킬 수 있다. FS:[0x30]가 PEB 시작 위치(참고로 offset 0은 TEB 시작 위치이다)
수많은(은아닌가) 환경 정보들 중에 IsDebugged가 있는데 해당 함수는 이 필드를 검사한다.

우회하는 방법은

image

일단 bp 걸어주고

image

f8 누르면 반환값이 0이 아닌 값일 텐데

image

space 바를 눌러서 0으로 바꿔주면

image

이렇게 뜬다!

dMd

대충 이러하다. ida로 까보자

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v6; // rax
  __int64 v7; // rax
  __int64 v8; // rax
  __int64 v9; // rax
  __int64 v10; // rax
  __int64 v11; // rax
  __int64 v12; // rax
  __int64 v13; // rax
  __int64 v14; // rax
  __int64 v15; // rax
  __int64 v16; // rax
  __int64 v17; // rax
  __int64 v18; // rax
  __int64 v19; // rax
  __int64 v20; // rax
  __int64 v21; // rax
  __int64 v23; // rax
  __int64 v24; // rax
  __int64 v25; // rax
  __int64 v26; // rax
  __int64 v27; // rax
  __int64 v28; // rax
  __int64 v29; // rax
  __int64 v30; // rax
  __int64 v31; // rax
  __int64 v32; // rax
  __int64 v33; // rax
  __int64 v34; // rax
  __int64 v35; // rax
  __int64 v36; // rax
  __int64 v37; // rax
  char v38; // [rsp+Fh] [rbp-71h] BYREF
  char v39[16]; // [rsp+10h] [rbp-70h] BYREF
  char v40[8]; // [rsp+20h] [rbp-60h] BYREF
  __int64 v41; // [rsp+28h] [rbp-58h]
  char v42[56]; // [rsp+30h] [rbp-50h] BYREF
  unsigned __int64 v43; // [rsp+68h] [rbp-18h]

  v43 = __readfsqword(0x28u);
  std::operator<<<std::char_traits<char>>(&std::cout, "Enter the valid key!\n", envp);
  std::operator>><char,std::char_traits<char>>(&edata, v42);
  std::allocator<char>::allocator(&v38);
  std::string::string(v39, v42, &v38);
  md5(v40, v39);
  v41 = std::string::c_str((std::string *)v40);
  std::string::~string((std::string *)v40);
  std::string::~string((std::string *)v39);
  std::allocator<char>::~allocator(&v38);
  if ( *(_WORD *)v41 == 14391
    && *(_BYTE *)(v41 + 2) == 48
    && *(_BYTE *)(v41 + 3) == 52
    && *(_BYTE *)(v41 + 4) == 51
    && *(_BYTE *)(v41 + 5) == 56
    && *(_BYTE *)(v41 + 6) == 100
    && *(_BYTE *)(v41 + 7) == 53
    && *(_BYTE *)(v41 + 8) == 98
    && *(_BYTE *)(v41 + 9) == 54
    && *(_BYTE *)(v41 + 10) == 101
    && *(_BYTE *)(v41 + 11) == 50
    && *(_BYTE *)(v41 + 12) == 57
    && *(_BYTE *)(v41 + 13) == 100
    && *(_BYTE *)(v41 + 14) == 98
    && *(_BYTE *)(v41 + 15) == 48
    && *(_BYTE *)(v41 + 16) == 56
    && *(_BYTE *)(v41 + 17) == 57
    && *(_BYTE *)(v41 + 18) == 56
    && *(_BYTE *)(v41 + 19) == 98
    && *(_BYTE *)(v41 + 20) == 99
    && *(_BYTE *)(v41 + 21) == 52
    && *(_BYTE *)(v41 + 22) == 102
    && *(_BYTE *)(v41 + 23) == 48
    && *(_BYTE *)(v41 + 24) == 50
    && *(_BYTE *)(v41 + 25) == 50
    && *(_BYTE *)(v41 + 26) == 53
    && *(_BYTE *)(v41 + 27) == 57
    && *(_BYTE *)(v41 + 28) == 51
    && *(_BYTE *)(v41 + 29) == 53
    && *(_BYTE *)(v41 + 30) == 99
    && *(_BYTE *)(v41 + 31) == 48 )
  {
    v3 = std::operator<<<std::char_traits<char>>(&std::cout, 84LL);
    v4 = std::operator<<<std::char_traits<char>>(v3, 104LL);
    v5 = std::operator<<<std::char_traits<char>>(v4, 101LL);
    v6 = std::operator<<<std::char_traits<char>>(v5, 32LL);
    v7 = std::operator<<<std::char_traits<char>>(v6, 107LL);
    v8 = std::operator<<<std::char_traits<char>>(v7, 101LL);
    v9 = std::operator<<<std::char_traits<char>>(v8, 121LL);
    v10 = std::operator<<<std::char_traits<char>>(v9, 32LL);
    v11 = std::operator<<<std::char_traits<char>>(v10, 105LL);
    v12 = std::operator<<<std::char_traits<char>>(v11, 115LL);
    v13 = std::operator<<<std::char_traits<char>>(v12, 32LL);
    v14 = std::operator<<<std::char_traits<char>>(v13, 118LL);
    v15 = std::operator<<<std::char_traits<char>>(v14, 97LL);
    v16 = std::operator<<<std::char_traits<char>>(v15, 108LL);
    v17 = std::operator<<<std::char_traits<char>>(v16, 105LL);
    v18 = std::operator<<<std::char_traits<char>>(v17, 100LL);
    v19 = std::operator<<<std::char_traits<char>>(v18, 32LL);
    v20 = std::operator<<<std::char_traits<char>>(v19, 58LL);
    v21 = std::operator<<<std::char_traits<char>>(v20, 41LL);
    std::ostream::operator<<(v21, &std::endl<char,std::char_traits<char>>);
    return 0;
  }
  else
  {
    v23 = std::operator<<<std::char_traits<char>>(&std::cout, 73LL);
    v24 = std::operator<<<std::char_traits<char>>(v23, 110LL);
    v25 = std::operator<<<std::char_traits<char>>(v24, 118LL);
    v26 = std::operator<<<std::char_traits<char>>(v25, 97LL);
    v27 = std::operator<<<std::char_traits<char>>(v26, 108LL);
    v28 = std::operator<<<std::char_traits<char>>(v27, 105LL);
    v29 = std::operator<<<std::char_traits<char>>(v28, 100LL);
    v30 = std::operator<<<std::char_traits<char>>(v29, 32LL);
    v31 = std::operator<<<std::char_traits<char>>(v30, 75LL);
    v32 = std::operator<<<std::char_traits<char>>(v31, 101LL);
    v33 = std::operator<<<std::char_traits<char>>(v32, 121LL);
    v34 = std::operator<<<std::char_traits<char>>(v33, 33LL);
    v35 = std::operator<<<std::char_traits<char>>(v34, 32LL);
    v36 = std::operator<<<std::char_traits<char>>(v35, 58LL);
    v37 = std::operator<<<std::char_traits<char>>(v36, 40LL);
    std::ostream::operator<<(v37, &std::endl<char,std::char_traits<char>>);
    return 0;
  }
}

일단 if문 충족하면 되는데 input 부분을 잘 봐야 한다.

char v38; // [rsp+Fh] [rbp-71h] BYREF
char v39[16]; // [rsp+10h] [rbp-70h] BYREF
char v40[8]; // [rsp+20h] [rbp-60h] BYREF
__int64 v41; // [rsp+28h] [rbp-58h]
char v42[56]; // [rsp+30h] [rbp-50h] BYREF
unsigned __int64 v43; // [rsp+68h] [rbp-18h]

v43 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "Enter the valid key!\n", envp);
std::operator>><char,std::char_traits<char>>(&edata, v42);
std::allocator<char>::allocator(&v38);
std::string::string(v39, v42, &v38);
md5(v40, v39);
v41 = std::string::c_str((std::string *)v40);
std::string::~string((std::string *)v40);
std::string::~string((std::string *)v39);
std::allocator<char>::~allocator(&v38);

md5가 있다.
일단 char형들을 모아보자 이건 IDA view에서 하는 게 빠르다

빨간색 친구들을 따라가면 된다.
780438d5b6e29db0898bc4f0225935c0
이걸 md5 cracking을 진행해야 한다.

MD5 Online

위 사이트 이용했다.